123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770 |
- var curve = require("./curve");
- var vec2 = require("./vector");
- var bbox = require("./bbox");
- var BoundingRect = require("./BoundingRect");
- var _config = require("../config");
- var dpr = _config.devicePixelRatio;
- /**
- * Path 代理,可以在`buildPath`中用于替代`ctx`, 会保存每个path操作的命令到pathCommands属性中
- * 可以用于 isInsidePath 判断以及获取boundingRect
- *
- * @module zrender/core/PathProxy
- * @author Yi Shen (http://www.github.com/pissang)
- */
- // TODO getTotalLength, getPointAtLength
- /* global Float32Array */
- var CMD = {
- M: 1,
- L: 2,
- C: 3,
- Q: 4,
- A: 5,
- Z: 6,
- // Rect
- R: 7
- }; // var CMD_MEM_SIZE = {
- // M: 3,
- // L: 3,
- // C: 7,
- // Q: 5,
- // A: 9,
- // R: 5,
- // Z: 1
- // };
- var min = [];
- var max = [];
- var min2 = [];
- var max2 = [];
- var mathMin = Math.min;
- var mathMax = Math.max;
- var mathCos = Math.cos;
- var mathSin = Math.sin;
- var mathSqrt = Math.sqrt;
- var mathAbs = Math.abs;
- var hasTypedArray = typeof Float32Array !== 'undefined';
- /**
- * @alias module:zrender/core/PathProxy
- * @constructor
- */
- var PathProxy = function (notSaveData) {
- this._saveData = !(notSaveData || false);
- if (this._saveData) {
- /**
- * Path data. Stored as flat array
- * @type {Array.<Object>}
- */
- this.data = [];
- }
- this._ctx = null;
- };
- /**
- * 快速计算Path包围盒(并不是最小包围盒)
- * @return {Object}
- */
- PathProxy.prototype = {
- constructor: PathProxy,
- _xi: 0,
- _yi: 0,
- _x0: 0,
- _y0: 0,
- // Unit x, Unit y. Provide for avoiding drawing that too short line segment
- _ux: 0,
- _uy: 0,
- _len: 0,
- _lineDash: null,
- _dashOffset: 0,
- _dashIdx: 0,
- _dashSum: 0,
- /**
- * @readOnly
- */
- setScale: function (sx, sy, segmentIgnoreThreshold) {
- // Compat. Previously there is no segmentIgnoreThreshold.
- segmentIgnoreThreshold = segmentIgnoreThreshold || 0;
- this._ux = mathAbs(segmentIgnoreThreshold / dpr / sx) || 0;
- this._uy = mathAbs(segmentIgnoreThreshold / dpr / sy) || 0;
- },
- getContext: function () {
- return this._ctx;
- },
- /**
- * @param {CanvasRenderingContext2D} ctx
- * @return {module:zrender/core/PathProxy}
- */
- beginPath: function (ctx) {
- this._ctx = ctx;
- ctx && ctx.beginPath();
- ctx && (this.dpr = ctx.dpr); // Reset
- if (this._saveData) {
- this._len = 0;
- }
- if (this._lineDash) {
- this._lineDash = null;
- this._dashOffset = 0;
- }
- return this;
- },
- /**
- * @param {number} x
- * @param {number} y
- * @return {module:zrender/core/PathProxy}
- */
- moveTo: function (x, y) {
- this.addData(CMD.M, x, y);
- this._ctx && this._ctx.moveTo(x, y); // x0, y0, xi, yi 是记录在 _dashedXXXXTo 方法中使用
- // xi, yi 记录当前点, x0, y0 在 closePath 的时候回到起始点。
- // 有可能在 beginPath 之后直接调用 lineTo,这时候 x0, y0 需要
- // 在 lineTo 方法中记录,这里先不考虑这种情况,dashed line 也只在 IE10- 中不支持
- this._x0 = x;
- this._y0 = y;
- this._xi = x;
- this._yi = y;
- return this;
- },
- /**
- * @param {number} x
- * @param {number} y
- * @return {module:zrender/core/PathProxy}
- */
- lineTo: function (x, y) {
- var exceedUnit = mathAbs(x - this._xi) > this._ux || mathAbs(y - this._yi) > this._uy // Force draw the first segment
- || this._len < 5;
- this.addData(CMD.L, x, y);
- if (this._ctx && exceedUnit) {
- this._needsDash() ? this._dashedLineTo(x, y) : this._ctx.lineTo(x, y);
- }
- if (exceedUnit) {
- this._xi = x;
- this._yi = y;
- }
- return this;
- },
- /**
- * @param {number} x1
- * @param {number} y1
- * @param {number} x2
- * @param {number} y2
- * @param {number} x3
- * @param {number} y3
- * @return {module:zrender/core/PathProxy}
- */
- bezierCurveTo: function (x1, y1, x2, y2, x3, y3) {
- this.addData(CMD.C, x1, y1, x2, y2, x3, y3);
- if (this._ctx) {
- this._needsDash() ? this._dashedBezierTo(x1, y1, x2, y2, x3, y3) : this._ctx.bezierCurveTo(x1, y1, x2, y2, x3, y3);
- }
- this._xi = x3;
- this._yi = y3;
- return this;
- },
- /**
- * @param {number} x1
- * @param {number} y1
- * @param {number} x2
- * @param {number} y2
- * @return {module:zrender/core/PathProxy}
- */
- quadraticCurveTo: function (x1, y1, x2, y2) {
- this.addData(CMD.Q, x1, y1, x2, y2);
- if (this._ctx) {
- this._needsDash() ? this._dashedQuadraticTo(x1, y1, x2, y2) : this._ctx.quadraticCurveTo(x1, y1, x2, y2);
- }
- this._xi = x2;
- this._yi = y2;
- return this;
- },
- /**
- * @param {number} cx
- * @param {number} cy
- * @param {number} r
- * @param {number} startAngle
- * @param {number} endAngle
- * @param {boolean} anticlockwise
- * @return {module:zrender/core/PathProxy}
- */
- arc: function (cx, cy, r, startAngle, endAngle, anticlockwise) {
- this.addData(CMD.A, cx, cy, r, r, startAngle, endAngle - startAngle, 0, anticlockwise ? 0 : 1);
- this._ctx && this._ctx.arc(cx, cy, r, startAngle, endAngle, anticlockwise);
- this._xi = mathCos(endAngle) * r + cx;
- this._yi = mathSin(endAngle) * r + cy;
- return this;
- },
- // TODO
- arcTo: function (x1, y1, x2, y2, radius) {
- if (this._ctx) {
- this._ctx.arcTo(x1, y1, x2, y2, radius);
- }
- return this;
- },
- // TODO
- rect: function (x, y, w, h) {
- this._ctx && this._ctx.rect(x, y, w, h);
- this.addData(CMD.R, x, y, w, h);
- return this;
- },
- /**
- * @return {module:zrender/core/PathProxy}
- */
- closePath: function () {
- this.addData(CMD.Z);
- var ctx = this._ctx;
- var x0 = this._x0;
- var y0 = this._y0;
- if (ctx) {
- this._needsDash() && this._dashedLineTo(x0, y0);
- ctx.closePath();
- }
- this._xi = x0;
- this._yi = y0;
- return this;
- },
- /**
- * Context 从外部传入,因为有可能是 rebuildPath 完之后再 fill。
- * stroke 同样
- * @param {CanvasRenderingContext2D} ctx
- * @return {module:zrender/core/PathProxy}
- */
- fill: function (ctx) {
- ctx && ctx.fill();
- this.toStatic();
- },
- /**
- * @param {CanvasRenderingContext2D} ctx
- * @return {module:zrender/core/PathProxy}
- */
- stroke: function (ctx) {
- ctx && ctx.stroke();
- this.toStatic();
- },
- /**
- * 必须在其它绘制命令前调用
- * Must be invoked before all other path drawing methods
- * @return {module:zrender/core/PathProxy}
- */
- setLineDash: function (lineDash) {
- if (lineDash instanceof Array) {
- this._lineDash = lineDash;
- this._dashIdx = 0;
- var lineDashSum = 0;
- for (var i = 0; i < lineDash.length; i++) {
- lineDashSum += lineDash[i];
- }
- this._dashSum = lineDashSum;
- }
- return this;
- },
- /**
- * 必须在其它绘制命令前调用
- * Must be invoked before all other path drawing methods
- * @return {module:zrender/core/PathProxy}
- */
- setLineDashOffset: function (offset) {
- this._dashOffset = offset;
- return this;
- },
- /**
- *
- * @return {boolean}
- */
- len: function () {
- return this._len;
- },
- /**
- * 直接设置 Path 数据
- */
- setData: function (data) {
- var len = data.length;
- if (!(this.data && this.data.length === len) && hasTypedArray) {
- this.data = new Float32Array(len);
- }
- for (var i = 0; i < len; i++) {
- this.data[i] = data[i];
- }
- this._len = len;
- },
- /**
- * 添加子路径
- * @param {module:zrender/core/PathProxy|Array.<module:zrender/core/PathProxy>} path
- */
- appendPath: function (path) {
- if (!(path instanceof Array)) {
- path = [path];
- }
- var len = path.length;
- var appendSize = 0;
- var offset = this._len;
- for (var i = 0; i < len; i++) {
- appendSize += path[i].len();
- }
- if (hasTypedArray && this.data instanceof Float32Array) {
- this.data = new Float32Array(offset + appendSize);
- }
- for (var i = 0; i < len; i++) {
- var appendPathData = path[i].data;
- for (var k = 0; k < appendPathData.length; k++) {
- this.data[offset++] = appendPathData[k];
- }
- }
- this._len = offset;
- },
- /**
- * 填充 Path 数据。
- * 尽量复用而不申明新的数组。大部分图形重绘的指令数据长度都是不变的。
- */
- addData: function (cmd) {
- if (!this._saveData) {
- return;
- }
- var data = this.data;
- if (this._len + arguments.length > data.length) {
- // 因为之前的数组已经转换成静态的 Float32Array
- // 所以不够用时需要扩展一个新的动态数组
- this._expandData();
- data = this.data;
- }
- for (var i = 0; i < arguments.length; i++) {
- data[this._len++] = arguments[i];
- }
- this._prevCmd = cmd;
- },
- _expandData: function () {
- // Only if data is Float32Array
- if (!(this.data instanceof Array)) {
- var newData = [];
- for (var i = 0; i < this._len; i++) {
- newData[i] = this.data[i];
- }
- this.data = newData;
- }
- },
- /**
- * If needs js implemented dashed line
- * @return {boolean}
- * @private
- */
- _needsDash: function () {
- return this._lineDash;
- },
- _dashedLineTo: function (x1, y1) {
- var dashSum = this._dashSum;
- var offset = this._dashOffset;
- var lineDash = this._lineDash;
- var ctx = this._ctx;
- var x0 = this._xi;
- var y0 = this._yi;
- var dx = x1 - x0;
- var dy = y1 - y0;
- var dist = mathSqrt(dx * dx + dy * dy);
- var x = x0;
- var y = y0;
- var dash;
- var nDash = lineDash.length;
- var idx;
- dx /= dist;
- dy /= dist;
- if (offset < 0) {
- // Convert to positive offset
- offset = dashSum + offset;
- }
- offset %= dashSum;
- x -= offset * dx;
- y -= offset * dy;
- while (dx > 0 && x <= x1 || dx < 0 && x >= x1 || dx === 0 && (dy > 0 && y <= y1 || dy < 0 && y >= y1)) {
- idx = this._dashIdx;
- dash = lineDash[idx];
- x += dx * dash;
- y += dy * dash;
- this._dashIdx = (idx + 1) % nDash; // Skip positive offset
- if (dx > 0 && x < x0 || dx < 0 && x > x0 || dy > 0 && y < y0 || dy < 0 && y > y0) {
- continue;
- }
- ctx[idx % 2 ? 'moveTo' : 'lineTo'](dx >= 0 ? mathMin(x, x1) : mathMax(x, x1), dy >= 0 ? mathMin(y, y1) : mathMax(y, y1));
- } // Offset for next lineTo
- dx = x - x1;
- dy = y - y1;
- this._dashOffset = -mathSqrt(dx * dx + dy * dy);
- },
- // Not accurate dashed line to
- _dashedBezierTo: function (x1, y1, x2, y2, x3, y3) {
- var dashSum = this._dashSum;
- var offset = this._dashOffset;
- var lineDash = this._lineDash;
- var ctx = this._ctx;
- var x0 = this._xi;
- var y0 = this._yi;
- var t;
- var dx;
- var dy;
- var cubicAt = curve.cubicAt;
- var bezierLen = 0;
- var idx = this._dashIdx;
- var nDash = lineDash.length;
- var x;
- var y;
- var tmpLen = 0;
- if (offset < 0) {
- // Convert to positive offset
- offset = dashSum + offset;
- }
- offset %= dashSum; // Bezier approx length
- for (t = 0; t < 1; t += 0.1) {
- dx = cubicAt(x0, x1, x2, x3, t + 0.1) - cubicAt(x0, x1, x2, x3, t);
- dy = cubicAt(y0, y1, y2, y3, t + 0.1) - cubicAt(y0, y1, y2, y3, t);
- bezierLen += mathSqrt(dx * dx + dy * dy);
- } // Find idx after add offset
- for (; idx < nDash; idx++) {
- tmpLen += lineDash[idx];
- if (tmpLen > offset) {
- break;
- }
- }
- t = (tmpLen - offset) / bezierLen;
- while (t <= 1) {
- x = cubicAt(x0, x1, x2, x3, t);
- y = cubicAt(y0, y1, y2, y3, t); // Use line to approximate dashed bezier
- // Bad result if dash is long
- idx % 2 ? ctx.moveTo(x, y) : ctx.lineTo(x, y);
- t += lineDash[idx] / bezierLen;
- idx = (idx + 1) % nDash;
- } // Finish the last segment and calculate the new offset
- idx % 2 !== 0 && ctx.lineTo(x3, y3);
- dx = x3 - x;
- dy = y3 - y;
- this._dashOffset = -mathSqrt(dx * dx + dy * dy);
- },
- _dashedQuadraticTo: function (x1, y1, x2, y2) {
- // Convert quadratic to cubic using degree elevation
- var x3 = x2;
- var y3 = y2;
- x2 = (x2 + 2 * x1) / 3;
- y2 = (y2 + 2 * y1) / 3;
- x1 = (this._xi + 2 * x1) / 3;
- y1 = (this._yi + 2 * y1) / 3;
- this._dashedBezierTo(x1, y1, x2, y2, x3, y3);
- },
- /**
- * 转成静态的 Float32Array 减少堆内存占用
- * Convert dynamic array to static Float32Array
- */
- toStatic: function () {
- var data = this.data;
- if (data instanceof Array) {
- data.length = this._len;
- if (hasTypedArray) {
- this.data = new Float32Array(data);
- }
- }
- },
- /**
- * @return {module:zrender/core/BoundingRect}
- */
- getBoundingRect: function () {
- min[0] = min[1] = min2[0] = min2[1] = Number.MAX_VALUE;
- max[0] = max[1] = max2[0] = max2[1] = -Number.MAX_VALUE;
- var data = this.data;
- var xi = 0;
- var yi = 0;
- var x0 = 0;
- var y0 = 0;
- for (var i = 0; i < data.length;) {
- var cmd = data[i++];
- if (i === 1) {
- // 如果第一个命令是 L, C, Q
- // 则 previous point 同绘制命令的第一个 point
- //
- // 第一个命令为 Arc 的情况下会在后面特殊处理
- xi = data[i];
- yi = data[i + 1];
- x0 = xi;
- y0 = yi;
- }
- switch (cmd) {
- case CMD.M:
- // moveTo 命令重新创建一个新的 subpath, 并且更新新的起点
- // 在 closePath 的时候使用
- x0 = data[i++];
- y0 = data[i++];
- xi = x0;
- yi = y0;
- min2[0] = x0;
- min2[1] = y0;
- max2[0] = x0;
- max2[1] = y0;
- break;
- case CMD.L:
- bbox.fromLine(xi, yi, data[i], data[i + 1], min2, max2);
- xi = data[i++];
- yi = data[i++];
- break;
- case CMD.C:
- bbox.fromCubic(xi, yi, data[i++], data[i++], data[i++], data[i++], data[i], data[i + 1], min2, max2);
- xi = data[i++];
- yi = data[i++];
- break;
- case CMD.Q:
- bbox.fromQuadratic(xi, yi, data[i++], data[i++], data[i], data[i + 1], min2, max2);
- xi = data[i++];
- yi = data[i++];
- break;
- case CMD.A:
- // TODO Arc 判断的开销比较大
- var cx = data[i++];
- var cy = data[i++];
- var rx = data[i++];
- var ry = data[i++];
- var startAngle = data[i++];
- var endAngle = data[i++] + startAngle; // TODO Arc 旋转
- i += 1;
- var anticlockwise = 1 - data[i++];
- if (i === 1) {
- // 直接使用 arc 命令
- // 第一个命令起点还未定义
- x0 = mathCos(startAngle) * rx + cx;
- y0 = mathSin(startAngle) * ry + cy;
- }
- bbox.fromArc(cx, cy, rx, ry, startAngle, endAngle, anticlockwise, min2, max2);
- xi = mathCos(endAngle) * rx + cx;
- yi = mathSin(endAngle) * ry + cy;
- break;
- case CMD.R:
- x0 = xi = data[i++];
- y0 = yi = data[i++];
- var width = data[i++];
- var height = data[i++]; // Use fromLine
- bbox.fromLine(x0, y0, x0 + width, y0 + height, min2, max2);
- break;
- case CMD.Z:
- xi = x0;
- yi = y0;
- break;
- } // Union
- vec2.min(min, min, min2);
- vec2.max(max, max, max2);
- } // No data
- if (i === 0) {
- min[0] = min[1] = max[0] = max[1] = 0;
- }
- return new BoundingRect(min[0], min[1], max[0] - min[0], max[1] - min[1]);
- },
- /**
- * Rebuild path from current data
- * Rebuild path will not consider javascript implemented line dash.
- * @param {CanvasRenderingContext2D} ctx
- */
- rebuildPath: function (ctx) {
- var d = this.data;
- var x0;
- var y0;
- var xi;
- var yi;
- var x;
- var y;
- var ux = this._ux;
- var uy = this._uy;
- var len = this._len;
- for (var i = 0; i < len;) {
- var cmd = d[i++];
- if (i === 1) {
- // 如果第一个命令是 L, C, Q
- // 则 previous point 同绘制命令的第一个 point
- //
- // 第一个命令为 Arc 的情况下会在后面特殊处理
- xi = d[i];
- yi = d[i + 1];
- x0 = xi;
- y0 = yi;
- }
- switch (cmd) {
- case CMD.M:
- x0 = xi = d[i++];
- y0 = yi = d[i++];
- ctx.moveTo(xi, yi);
- break;
- case CMD.L:
- x = d[i++];
- y = d[i++]; // Not draw too small seg between
- if (mathAbs(x - xi) > ux || mathAbs(y - yi) > uy || i === len - 1) {
- ctx.lineTo(x, y);
- xi = x;
- yi = y;
- }
- break;
- case CMD.C:
- ctx.bezierCurveTo(d[i++], d[i++], d[i++], d[i++], d[i++], d[i++]);
- xi = d[i - 2];
- yi = d[i - 1];
- break;
- case CMD.Q:
- ctx.quadraticCurveTo(d[i++], d[i++], d[i++], d[i++]);
- xi = d[i - 2];
- yi = d[i - 1];
- break;
- case CMD.A:
- var cx = d[i++];
- var cy = d[i++];
- var rx = d[i++];
- var ry = d[i++];
- var theta = d[i++];
- var dTheta = d[i++];
- var psi = d[i++];
- var fs = d[i++];
- var r = rx > ry ? rx : ry;
- var scaleX = rx > ry ? 1 : rx / ry;
- var scaleY = rx > ry ? ry / rx : 1;
- var isEllipse = Math.abs(rx - ry) > 1e-3;
- var endAngle = theta + dTheta;
- if (isEllipse) {
- ctx.translate(cx, cy);
- ctx.rotate(psi);
- ctx.scale(scaleX, scaleY);
- ctx.arc(0, 0, r, theta, endAngle, 1 - fs);
- ctx.scale(1 / scaleX, 1 / scaleY);
- ctx.rotate(-psi);
- ctx.translate(-cx, -cy);
- } else {
- ctx.arc(cx, cy, r, theta, endAngle, 1 - fs);
- }
- if (i === 1) {
- // 直接使用 arc 命令
- // 第一个命令起点还未定义
- x0 = mathCos(theta) * rx + cx;
- y0 = mathSin(theta) * ry + cy;
- }
- xi = mathCos(endAngle) * rx + cx;
- yi = mathSin(endAngle) * ry + cy;
- break;
- case CMD.R:
- x0 = xi = d[i];
- y0 = yi = d[i + 1];
- ctx.rect(d[i++], d[i++], d[i++], d[i++]);
- break;
- case CMD.Z:
- ctx.closePath();
- xi = x0;
- yi = y0;
- }
- }
- }
- };
- PathProxy.CMD = CMD;
- var _default = PathProxy;
- module.exports = _default;
|