'use strict'; var parseSvgPath = require('parse-svg-path'); var constants = require('./constants'); var CIRCLE_SIDES = constants.CIRCLE_SIDES; var SQRT2 = constants.SQRT2; var cartesianHelpers = require('../../selections/helpers'); var p2r = cartesianHelpers.p2r; var r2p = cartesianHelpers.r2p; var iC = [0, 3, 4, 5, 6, 1, 2]; var iQS = [0, 3, 4, 1, 2]; exports.writePaths = function(polygons) { var nI = polygons.length; if(!nI) return 'M0,0Z'; var str = ''; for(var i = 0; i < nI; i++) { var nJ = polygons[i].length; for(var j = 0; j < nJ; j++) { var w = polygons[i][j][0]; if(w === 'Z') { str += 'Z'; } else { var nK = polygons[i][j].length; for(var k = 0; k < nK; k++) { var realK = k; if(w === 'Q' || w === 'S') { realK = iQS[k]; } else if(w === 'C') { realK = iC[k]; } str += polygons[i][j][realK]; if(k > 0 && k < nK - 1) { str += ','; } } } } } return str; }; exports.readPaths = function(str, gd, plotinfo, isActiveShape) { var cmd = parseSvgPath(str); var polys = []; var n = -1; var newPoly = function() { n++; polys[n] = []; }; var k; var x = 0; var y = 0; var initX; var initY; var recStart = function() { initX = x; initY = y; }; recStart(); for(var i = 0; i < cmd.length; i++) { var newPos = []; var x1, x2, y1, y2; // i.e. extra params for curves var c = cmd[i][0]; var w = c; switch(c) { case 'M': newPoly(); x = +cmd[i][1]; y = +cmd[i][2]; newPos.push([w, x, y]); recStart(); break; case 'Q': case 'S': x1 = +cmd[i][1]; y1 = +cmd[i][2]; x = +cmd[i][3]; y = +cmd[i][4]; newPos.push([w, x, y, x1, y1]); // -> iQS order break; case 'C': x1 = +cmd[i][1]; y1 = +cmd[i][2]; x2 = +cmd[i][3]; y2 = +cmd[i][4]; x = +cmd[i][5]; y = +cmd[i][6]; newPos.push([w, x, y, x1, y1, x2, y2]); // -> iC order break; case 'T': case 'L': x = +cmd[i][1]; y = +cmd[i][2]; newPos.push([w, x, y]); break; case 'H': w = 'L'; // convert to line (for now) x = +cmd[i][1]; newPos.push([w, x, y]); break; case 'V': w = 'L'; // convert to line (for now) y = +cmd[i][1]; newPos.push([w, x, y]); break; case 'A': w = 'L'; // convert to line to handle circle var rx = +cmd[i][1]; var ry = +cmd[i][2]; if(!+cmd[i][4]) { rx = -rx; ry = -ry; } var cenX = x - rx; var cenY = y; for(k = 1; k <= CIRCLE_SIDES / 2; k++) { var t = 2 * Math.PI * k / CIRCLE_SIDES; newPos.push([ w, cenX + rx * Math.cos(t), cenY + ry * Math.sin(t) ]); } break; case 'Z': if(x !== initX || y !== initY) { x = initX; y = initY; newPos.push([w, x, y]); } break; } var domain = (plotinfo || {}).domain; var size = gd._fullLayout._size; var xPixelSized = plotinfo && plotinfo.xsizemode === 'pixel'; var yPixelSized = plotinfo && plotinfo.ysizemode === 'pixel'; var noOffset = isActiveShape === false; for(var j = 0; j < newPos.length; j++) { for(k = 0; k + 2 < 7; k += 2) { var _x = newPos[j][k + 1]; var _y = newPos[j][k + 2]; if(_x === undefined || _y === undefined) continue; // keep track of end point for Z x = _x; y = _y; if(plotinfo) { if(plotinfo.xaxis && plotinfo.xaxis.p2r) { if(noOffset) _x -= plotinfo.xaxis._offset; if(xPixelSized) { _x = r2p(plotinfo.xaxis, plotinfo.xanchor) + _x; } else { _x = p2r(plotinfo.xaxis, _x); } } else { if(noOffset) _x -= size.l; if(domain) _x = domain.x[0] + _x / size.w; else _x = _x / size.w; } if(plotinfo.yaxis && plotinfo.yaxis.p2r) { if(noOffset) _y -= plotinfo.yaxis._offset; if(yPixelSized) { _y = r2p(plotinfo.yaxis, plotinfo.yanchor) - _y; } else { _y = p2r(plotinfo.yaxis, _y); } } else { if(noOffset) _y -= size.t; if(domain) _y = domain.y[1] - _y / size.h; else _y = 1 - _y / size.h; } } newPos[j][k + 1] = _x; newPos[j][k + 2] = _y; } polys[n].push( newPos[j].slice() ); } } return polys; }; function almostEq(a, b) { return Math.abs(a - b) <= 1e-6; } function dist(a, b) { var dx = b[1] - a[1]; var dy = b[2] - a[2]; return Math.sqrt( dx * dx + dy * dy ); } exports.pointsOnRectangle = function(cell) { var len = cell.length; if(len !== 5) return false; for(var j = 1; j < 3; j++) { var e01 = cell[0][j] - cell[1][j]; var e32 = cell[3][j] - cell[2][j]; if(!almostEq(e01, e32)) return false; var e03 = cell[0][j] - cell[3][j]; var e12 = cell[1][j] - cell[2][j]; if(!almostEq(e03, e12)) return false; } // N.B. rotated rectangles are not valid rects since rotation is not supported in shapes for now. if( !almostEq(cell[0][1], cell[1][1]) && !almostEq(cell[0][1], cell[3][1]) ) return false; // reject cases with zero area return !!( dist(cell[0], cell[1]) * dist(cell[0], cell[3]) ); }; exports.pointsOnEllipse = function(cell) { var len = cell.length; if(len !== CIRCLE_SIDES + 1) return false; // opposite diagonals should be the same len = CIRCLE_SIDES; for(var i = 0; i < len; i++) { var k = (len * 2 - i) % len; var k2 = (len / 2 + k) % len; var i2 = (len / 2 + i) % len; if(!almostEq( dist(cell[i], cell[i2]), dist(cell[k], cell[k2]) )) return false; } return true; }; exports.handleEllipse = function(isEllipse, start, end) { if(!isEllipse) return [start, end]; // i.e. case of line var pos = exports.ellipseOver({ x0: start[0], y0: start[1], x1: end[0], y1: end[1] }); var cx = (pos.x1 + pos.x0) / 2; var cy = (pos.y1 + pos.y0) / 2; var rx = (pos.x1 - pos.x0) / 2; var ry = (pos.y1 - pos.y0) / 2; // make a circle when one dimension is zero if(!rx) rx = ry = ry / SQRT2; if(!ry) ry = rx = rx / SQRT2; var cell = []; for(var i = 0; i < CIRCLE_SIDES; i++) { var t = i * 2 * Math.PI / CIRCLE_SIDES; cell.push([ cx + rx * Math.cos(t), cy + ry * Math.sin(t), ]); } return cell; }; exports.ellipseOver = function(pos) { var x0 = pos.x0; var y0 = pos.y0; var x1 = pos.x1; var y1 = pos.y1; var dx = x1 - x0; var dy = y1 - y0; x0 -= dx; y0 -= dy; var cx = (x0 + x1) / 2; var cy = (y0 + y1) / 2; var scale = SQRT2; dx *= scale; dy *= scale; return { x0: cx - dx, y0: cy - dy, x1: cx + dx, y1: cy + dy }; }; exports.fixDatesForPaths = function(polygons, xaxis, yaxis) { var xIsDate = xaxis.type === 'date'; var yIsDate = yaxis.type === 'date'; if(!xIsDate && !yIsDate) return polygons; for(var i = 0; i < polygons.length; i++) { for(var j = 0; j < polygons[i].length; j++) { for(var k = 0; k + 2 < polygons[i][j].length; k += 2) { if(xIsDate) polygons[i][j][k + 1] = polygons[i][j][k + 1].replace(' ', '_'); if(yIsDate) polygons[i][j][k + 2] = polygons[i][j][k + 2].replace(' ', '_'); } } } return polygons; };