NuclearDispersionSystem/ant-design-vue-jeecg/node_modules/@antv/g2/lib/geom/shape/interval.js

719 lines
18 KiB
Java
Raw Normal View History

2023-09-14 14:47:11 +08:00
/**
* @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;