381 lines
11 KiB
JavaScript
381 lines
11 KiB
JavaScript
|
|
||
|
/*
|
||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||
|
* or more contributor license agreements. See the NOTICE file
|
||
|
* distributed with this work for additional information
|
||
|
* regarding copyright ownership. The ASF licenses this file
|
||
|
* to you under the Apache License, Version 2.0 (the
|
||
|
* "License"); you may not use this file except in compliance
|
||
|
* with the License. You may obtain a copy of the License at
|
||
|
*
|
||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||
|
*
|
||
|
* Unless required by applicable law or agreed to in writing,
|
||
|
* software distributed under the License is distributed on an
|
||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||
|
* KIND, either express or implied. See the License for the
|
||
|
* specific language governing permissions and limitations
|
||
|
* under the License.
|
||
|
*/
|
||
|
|
||
|
var Path = require("zrender/lib/graphic/Path");
|
||
|
|
||
|
var vec2 = require("zrender/lib/core/vector");
|
||
|
|
||
|
var fixClipWithShadow = require("zrender/lib/graphic/helper/fixClipWithShadow");
|
||
|
|
||
|
/*
|
||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||
|
* or more contributor license agreements. See the NOTICE file
|
||
|
* distributed with this work for additional information
|
||
|
* regarding copyright ownership. The ASF licenses this file
|
||
|
* to you under the Apache License, Version 2.0 (the
|
||
|
* "License"); you may not use this file except in compliance
|
||
|
* with the License. You may obtain a copy of the License at
|
||
|
*
|
||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||
|
*
|
||
|
* Unless required by applicable law or agreed to in writing,
|
||
|
* software distributed under the License is distributed on an
|
||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||
|
* KIND, either express or implied. See the License for the
|
||
|
* specific language governing permissions and limitations
|
||
|
* under the License.
|
||
|
*/
|
||
|
// Poly path support NaN point
|
||
|
var vec2Min = vec2.min;
|
||
|
var vec2Max = vec2.max;
|
||
|
var scaleAndAdd = vec2.scaleAndAdd;
|
||
|
var v2Copy = vec2.copy; // Temporary variable
|
||
|
|
||
|
var v = [];
|
||
|
var cp0 = [];
|
||
|
var cp1 = [];
|
||
|
|
||
|
function isPointNull(p) {
|
||
|
return isNaN(p[0]) || isNaN(p[1]);
|
||
|
}
|
||
|
|
||
|
function drawSegment(ctx, points, start, segLen, allLen, dir, smoothMin, smoothMax, smooth, smoothMonotone, connectNulls) {
|
||
|
// if (smoothMonotone == null) {
|
||
|
// if (isMono(points, 'x')) {
|
||
|
// return drawMono(ctx, points, start, segLen, allLen,
|
||
|
// dir, smoothMin, smoothMax, smooth, 'x', connectNulls);
|
||
|
// }
|
||
|
// else if (isMono(points, 'y')) {
|
||
|
// return drawMono(ctx, points, start, segLen, allLen,
|
||
|
// dir, smoothMin, smoothMax, smooth, 'y', connectNulls);
|
||
|
// }
|
||
|
// else {
|
||
|
// return drawNonMono.apply(this, arguments);
|
||
|
// }
|
||
|
// }
|
||
|
// else if (smoothMonotone !== 'none' && isMono(points, smoothMonotone)) {
|
||
|
// return drawMono.apply(this, arguments);
|
||
|
// }
|
||
|
// else {
|
||
|
// return drawNonMono.apply(this, arguments);
|
||
|
// }
|
||
|
if (smoothMonotone === 'none' || !smoothMonotone) {
|
||
|
return drawNonMono.apply(this, arguments);
|
||
|
} else {
|
||
|
return drawMono.apply(this, arguments);
|
||
|
}
|
||
|
}
|
||
|
/**
|
||
|
* Check if points is in monotone.
|
||
|
*
|
||
|
* @param {number[][]} points Array of points which is in [x, y] form
|
||
|
* @param {string} smoothMonotone 'x', 'y', or 'none', stating for which
|
||
|
* dimension that is checking.
|
||
|
* If is 'none', `drawNonMono` should be
|
||
|
* called.
|
||
|
* If is undefined, either being monotone
|
||
|
* in 'x' or 'y' will call `drawMono`.
|
||
|
*/
|
||
|
// function isMono(points, smoothMonotone) {
|
||
|
// if (points.length <= 1) {
|
||
|
// return true;
|
||
|
// }
|
||
|
// var dim = smoothMonotone === 'x' ? 0 : 1;
|
||
|
// var last = points[0][dim];
|
||
|
// var lastDiff = 0;
|
||
|
// for (var i = 1; i < points.length; ++i) {
|
||
|
// var diff = points[i][dim] - last;
|
||
|
// if (!isNaN(diff) && !isNaN(lastDiff)
|
||
|
// && diff !== 0 && lastDiff !== 0
|
||
|
// && ((diff >= 0) !== (lastDiff >= 0))
|
||
|
// ) {
|
||
|
// return false;
|
||
|
// }
|
||
|
// if (!isNaN(diff) && diff !== 0) {
|
||
|
// lastDiff = diff;
|
||
|
// last = points[i][dim];
|
||
|
// }
|
||
|
// }
|
||
|
// return true;
|
||
|
// }
|
||
|
|
||
|
/**
|
||
|
* Draw smoothed line in monotone, in which only vertical or horizontal bezier
|
||
|
* control points will be used. This should be used when points are monotone
|
||
|
* either in x or y dimension.
|
||
|
*/
|
||
|
|
||
|
|
||
|
function drawMono(ctx, points, start, segLen, allLen, dir, smoothMin, smoothMax, smooth, smoothMonotone, connectNulls) {
|
||
|
var prevIdx = 0;
|
||
|
var idx = start;
|
||
|
|
||
|
for (var k = 0; k < segLen; k++) {
|
||
|
var p = points[idx];
|
||
|
|
||
|
if (idx >= allLen || idx < 0) {
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if (isPointNull(p)) {
|
||
|
if (connectNulls) {
|
||
|
idx += dir;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if (idx === start) {
|
||
|
ctx[dir > 0 ? 'moveTo' : 'lineTo'](p[0], p[1]);
|
||
|
} else {
|
||
|
if (smooth > 0) {
|
||
|
var prevP = points[prevIdx];
|
||
|
var dim = smoothMonotone === 'y' ? 1 : 0; // Length of control point to p, either in x or y, but not both
|
||
|
|
||
|
var ctrlLen = (p[dim] - prevP[dim]) * smooth;
|
||
|
v2Copy(cp0, prevP);
|
||
|
cp0[dim] = prevP[dim] + ctrlLen;
|
||
|
v2Copy(cp1, p);
|
||
|
cp1[dim] = p[dim] - ctrlLen;
|
||
|
ctx.bezierCurveTo(cp0[0], cp0[1], cp1[0], cp1[1], p[0], p[1]);
|
||
|
} else {
|
||
|
ctx.lineTo(p[0], p[1]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
prevIdx = idx;
|
||
|
idx += dir;
|
||
|
}
|
||
|
|
||
|
return k;
|
||
|
}
|
||
|
/**
|
||
|
* Draw smoothed line in non-monotone, in may cause undesired curve in extreme
|
||
|
* situations. This should be used when points are non-monotone neither in x or
|
||
|
* y dimension.
|
||
|
*/
|
||
|
|
||
|
|
||
|
function drawNonMono(ctx, points, start, segLen, allLen, dir, smoothMin, smoothMax, smooth, smoothMonotone, connectNulls) {
|
||
|
var prevIdx = 0;
|
||
|
var idx = start;
|
||
|
|
||
|
for (var k = 0; k < segLen; k++) {
|
||
|
var p = points[idx];
|
||
|
|
||
|
if (idx >= allLen || idx < 0) {
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if (isPointNull(p)) {
|
||
|
if (connectNulls) {
|
||
|
idx += dir;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if (idx === start) {
|
||
|
ctx[dir > 0 ? 'moveTo' : 'lineTo'](p[0], p[1]);
|
||
|
v2Copy(cp0, p);
|
||
|
} else {
|
||
|
if (smooth > 0) {
|
||
|
var nextIdx = idx + dir;
|
||
|
var nextP = points[nextIdx];
|
||
|
|
||
|
if (connectNulls) {
|
||
|
// Find next point not null
|
||
|
while (nextP && isPointNull(points[nextIdx])) {
|
||
|
nextIdx += dir;
|
||
|
nextP = points[nextIdx];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var ratioNextSeg = 0.5;
|
||
|
var prevP = points[prevIdx];
|
||
|
var nextP = points[nextIdx]; // Last point
|
||
|
|
||
|
if (!nextP || isPointNull(nextP)) {
|
||
|
v2Copy(cp1, p);
|
||
|
} else {
|
||
|
// If next data is null in not connect case
|
||
|
if (isPointNull(nextP) && !connectNulls) {
|
||
|
nextP = p;
|
||
|
}
|
||
|
|
||
|
vec2.sub(v, nextP, prevP);
|
||
|
var lenPrevSeg;
|
||
|
var lenNextSeg;
|
||
|
|
||
|
if (smoothMonotone === 'x' || smoothMonotone === 'y') {
|
||
|
var dim = smoothMonotone === 'x' ? 0 : 1;
|
||
|
lenPrevSeg = Math.abs(p[dim] - prevP[dim]);
|
||
|
lenNextSeg = Math.abs(p[dim] - nextP[dim]);
|
||
|
} else {
|
||
|
lenPrevSeg = vec2.dist(p, prevP);
|
||
|
lenNextSeg = vec2.dist(p, nextP);
|
||
|
} // Use ratio of seg length
|
||
|
|
||
|
|
||
|
ratioNextSeg = lenNextSeg / (lenNextSeg + lenPrevSeg);
|
||
|
scaleAndAdd(cp1, p, v, -smooth * (1 - ratioNextSeg));
|
||
|
} // Smooth constraint
|
||
|
|
||
|
|
||
|
vec2Min(cp0, cp0, smoothMax);
|
||
|
vec2Max(cp0, cp0, smoothMin);
|
||
|
vec2Min(cp1, cp1, smoothMax);
|
||
|
vec2Max(cp1, cp1, smoothMin);
|
||
|
ctx.bezierCurveTo(cp0[0], cp0[1], cp1[0], cp1[1], p[0], p[1]); // cp0 of next segment
|
||
|
|
||
|
scaleAndAdd(cp0, p, v, smooth * ratioNextSeg);
|
||
|
} else {
|
||
|
ctx.lineTo(p[0], p[1]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
prevIdx = idx;
|
||
|
idx += dir;
|
||
|
}
|
||
|
|
||
|
return k;
|
||
|
}
|
||
|
|
||
|
function getBoundingBox(points, smoothConstraint) {
|
||
|
var ptMin = [Infinity, Infinity];
|
||
|
var ptMax = [-Infinity, -Infinity];
|
||
|
|
||
|
if (smoothConstraint) {
|
||
|
for (var i = 0; i < points.length; i++) {
|
||
|
var pt = points[i];
|
||
|
|
||
|
if (pt[0] < ptMin[0]) {
|
||
|
ptMin[0] = pt[0];
|
||
|
}
|
||
|
|
||
|
if (pt[1] < ptMin[1]) {
|
||
|
ptMin[1] = pt[1];
|
||
|
}
|
||
|
|
||
|
if (pt[0] > ptMax[0]) {
|
||
|
ptMax[0] = pt[0];
|
||
|
}
|
||
|
|
||
|
if (pt[1] > ptMax[1]) {
|
||
|
ptMax[1] = pt[1];
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return {
|
||
|
min: smoothConstraint ? ptMin : ptMax,
|
||
|
max: smoothConstraint ? ptMax : ptMin
|
||
|
};
|
||
|
}
|
||
|
|
||
|
var Polyline = Path.extend({
|
||
|
type: 'ec-polyline',
|
||
|
shape: {
|
||
|
points: [],
|
||
|
smooth: 0,
|
||
|
smoothConstraint: true,
|
||
|
smoothMonotone: null,
|
||
|
connectNulls: false
|
||
|
},
|
||
|
style: {
|
||
|
fill: null,
|
||
|
stroke: '#000'
|
||
|
},
|
||
|
brush: fixClipWithShadow(Path.prototype.brush),
|
||
|
buildPath: function (ctx, shape) {
|
||
|
var points = shape.points;
|
||
|
var i = 0;
|
||
|
var len = points.length;
|
||
|
var result = getBoundingBox(points, shape.smoothConstraint);
|
||
|
|
||
|
if (shape.connectNulls) {
|
||
|
// Must remove first and last null values avoid draw error in polygon
|
||
|
for (; len > 0; len--) {
|
||
|
if (!isPointNull(points[len - 1])) {
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for (; i < len; i++) {
|
||
|
if (!isPointNull(points[i])) {
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
while (i < len) {
|
||
|
i += drawSegment(ctx, points, i, len, len, 1, result.min, result.max, shape.smooth, shape.smoothMonotone, shape.connectNulls) + 1;
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
var Polygon = Path.extend({
|
||
|
type: 'ec-polygon',
|
||
|
shape: {
|
||
|
points: [],
|
||
|
// Offset between stacked base points and points
|
||
|
stackedOnPoints: [],
|
||
|
smooth: 0,
|
||
|
stackedOnSmooth: 0,
|
||
|
smoothConstraint: true,
|
||
|
smoothMonotone: null,
|
||
|
connectNulls: false
|
||
|
},
|
||
|
brush: fixClipWithShadow(Path.prototype.brush),
|
||
|
buildPath: function (ctx, shape) {
|
||
|
var points = shape.points;
|
||
|
var stackedOnPoints = shape.stackedOnPoints;
|
||
|
var i = 0;
|
||
|
var len = points.length;
|
||
|
var smoothMonotone = shape.smoothMonotone;
|
||
|
var bbox = getBoundingBox(points, shape.smoothConstraint);
|
||
|
var stackedOnBBox = getBoundingBox(stackedOnPoints, shape.smoothConstraint);
|
||
|
|
||
|
if (shape.connectNulls) {
|
||
|
// Must remove first and last null values avoid draw error in polygon
|
||
|
for (; len > 0; len--) {
|
||
|
if (!isPointNull(points[len - 1])) {
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for (; i < len; i++) {
|
||
|
if (!isPointNull(points[i])) {
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
while (i < len) {
|
||
|
var k = drawSegment(ctx, points, i, len, len, 1, bbox.min, bbox.max, shape.smooth, smoothMonotone, shape.connectNulls);
|
||
|
drawSegment(ctx, stackedOnPoints, i + k - 1, k, len, -1, stackedOnBBox.min, stackedOnBBox.max, shape.stackedOnSmooth, smoothMonotone, shape.connectNulls);
|
||
|
i += k + 1;
|
||
|
ctx.closePath();
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
exports.Polyline = Polyline;
|
||
|
exports.Polygon = Polygon;
|