chartx
Version:
Data Visualization Chart Library
719 lines (707 loc) • 22 kB
JavaScript
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
var _possibleConstructorReturn2 = _interopRequireDefault(require("@babel/runtime/helpers/possibleConstructorReturn"));
var _getPrototypeOf2 = _interopRequireDefault(require("@babel/runtime/helpers/getPrototypeOf"));
var _inherits2 = _interopRequireDefault(require("@babel/runtime/helpers/inherits"));
var _canvax = _interopRequireDefault(require("canvax"));
var _index = _interopRequireDefault(require("../index"));
var _layout = require("../../../layout/venn/layout");
var _circleintersection = require("../../../layout/venn/circleintersection");
var _fmin = require("fmin");
var _tools = require("../../../utils/tools");
function _callSuper(t, o, e) { return o = (0, _getPrototypeOf2.default)(o), (0, _possibleConstructorReturn2.default)(t, _isNativeReflectConstruct() ? Reflect.construct(o, e || [], (0, _getPrototypeOf2.default)(t).constructor) : o.apply(t, e)); }
function _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function _isNativeReflectConstruct() { return !!t; })(); }
var _ = _canvax.default._,
event = _canvax.default.event;
var Text = _canvax.default.Display.Text;
var Path = _canvax.default.Shapes.Path;
var Circle = _canvax.default.Shapes.Circle;
var VennGraphs = /*#__PURE__*/function (_GraphsBase) {
function VennGraphs(opt, app) {
var _this;
(0, _classCallCheck2.default)(this, VennGraphs);
_this = _callSuper(this, VennGraphs, [opt, app]);
_this.type = "venn";
_this.vennData = null;
_.extend(true, _this, (0, _tools.getDefaultProps)(VennGraphs.defaultProps()), opt);
//_trimGraphs后,计算出来本次data的一些属性
_this._dataCircleLen = 0;
_this._dataLabelLen = 0;
_this._dataPathLen = 0;
_this.init();
return _this;
}
(0, _inherits2.default)(VennGraphs, _GraphsBase);
return (0, _createClass2.default)(VennGraphs, [{
key: "init",
value: function init() {
this.venn_circles = new _canvax.default.Display.Sprite({
id: "venn_circles"
});
this.sprite.addChild(this.venn_circles);
this.venn_paths = new _canvax.default.Display.Sprite({
id: "venn_paths"
});
this.sprite.addChild(this.venn_paths);
this.venn_labels = new _canvax.default.Display.Sprite({
id: "venn_labels"
});
this.sprite.addChild(this.venn_labels);
}
}, {
key: "draw",
value: function draw(opt) {
!opt && (opt = {});
_.extend(true, this, opt);
this.data = this._trimGraphs();
this._widget();
this.sprite.context.x = this.app.padding.left;
this.sprite.context.y = this.app.padding.top;
this.fire("complete");
}
}, {
key: "resetData",
value: function resetData(dataFrame) {
this.dataFrame = dataFrame;
this.data = this._trimGraphs();
this._widget();
}
}, {
key: "_trimGraphs",
value: function _trimGraphs() {
var me = this;
var data = me._vennData();
var layoutFunction = _layout.venn;
var loss = _layout.lossFunction;
var orientation = Math.PI / 2;
var orientationOrder = null;
var normalize = true;
var circles = {};
var textCentres = {};
this._dataCircleLen = 0;
this._dataLabelLen = 0;
this._dataPathLen = 0;
if (data.length > 0) {
var solution = layoutFunction(data, {
lossFunction: loss
});
if (normalize) {
solution = (0, _layout.normalizeSolution)(solution, orientation, orientationOrder);
}
;
//第4个参数本是padding,但是这里的width,height已经是减去过padding的
//所以就传0
circles = (0, _layout.scaleSolution)(solution, this.width, this.height, 0);
textCentres = computeTextCentres(circles, data);
}
;
var circleInd = 0;
var pathInd = 0;
_.each(data, function (d) {
if (d.label) {
if (d.sets.length > 1 && !me.label.showInter) {
//不显示path的文本
// ...
} else {
d.labelPosition = textCentres[d.nodeId];
me._dataLabelLen++;
}
}
;
if (d.sets.length > 1) {
var _path = intersectionAreaPath(d.sets.map(function (set) {
return circles[set];
}));
d.shape = {
type: 'path',
path: _path,
pathInd: pathInd++
};
me._dataPathLen++;
} else if (d.sets.length == 1) {
d.shape = _.extend({
type: 'circle',
circleInd: circleInd++
}, circles[d.nodeId]);
me._dataCircleLen++;
}
});
return data;
}
}, {
key: "_vennData",
value: function _vennData() {
var data = [];
var me = this;
for (var i = 0, l = this.dataFrame.length; i < l; i++) {
var rowData = me.dataFrame.getRowDataAt(i);
var obj = {
type: "venn",
field: me.field,
iNode: i,
nodeId: null,
rowData: rowData,
sets: null,
//size和value是同一个值,size是 vennLayout 需要用到的属性
//value是 chartx中和其他图表的值属性保持统一,比如tips中就会读取value
size: null,
value: null,
//这两个在绘制的时候赋值
fillStyle: null,
strokeStyle: null,
label: null,
labelPosition: null
};
for (var p in rowData) {
var val = rowData[p];
if (p == me.keyField) {
if (_.isString(val)) {
val = val.split(/[,|]/);
}
;
obj.sets = val;
obj.nodeId = val.join();
if (!me.label.field) {
//如果没有设置label的field
//那么就默认获取keyField
obj.label = val.join();
}
;
}
;
if (p == me.field) {
obj.size = val;
obj.value = val;
}
;
if (p == me.label.field) {
obj.label = val;
}
;
}
data.push(obj);
}
;
return data;
}
}, {
key: "_getStyle",
value: function _getStyle(style, ind, nodeData, defColor) {
var color;
if (_.isString(style)) {
color = style;
}
if (_.isFunction(style)) {
color = style(nodeData);
}
if (!color && ind != undefined) {
color = this.app.getTheme(ind);
}
if (!color && defColor != undefined) {
color = defColor;
}
return color;
}
}, {
key: "_widget",
value: function _widget() {
var me = this;
//那么有多余的元素要去除掉 begin
if (me.venn_circles.children.length > me._dataCircleLen) {
for (var i = me._dataCircleLen; i < me.venn_circles.children.length; i++) {
me.venn_circles.getChildAt(i--).destroy();
}
}
;
if (me.venn_paths.children.length > me._dataPathLen) {
for (var _i = me._dataPathLen; _i < me.venn_paths.children.length; _i++) {
me.venn_paths.getChildAt(_i--).destroy();
}
}
;
if (me.venn_labels.children.length > me._dataLabelLen) {
for (var _i2 = me._dataLabelLen; _i2 < me.venn_labels.children.length; _i2++) {
me.venn_labels.getChildAt(_i2--).destroy();
}
}
;
//那么有多余的元素要去除掉 end
var circleInd = 0;
var pathInd = 0;
var labelInd = 0;
_.each(this.data, function (nodeData) {
var shape = nodeData.shape;
var _shape;
var isNewShape = true;
if (shape) {
var context;
if (shape.type == 'circle') {
var fillStyle = me._getStyle(me.node.fillStyle, shape.circleInd, nodeData);
var strokeStyle = me._getStyle(me.node.strokeStyle, shape.circleInd, nodeData);
nodeData.fillStyle = fillStyle;
nodeData.strokeStyle = strokeStyle;
context = {
x: shape.x,
y: shape.y,
r: shape.radius,
fillStyle: fillStyle,
fillAlpha: me.node.fillAlpha,
lineWidth: me.node.lineWidth,
strokeStyle: strokeStyle,
strokeAlpha: me.node.strokeAlpha
};
_shape = me.venn_circles.getChildAt(circleInd++);
if (!_shape) {
_shape = new Circle({
pointChkPriority: false,
hoverClone: false,
context: context
});
me.venn_circles.addChild(_shape);
} else {
isNewShape = false;
_shape.animate(context);
}
}
;
if (nodeData.shape.type == 'path') {
context = {
path: shape.path,
fillStyle: "#ffffff",
fillAlpha: 0,
//me.node.fillAlpha,
lineWidth: me.node.lineWidth,
strokeStyle: "#ffffff",
strokeAlpha: 0 //me.node.strokeAlpha
};
_shape = me.venn_paths.getChildAt(pathInd++);
if (!_shape) {
_shape = new Path({
pointChkPriority: false,
context: context
});
me.venn_paths.addChild(_shape);
} else {
isNewShape = false;
_shape.context.path = shape.path;
//_shape.animate( context )
}
}
;
_shape.nodeData = nodeData;
nodeData._node = _shape;
me.node.focus.enabled && _shape.hover(function () {
me.focusAt(this.nodeData.iNode);
}, function () {
!this.nodeData.selected && me.unfocusAt(this.nodeData.iNode);
});
//新创建的元素才需要绑定事件,因为复用的原件已经绑定过事件了
if (isNewShape) {
_shape.on(event.types.get(), function (e) {
e.eventInfo = {
trigger: 'this.node',
//me.node,
title: null,
nodes: [this.nodeData]
};
//fire到root上面去的是为了让root去处理tips
me.app.fire(e.type, e);
});
}
;
}
if (nodeData.label && me.label.enabled) {
var fontColor = me._getStyle(me.label.fontColor, shape.circleInd, nodeData, "#999");
var fontSize = me.label.fontSize;
if (nodeData.sets.length > 1) {
if (!me.label.showInter) {
fontSize = 0;
} else {
fontSize -= 2;
}
}
;
if (fontSize) {
var _textContext = {
x: nodeData.labelPosition.x,
y: nodeData.labelPosition.y,
fontSize: fontSize,
//fontFamily: me.label.fontFamily,
textBaseline: "middle",
textAlign: "center",
fontWeight: me.label.fontWeight,
fillStyle: fontColor
};
var _txt = me.venn_labels.getChildAt(labelInd++);
if (!_txt) {
_txt = new Text(nodeData.label, {
context: _textContext
});
me.venn_labels.addChild(_txt);
} else {
_txt.resetText(nodeData.label);
_txt.animate(_textContext);
}
}
}
});
}
}, {
key: "focusAt",
value: function focusAt(ind) {
var nodeData = this.data[ind];
if (!this.node.focus.enabled || nodeData.focused) return;
var nctx = nodeData._node.context;
//nctx.strokeAlpha += 0.5;
if (nodeData.sets.length > 1) {
//path
nctx.strokeAlpha = 1;
} else {
//circle
nctx.strokeAlpha = this.node.focus.strokeAlpha;
}
nodeData.focused = true;
}
}, {
key: "unfocusAt",
value: function unfocusAt(ind) {
var nodeData = this.data[ind];
if (!this.node.focus.enabled || !nodeData.focused) return;
var nctx = nodeData._node.context;
//nctx.strokeAlpha = 0.5;
nctx.strokeAlpha = this.node.strokeAlpha;
nodeData.focused = false;
}
}, {
key: "selectAt",
value: function selectAt(ind) {
var nodeData = this.data[ind];
if (!this.node.select.enabled || nodeData.selected) return;
var nctx = nodeData._node.context;
nctx.lineWidth = this.node.select.lineWidth;
nctx.strokeAlpha = this.node.select.strokeAlpha;
nctx.strokeStyle = this.node.select.strokeStyle;
nodeData.selected = true;
}
}, {
key: "unselectAt",
value: function unselectAt(ind) {
var nodeData = this.data[ind];
if (!this.node.select.enabled || !nodeData.selected) return;
var nctx = nodeData._node.context;
nctx.strokeStyle = this.node.strokeStyle;
nodeData.selected = false;
}
}], [{
key: "defaultProps",
value: function defaultProps() {
return {
field: {
detail: 'value字段',
default: 'value'
},
keyField: {
detail: 'key字段',
default: 'name'
},
node: {
detail: '单个节点配置',
propertys: {
strokeStyle: {
detail: '边框颜色',
default: null
},
lineWidth: {
detail: '边框大小',
default: 2
},
strokeAlpha: {
detail: '边框透明度',
default: 0
},
fillStyle: {
detail: '背景色',
default: null
},
fillAlpha: {
detail: '背景透明度',
default: 0.25
},
focus: {
detail: 'hover设置',
propertys: {
enabled: {
detail: '是否开启',
default: true
},
strokeAlpha: {
detail: '边框透明度',
default: 0.3
}
}
},
select: {
detail: '选中设置',
propertys: {
enabled: {
detail: '是否开启',
default: true
},
lineWidth: {
detail: '描边宽度',
default: 2
},
strokeStyle: {
detail: '描边颜色',
default: '#666666'
}
}
}
}
},
label: {
detail: '文本设置',
propertys: {
field: {
detail: '获取文本的字段',
default: null
},
fontSize: {
detail: '字体大小',
default: 14
},
fontColor: {
detail: '文本颜色',
default: null
},
fontWeight: {
detail: 'fontWeight',
default: 'normal'
},
showInter: {
detail: '是否显示相交部分的文本',
default: true
}
}
}
};
}
}, {
key: "polyfill",
value: function polyfill(opt) {
if (opt.valueField) {
//20220304 所有的graph都统一一个field
opt.field = opt.valueField;
delete opt.valueField;
}
return opt;
}
}]);
}(_index.default); //venn computeTextCentres 需要的相关代码 begin
function getOverlappingCircles(circles) {
var ret = {},
circleids = [];
for (var circleid in circles) {
circleids.push(circleid);
ret[circleid] = [];
}
for (var i = 0; i < circleids.length; i++) {
var a = circles[circleids[i]];
for (var j = i + 1; j < circleids.length; ++j) {
var b = circles[circleids[j]],
d = (0, _circleintersection.distance)(a, b);
if (d + b.radius <= a.radius + 1e-10) {
ret[circleids[j]].push(circleids[i]);
} else if (d + a.radius <= b.radius + 1e-10) {
ret[circleids[i]].push(circleids[j]);
}
}
}
return ret;
}
function computeTextCentres(circles, areas) {
var ret = {},
overlapped = getOverlappingCircles(circles);
for (var i = 0; i < areas.length; ++i) {
var area = areas[i].sets,
areaids = {},
exclude = {};
for (var j = 0; j < area.length; ++j) {
areaids[area[j]] = true;
var overlaps = overlapped[area[j]];
for (var k = 0; k < overlaps.length; ++k) {
exclude[overlaps[k]] = true;
}
}
var interior = [],
exterior = [];
for (var setid in circles) {
if (setid in areaids) {
interior.push(circles[setid]);
} else if (!(setid in exclude)) {
exterior.push(circles[setid]);
}
}
var centre = computeTextCentre(interior, exterior);
ret[area] = centre;
if (centre.disjoint && areas[i].size > 0) {}
}
return ret;
}
function computeTextCentre(interior, exterior) {
var points = [],
i;
for (i = 0; i < interior.length; ++i) {
var c = interior[i];
points.push({
x: c.x,
y: c.y
});
points.push({
x: c.x + c.radius / 2,
y: c.y
});
points.push({
x: c.x - c.radius / 2,
y: c.y
});
points.push({
x: c.x,
y: c.y + c.radius / 2
});
points.push({
x: c.x,
y: c.y - c.radius / 2
});
}
var initial = points[0],
margin = circleMargin(points[0], interior, exterior);
for (i = 1; i < points.length; ++i) {
var m = circleMargin(points[i], interior, exterior);
if (m >= margin) {
initial = points[i];
margin = m;
}
}
// maximize the margin numerically
var solution = (0, _fmin.nelderMead)(function (p) {
return -1 * circleMargin({
x: p[0],
y: p[1]
}, interior, exterior);
}, [initial.x, initial.y], {
maxIterations: 500,
minErrorDelta: 1e-10
}).x;
var ret = {
x: solution[0],
y: solution[1]
};
// check solution, fallback as needed (happens if fully overlapped
// etc)
var valid = true;
for (i = 0; i < interior.length; ++i) {
if ((0, _circleintersection.distance)(ret, interior[i]) > interior[i].radius) {
valid = false;
break;
}
}
for (i = 0; i < exterior.length; ++i) {
if ((0, _circleintersection.distance)(ret, exterior[i]) < exterior[i].radius) {
valid = false;
break;
}
}
if (!valid) {
if (interior.length == 1) {
ret = {
x: interior[0].x,
y: interior[0].y
};
} else {
var areaStats = {};
(0, _circleintersection.intersectionArea)(interior, areaStats);
if (areaStats.arcs.length === 0) {
ret = {
'x': 0,
'y': -1000,
disjoint: true
};
} else if (areaStats.arcs.length == 1) {
ret = {
'x': areaStats.arcs[0].circle.x,
'y': areaStats.arcs[0].circle.y
};
} else if (exterior.length) {
// try again without other circles
ret = computeTextCentre(interior, []);
} else {
// take average of all the points in the intersection
// polygon. this should basically never happen
// and has some issues:
// https://github.com/benfred/venn.js/issues/48#issuecomment-146069777
ret = (0, _circleintersection.getCenter)(areaStats.arcs.map(function (a) {
return a.p1;
}));
}
}
}
return ret;
}
function circleMargin(current, interior, exterior) {
var margin = interior[0].radius - (0, _circleintersection.distance)(interior[0], current),
i,
m;
for (i = 1; i < interior.length; ++i) {
m = interior[i].radius - (0, _circleintersection.distance)(interior[i], current);
if (m <= margin) {
margin = m;
}
}
for (i = 0; i < exterior.length; ++i) {
m = (0, _circleintersection.distance)(exterior[i], current) - exterior[i].radius;
if (m <= margin) {
margin = m;
}
}
return margin;
}
function circlePath(x, y, r) {
var ret = [];
ret.push("\nM", x, y);
ret.push("\nm", -r, 0);
ret.push("\na", r, r, 0, 1, 0, r * 2, 0);
ret.push("\na", r, r, 0, 1, 0, -r * 2, 0);
return ret.join(" ");
}
/** returns a svg path of the intersection area of a bunch of circles */
function intersectionAreaPath(circles) {
var stats = {};
(0, _circleintersection.intersectionArea)(circles, stats);
var arcs = stats.arcs;
if (arcs.length === 0) {
return "M 0 0";
} else if (arcs.length == 1) {
var circle = arcs[0].circle;
return circlePath(circle.x, circle.y, circle.radius);
} else {
// draw path around arcs
var ret = ["\nM", arcs[0].p2.x, arcs[0].p2.y];
for (var i = 0; i < arcs.length; ++i) {
var arc = arcs[i],
r = arc.circle.radius,
wide = arc.width > r;
ret.push("\nA", r, r, 0, wide ? 1 : 0, 1, arc.p1.x, arc.p1.y);
}
return ret.join(" ") + " z";
}
}
//venn computeTextCentres 需要的相关代码 end
_index.default.registerComponent(VennGraphs, 'graphs', 'venn');
var _default = exports.default = VennGraphs;