NuclearDispersionSystem/ant-design-vue-jeecg/node_modules/@antv/g2/lib/geom/shape/interval.js
2023-09-14 14:47:11 +08:00

719 lines
18 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* @fileOverview interval shapes
* @author dxq613@gmail.com
* @author sima.zhang1990@gmail.com
* @author huangtonger@aliyun.com
*/
var Util = require('../../util');
var Shape = require('./shape');
var PathUtil = require('../util/path');
var ShapeUtil = require('../util/shape');
var Global = require('../../global');
var G = require('../../renderer');
var GPathUtil = Util.PathUtil; // 获取柱状图的几个点
function getRectPoints(cfg, isPyramid) {
var x = cfg.x;
var y = cfg.y;
var y0 = cfg.y0; // 0 点的位置
var width = cfg.size; // 有3种情况
// 1. yx都不是数组
// 2. y是数组x不是
// 3. x是数组y不是
var ymin = y0;
var ymax = y;
if (Util.isArray(y)) {
ymax = y[1];
ymin = y[0];
}
var xmin;
var xmax;
if (Util.isArray(x)) {
xmin = x[0];
xmax = x[1];
} else {
xmin = x - width / 2;
xmax = x + width / 2;
}
var points = [];
points.push({
x: xmin,
y: ymin
}, {
x: xmin,
y: ymax
});
if (isPyramid) {
points.push({
x: xmax,
y: (ymax + ymin) / 2
});
} else {
points.push({
x: xmax,
y: ymax
}, {
x: xmax,
y: ymin
});
}
return points;
}
function getRectPath(points) {
var path = [];
for (var i = 0; i < points.length; i++) {
var point = points[i];
if (point) {
var action = i === 0 ? 'M' : 'L';
path.push([action, point.x, point.y]);
}
}
var first = points[0];
path.push(['L', first.x, first.y]);
path.push(['z']);
return path;
}
function getLinePoints(cfg) {
var x = cfg.x;
var y = cfg.y;
var y0 = cfg.y0; // 0 点的位置
var points = [];
if (Util.isArray(y)) {
Util.each(y, function (yItem, idx) {
points.push({
x: Util.isArray(x) ? x[idx] : x,
y: yItem
});
});
} else {
points.push({
x: x,
y: y
}, {
x: x,
y: y0
});
}
return points;
}
function getTickPoints(cfg) {
var x = cfg.x;
var y = Util.isArray(cfg.y) ? cfg.y[1] : cfg.y;
var y0 = Util.isArray(cfg.y) ? cfg.y[0] : cfg.y0;
var barWidth = cfg.size;
var points = [];
points.push({
x: x - barWidth / 2,
y: y
}, {
x: x + barWidth / 2,
y: y
}, {
x: x,
y: y
}, {
x: x,
y: y0
}, {
x: x - barWidth / 2,
y: y0
}, {
x: x + barWidth / 2,
y: y0
});
return points;
}
function getTickPath(points) {
var path = [];
path.push(['M', points[0].x, points[0].y], ['L', points[1].x, points[1].y], ['M', points[2].x, points[2].y], ['L', points[3].x, points[3].y], ['M', points[4].x, points[4].y], ['L', points[5].x, points[5].y]);
return path;
}
function getFillAttrs(cfg) {
var defaultAttrs = Global.shape.interval;
var attrs = Util.mix({}, defaultAttrs, cfg.style);
ShapeUtil.addFillAttrs(attrs, cfg);
if (cfg.color) {
attrs.stroke = attrs.stroke || cfg.color;
}
return attrs;
}
function getLineAttrs(cfg) {
var defaultAttrs = Global.shape.hollowInterval;
var attrs = Util.mix({}, defaultAttrs, cfg.style);
ShapeUtil.addStrokeAttrs(attrs, cfg);
return attrs;
}
function getFunnelPath(cfg, isFunnel) {
var path = [];
var points = cfg.points;
var nextPoints = cfg.nextPoints;
if (!Util.isNil(nextPoints)) {
path.push(['M', points[0].x, points[0].y], ['L', points[1].x, points[1].y], ['L', nextPoints[1].x, nextPoints[1].y], ['L', nextPoints[0].x, nextPoints[0].y], ['Z']);
} else if (isFunnel) {
path.push(['M', points[0].x, points[0].y], ['L', points[1].x, points[1].y], ['L', points[2].x, points[2].y], ['L', points[3].x, points[3].y], ['Z']);
} else {
path.push(['M', points[0].x, points[0].y], ['L', points[1].x, points[1].y], ['L', points[2].x, points[2].y], ['L', points[2].x, points[2].y], ['Z']);
}
return path;
}
function getThetaCfg(point, coord) {
var r = coord.getRadius();
var inner = coord.innerRadius;
var startAngle;
var endAngle;
var ir = r * inner;
var startPoint;
var endPoint;
if (!Util.isArray(point.x) && Util.isArray(point.y)) {
point.x = [point.x, point.x]; // 如果x是一个值y是数组将x转成数组
}
if (Util.isArray(point.x)) {
startPoint = {
x: point.x[0],
y: point.y[0]
};
endPoint = {
x: point.x[1],
y: point.y[1]
};
startAngle = PathUtil.getPointAngle(coord, startPoint);
endAngle = PathUtil.getPointAngle(coord, endPoint);
if (endAngle <= startAngle) {
// 考虑占比百分百的情形
endAngle = endAngle + Math.PI * 2;
}
} else {
endPoint = point;
startAngle = coord.startAngle;
endAngle = PathUtil.getPointAngle(coord, endPoint);
}
return {
r: r,
ir: ir,
startAngle: startAngle,
endAngle: endAngle
};
} // 获取选中时的样式,当前仅支持饼图
function _getSelectedCfg(type, cfg) {
var geom = cfg.geom;
var coord = geom.get('coord');
var point = cfg.point;
var r = 7.5;
var selectedCfg;
if (coord && coord.type === 'theta') {
var thetaCfg = getThetaCfg(point, coord);
var middleAngle = (thetaCfg.endAngle - thetaCfg.startAngle) / 2 + thetaCfg.startAngle;
var x = r * Math.cos(middleAngle);
var y = r * Math.sin(middleAngle);
selectedCfg = {
transform: [['t', x, y]]
};
}
return Util.mix({}, selectedCfg);
}
var Interval = Shape.registerFactory('interval', {
defaultShapeType: 'rect',
getActiveCfg: function getActiveCfg(type, cfg) {
if (!type || Util.inArray(['rect', 'funnel', 'pyramid'], type)) {
// 透明度降低 0.15
var fillOpacity = cfg.fillOpacity || cfg.opacity || 1;
return {
fillOpacity: fillOpacity - 0.15
};
}
var lineWidth = cfg.lineWidth || 0;
return {
lineWidth: lineWidth + 1
};
},
getDefaultPoints: function getDefaultPoints(pointInfo) {
return getRectPoints(pointInfo);
},
getSelectedCfg: function getSelectedCfg(type, cfg) {
return _getSelectedCfg(type, cfg);
}
}); // 默认柱状图
Shape.registerShape('interval', 'rect', {
draw: function draw(cfg, container) {
var attrs = getFillAttrs(cfg);
var path = getRectPath(cfg.points);
path = this.parsePath(path);
return container.addShape('path', {
attrs: Util.mix(attrs, {
path: path
})
});
},
getMarkerCfg: function getMarkerCfg(cfg) {
var rectCfg = getFillAttrs(cfg);
var isInCircle = cfg.isInCircle;
return Util.mix({
symbol: isInCircle ? 'circle' : 'square',
radius: isInCircle ? 4.5 : 4
}, rectCfg);
}
}); // 空心柱状图
Shape.registerShape('interval', 'hollowRect', {
draw: function draw(cfg, container) {
var attrs = getLineAttrs(cfg);
var path = getRectPath(cfg.points);
path = this.parsePath(path);
return container.addShape('path', {
attrs: Util.mix(attrs, {
path: path
})
});
},
getMarkerCfg: function getMarkerCfg(cfg) {
var rectCfg = getLineAttrs(cfg);
var isInCircle = cfg.isInCircle;
return Util.mix({
symbol: isInCircle ? 'circle' : 'square',
radius: isInCircle ? 4.5 : 4
}, rectCfg);
}
}); // 线形柱状图
Shape.registerShape('interval', 'line', {
getPoints: function getPoints(pointInfo) {
return getLinePoints(pointInfo);
},
draw: function draw(cfg, container) {
var attrs = getLineAttrs(cfg);
attrs.lineWidth = cfg.size || 1; // size 就是线的宽度
var path = getRectPath(cfg.points);
path = this.parsePath(path);
return container.addShape('path', {
attrs: Util.mix(attrs, {
path: path
})
});
},
getMarkerCfg: function getMarkerCfg(cfg) {
var lineCfg = getLineAttrs(cfg);
return Util.mix({
symbol: 'line',
radius: 5
}, lineCfg);
}
}); // 钉子形的柱状图
Shape.registerShape('interval', 'tick', {
getPoints: function getPoints(pointInfo) {
return getTickPoints(pointInfo);
},
draw: function draw(cfg, container) {
var attrs = getLineAttrs(cfg); // @2018-12-25 by blue.lb 经过测试发现size代表的是宽度而style中的lineWidth才是设置线宽放在interval暂时先特殊处理
if (!attrs.lineWidth) {
attrs.lineWidth = 2;
}
var path = getTickPath(cfg.points);
path = this.parsePath(path);
return container.addShape('path', {
attrs: Util.mix(attrs, {
path: path
})
});
},
getMarkerCfg: function getMarkerCfg(cfg) {
var lineCfg = getLineAttrs(cfg);
return Util.mix({
symbol: 'tick',
radius: 5
}, lineCfg);
}
}); // 漏斗图
Shape.registerShape('interval', 'funnel', {
getPoints: function getPoints(pointInfo) {
pointInfo.size = pointInfo.size * 2; // 漏斗图的 size 是柱状图的两倍
return getRectPoints(pointInfo);
},
draw: function draw(cfg, container) {
var attrs = getFillAttrs(cfg);
var path = getFunnelPath(cfg, true);
path = this.parsePath(path);
return container.addShape('path', {
attrs: Util.mix(attrs, {
path: path
})
});
},
getMarkerCfg: function getMarkerCfg(cfg) {
var funnelCfg = getFillAttrs(cfg);
return Util.mix({
symbol: 'square',
radius: 4
}, funnelCfg);
}
}); // 金字塔图
Shape.registerShape('interval', 'pyramid', {
getPoints: function getPoints(pointInfo) {
pointInfo.size = pointInfo.size * 2; // 漏斗图的 size 是柱状图的两倍
return getRectPoints(pointInfo, true);
},
draw: function draw(cfg, container) {
var attrs = getFillAttrs(cfg);
var path = getFunnelPath(cfg, false);
path = this.parsePath(path);
return container.addShape('path', {
attrs: Util.mix(attrs, {
path: path
})
});
},
getMarkerCfg: function getMarkerCfg(cfg) {
var funnelCfg = getFillAttrs(cfg);
return Util.mix({
symbol: 'square',
radius: 4
}, funnelCfg);
}
}); // 水波图
/**
* 用贝塞尔曲线模拟正弦波
* Using Bezier curves to fit sine wave.
* There is 4 control points for each curve of wave,
* which is at 1/4 wave length of the sine wave.
*
* The control points for a wave from (a) to (d) are a-b-c-d:
* c *----* d
* b *
* |
* ... a * ..................
*
* whose positions are a: (0, 0), b: (0.5, 0.5), c: (1, 1), d: (PI / 2, 1)
*
* @param {number} x x position of the left-most point (a)
* @param {number} stage 0-3, stating which part of the wave it is
* @param {number} waveLength wave length of the sine wave
* @param {number} amplitude wave amplitude
* @return {Array} 正弦片段曲线
*/
function getWaterWavePositions(x, stage, waveLength, amplitude) {
if (stage === 0) {
return [[x + 1 / 2 * waveLength / Math.PI / 2, amplitude / 2], [x + 1 / 2 * waveLength / Math.PI, amplitude], [x + waveLength / 4, amplitude]];
} else if (stage === 1) {
return [[x + 1 / 2 * waveLength / Math.PI / 2 * (Math.PI - 2), amplitude], [x + 1 / 2 * waveLength / Math.PI / 2 * (Math.PI - 1), amplitude / 2], [x + waveLength / 4, 0]];
} else if (stage === 2) {
return [[x + 1 / 2 * waveLength / Math.PI / 2, -amplitude / 2], [x + 1 / 2 * waveLength / Math.PI, -amplitude], [x + waveLength / 4, -amplitude]];
}
return [[x + 1 / 2 * waveLength / Math.PI / 2 * (Math.PI - 2), -amplitude], [x + 1 / 2 * waveLength / Math.PI / 2 * (Math.PI - 1), -amplitude / 2], [x + waveLength / 4, 0]];
}
/**
* 获取水波路径
* @param {number} radius 半径
* @param {number} waterLevel 水位
* @param {number} waveLength 波长
* @param {number} phase 相位
* @param {number} amplitude 震幅
* @param {number} cx 圆心x
* @param {number} cy 圆心y
* @return {Array} path 路径
* @reference http://gitlab.alipay-inc.com/datavis/g6/blob/1.2.0/src/graph/utils/path.js#L135
*/
function getWaterWavePath(radius, waterLevel, waveLength, phase, amplitude, cx, cy) {
var curves = Math.ceil(2 * radius / waveLength * 4) * 2;
var path = []; // map phase to [-Math.PI * 2, 0]
while (phase < -Math.PI * 2) {
phase += Math.PI * 2;
}
while (phase > 0) {
phase -= Math.PI * 2;
}
phase = phase / Math.PI / 2 * waveLength;
var left = cx - radius + phase - radius * 2;
/**
* top-left corner as start point
*
* draws this point
* |
* \|/
* ~~~~~~~~
* | |
* +------+
*/
path.push(['M', left, waterLevel]);
/**
* top wave
*
* ~~~~~~~~ <- draws this sine wave
* | |
* +------+
*/
var waveRight = 0;
for (var c = 0; c < curves; ++c) {
var stage = c % 4;
var pos = getWaterWavePositions(c * waveLength / 4, stage, waveLength, amplitude);
path.push(['C', pos[0][0] + left, -pos[0][1] + waterLevel, pos[1][0] + left, -pos[1][1] + waterLevel, pos[2][0] + left, -pos[2][1] + waterLevel]);
if (c === curves - 1) {
waveRight = pos[2][0];
}
}
/**
* top-right corner
*
* ~~~~~~~~
* 3. draws this line -> | | <- 1. draws this line
* +------+
* ^
* |
* 2. draws this line
*/
path.push(['L', waveRight + left, cy + radius]);
path.push(['L', left, cy + radius]);
path.push(['L', left, waterLevel]);
return path;
}
/**
* 添加水波
* @param {number} x 中心x
* @param {number} y 中心y
* @param {number} level 水位等级 01
* @param {number} waveCount 水波数
* @param {number} colors 色值
* @param {number} group 图组
* @param {number} clip 用于剪切的图形
* @param {number} radius 绘制图形的高度
*/
function addWaterWave(x, y, level, waveCount, colors, group, clip, radius) {
var bbox = clip.getBBox();
var width = bbox.maxX - bbox.minX;
var height = bbox.maxY - bbox.minY;
var duration = 5000;
var delayDiff = 300;
for (var i = 0; i < waveCount; i++) {
var wave = group.addShape('path', {
attrs: {
path: getWaterWavePath(radius, bbox.minY + height * level, width / 4, 0, width / 64, x, y),
fill: colors[i],
clip: clip
}
}); // FIXME wave animation error in svg
if (Global.renderer === 'canvas') {
wave.animate({
transform: [['t', width / 2, 0]],
repeat: true
}, duration - i * delayDiff);
}
}
}
Shape.registerShape('interval', 'liquid-fill-gauge', {
draw: function draw(cfg, container) {
var self = this;
var cy = 0.5;
var sumX = 0;
var minX = Infinity;
Util.each(cfg.points, function (p) {
if (p.x < minX) {
minX = p.x;
}
sumX += p.x;
});
var cx = sumX / cfg.points.length;
var cp = self.parsePoint({
x: cx,
y: cy
});
var minP = self.parsePoint({
x: minX,
y: 0.5
});
var xWidth = cp.x - minP.x;
var radius = Math.min(xWidth, minP.y);
var attrs = getFillAttrs(cfg);
var clipCircle = new G.Circle({
attrs: {
x: cp.x,
y: cp.y,
r: radius
}
});
addWaterWave(cp.x, cp.y, cfg.y / (2 * cp.y), 1, [attrs.fill], container, clipCircle, radius * 4);
return container.addShape('circle', {
attrs: Util.mix(getLineAttrs(cfg), {
x: cp.x,
y: cp.y,
r: radius + radius / 8
})
});
}
});
var pathMetaCache = {};
Shape.registerShape('interval', 'liquid-fill-path', {
draw: function draw(cfg, container) {
var self = this;
var attrs = Util.mix({}, getFillAttrs(cfg));
var path = cfg.shape[1];
var cy = 0.5;
var sumX = 0;
var minX = Infinity;
Util.each(cfg.points, function (p) {
if (p.x < minX) {
minX = p.x;
}
sumX += p.x;
});
var cx = sumX / cfg.points.length;
var cp = self.parsePoint({
x: cx,
y: cy
});
var minP = self.parsePoint({
x: minX,
y: 0.5
});
var xWidth = cp.x - minP.x;
var radius = Math.min(xWidth, minP.y);
var pathMeta;
if (pathMetaCache[path]) {
pathMeta = pathMetaCache[path];
} else {
var segments = GPathUtil.parsePathString(path);
pathMetaCache[path] = pathMeta = {
segments: segments
};
}
var transform = [];
if (attrs.rotate) {
transform.push(['r', attrs.rotate / 180 * Math.PI]);
delete attrs.rotate;
}
var shape = container.addShape('path', {
attrs: Util.mix(attrs, {
fillOpacity: 0,
path: pathMeta.segments
})
});
var bbox = Util.cloneDeep(shape.getBBox());
var rangeX = bbox.maxX - bbox.minX;
var rangeY = bbox.maxY - bbox.minY;
var range = Math.max(rangeX, rangeY);
var scale = radius * 2 / range;
shape.transform(transform.concat([['s', scale, scale]]));
var dw = scale * rangeX / 2; // (bbox.maxX - bbox.minX) / 2;
var dh = scale * rangeY / 2; // (bbox.maxY - bbox.minY) / 2;
shape.transform([['t', cp.x - dw, cp.y - dh]]);
addWaterWave(cp.x, cp.y, cfg.y / (2 * cp.y), 1, [attrs.fill], container, shape, minP.y * 4);
var keyShape = container.addShape('path', {
attrs: Util.mix(getLineAttrs(cfg), {
path: pathMeta.segments
})
});
keyShape.transform(transform.concat([['s', scale, scale], ['t', cp.x - dw, cp.y - dh]]));
return keyShape;
}
});
Shape.registerShape('interval', 'top-line', {
draw: function draw(cfg, container) {
var attrs = getFillAttrs(cfg);
var style = cfg.style || {};
var linePath = [['M', cfg.points[1].x, cfg.points[1].y], ['L', cfg.points[2].x, cfg.points[2].y]];
var lineAttrs = {
stroke: style.stroke || 'white',
lineWidth: style.lineWidth || 1,
path: this.parsePath(linePath)
};
var path = getRectPath(cfg.points);
path = this.parsePath(path);
delete attrs.stroke; // 不在柱子上绘制线
var rectShape = container.addShape('path', {
attrs: Util.mix(attrs, {
zIndex: 0,
path: path
})
});
container.addShape('path', {
zIndex: 1,
attrs: lineAttrs
});
return rectShape;
},
getMarkerCfg: function getMarkerCfg(cfg) {
var rectCfg = getFillAttrs(cfg);
var isInCircle = cfg.isInCircle;
return Util.mix({
symbol: isInCircle ? 'circle' : 'square',
radius: isInCircle ? 4.5 : 4
}, rectCfg);
}
});
module.exports = Interval;