UNPKG

chartx

Version:

Data Visualization Chart Library

818 lines (785 loc) 27.1 kB
"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 _canvax = _interopRequireDefault(require("canvax")); var _tools = require("../../../utils/tools"); var _ = _canvax.default._, event = _canvax.default.event; var Circle = _canvax.default.Shapes.Circle; var PlanetGroup = exports.default = /*#__PURE__*/function () { function PlanetGroup(opt, dataFrame, _graphs) { (0, _classCallCheck2.default)(this, PlanetGroup); this._opt = opt; this.dataFrame = dataFrame; this._graphs = _graphs; this.app = _graphs.app; this.field = null; this.iGroup = 0; this.groupLen = 1; //分组可以绘制的半径范围 this.rRange = { start: 0, to: 0 }; this.width = 0; this.height = 0; this.selectInds = []; //会从外面的index中传入一个统一的selectInds 引用 this.layoutType = "radian"; //坑位,用来做占位 this.pit = { radius: 30 }; this.planets = []; this.maxRingNum = 0; this.ringNum = 0; _.extend(true, this, (0, _tools.getDefaultProps)(PlanetGroup.defaultProps()), opt); //circle.maxRadius 绝对不能大于最大 占位 pit.radius if (this.node.maxRadius > this.pit.radius) { this.pit.radius = this.node.maxRadius; } ; this.init(); } return (0, _createClass2.default)(PlanetGroup, [{ key: "init", value: function init() { //let me = this; var _coord = this.app.getComponent({ name: 'coord' }); this.sprite = new _canvax.default.Display.Sprite({ id: "group_" + this.iGroup, context: { x: _coord.origin.x, y: _coord.origin.y } }); this._trimGraphs(); this.draw(); } }, { key: "_trimGraphs", value: function _trimGraphs() { var me = this; var _coord = this.app.getComponent({ name: 'coord' }); var _coordMaxDis = _coord.getMaxDisToViewOfOrigin(); if ((_coordMaxDis - this.rRange.to) / (this.pit.radius * 2) < this.groupLen - 1 - this.iGroup) { //要保证后面的group至少能有意个ringNum this.rRange.to = _coordMaxDis - (this.groupLen - 1 - this.iGroup) * this.pit.radius * 2; } ; if (this.rRange.to - this.rRange.start < this.pit.radius * 2) { this.rRange.to = this.rRange.start + this.pit.radius * 2; } ; //计算该group中可以最多分布多少ring if (!this.maxRingNum) { this.maxRingNum = parseInt((this.rRange.to - this.rRange.start) / (this.pit.radius * 2), 10); /* TODO: 这个目前有问题 //如果可以划10个环,但是其实数据只有8条, 那么就 当然是只需要划分8ring //this.ringNum = Math.min( this.maxRingNum , this.dataFrame.length ); */ this.ringNum = this.maxRingNum; } ; //重新计算修改 rRange.to的值 this.rRange.to = this.rRange.start + this.ringNum * this.pit.radius * 2; //根据数据创建n个星球 var planets = []; var dataLen = this.dataFrame.length; for (var i = 0; i < dataLen; i++) { var rowData = this.dataFrame.getRowDataAt(i); var planetLayoutData = { type: "planet", groupLen: this.groupLen, iGroup: me.iGroup, iNode: i, nodeElement: null, //canvax元素 labelElement: null, //label的canvax元素 rowData: rowData, //下面这些都只能在绘制的时候确定然后赋值 iRing: null, iPlanet: null, fillStyle: null, color: null, //给tips用 strokeStyle: null, pit: null, //假设这个planet是个萝卜,那么 pit 就是这个萝卜的坑 ringInd: -1, field: me.field, label: rowData[me.field], focused: false, selected: !!~_.indexOf(this.selectInds, rowData.__index__) }; planets.push(planetLayoutData); } ; if (me.sortField) { planets = planets.sort(function (a, b) { var field = me.sortField; if (me.sort == "desc") { return b.rowData[field] - a.rowData[field]; } else { return a.rowData[field] - b.rowData[field]; } }); //修正下 排序过后的 iNode _.each(planets, function (planet, i) { planet.iNode = i; }); } ; this._rings = this["_setRings_" + this.layoutType + "Range"](planets); this.planets = planets; } //根据弧度对应可以排列多少个星球的占比来分段 }, { key: "_setRings_radianRange", value: function _setRings_radianRange(planets) { var me = this; var _rings = []; var _coord = this.app.getComponent({ name: 'coord' }); for (var i = 0, l = this.ringNum; i < l; i++) { var _r = i * this.pit.radius * 2 + this.pit.radius + this.rRange.start; if (!me._graphs.center.enabled) { _r = i * this.pit.radius * 2 + this.rRange.start; } ; //该半径上面的弧度集合 var arcs = _coord.getRadiansAtR(_r, me.width, me.height); //测试代码begin--------------------------------------------------- //用来绘制弧度的辅助线 /* _.each( arcs, function( arc ){ let sector = new Canvax.Shapes.Sector({ context: { r: _r, startAngle: arc[0].radian*180/Math.PI, endAngle: arc[1].radian*180/Math.PI, //secc.endAngle, strokeStyle: "#ccc", lineWidth:1 }, }); me.sprite.addChild( sector ); } ); */ //测试代码end------------------------------------------------------ //该半径圆弧上,可以绘制一个星球的最小弧度值 //let minRadianItem = Math.atan( this.pit.radius / _r ); _rings.push({ arcs: arcs, pits: [], //萝卜坑 planets: [], //将要入坑的萝卜 radius: _r, //这个ring所在的半径轨道 max: 0 //这个环上面最多能布局下的 planet 数量 }); } ; var allplanetsMax = 0; //所有ring里面 //计算每个环的最大可以创建星球数量,然后把所有的数量相加做分母。 //然后计算自己的比例去 planets 里面拿对应比例的数据 _.each(_rings, function (ring) { //先计算上这个轨道上排排站一共可以放的下多少个星球 //一个星球需要多少弧度 var minRadian = Math.asin(me.pit.radius / ring.radius) * 2; if (ring.radius == 0) { //说明就在圆心 minRadian = Math.PI * 2; } ; var _count = 0; _.each(ring.arcs, function (arc) { var _adiff = me._getDiffRadian(arc[0].radian, arc[1].radian); if (_adiff >= minRadian) { var _arc_count = parseInt(_adiff / minRadian, 10); _count += _arc_count; //这个弧段里可以放_count个坑位 for (var p = 0; p < _arc_count; p++) { var pit = { hasRadish: false, //是否已经有萝卜(一个萝卜一个坑) start: (arc[0].radian + minRadian * p + Math.PI * 2) % (Math.PI * 2) }; pit.middle = (pit.start + minRadian / 2 + Math.PI * 2) % (Math.PI * 2); pit.to = (pit.start + minRadian + Math.PI * 2) % (Math.PI * 2); ring.pits.push(pit); //测试占位情况代码begin--------------------------------------------- /* let point = me.app.getComponent({name:'coord'}).getPointInRadianOfR( pit.middle , ring.radius ) me.sprite.addChild(new Circle({ context:{ x : point.x, y : point.y, r : me.pit.radius, fillStyle: "#ccc", strokeStyle: "red", lineWidth: 1, globalAlpha:0.3 } })); */ //测试占位情况代码end----------------------------------------------- } ; } else { //这个arc圆弧上面放不下一个坑位 } }); ring.max = _count; allplanetsMax += _count; //坑位做次随机乱序 ring.pits = _.shuffle(ring.pits); }); //allplanetsMax有了后作为分明, 可以给每个ring去分摊 planet 了 var preAllCount = 0; _.each(_rings, function (ring, i) { if (preAllCount >= planets.length) { return false; } ; var num = Math.ceil(ring.max / allplanetsMax * planets.length); num = Math.min(ring.max, num); ring.planets = planets.slice(preAllCount, preAllCount + num); if (i == _rings.length - 1) { ring.planets = planets.slice(preAllCount); } ; preAllCount += num; //给每个萝卜分配一个坑位 _.each(ring.planets, function (planet, ii) { if (ii >= ring.pits.length) { //如果萝卜已经比这个ring上面的坑要多,就要扔掉, 没办法的 return; } ; var pits = _.filter(ring.pits, function (pit) { return !pit.hasRadish; }); var targetPit = pits[parseInt(Math.random() * pits.length)]; targetPit.hasRadish = true; planet.pit = targetPit; }); }); return _rings; } }, { key: "_getDiffRadian", value: function _getDiffRadian(_start, _to) { var _adiff = _to - _start; if (_to < _start) { _adiff = (_to + Math.PI * 2 - _start) % (Math.PI * 2); } return _adiff; } //索引区间分段法 待实现 }, { key: "_setRings_indexRange", value: function _setRings_indexRange() {} //值区间分段法 //todo:这样确实就很可能数据集中在两段中间没有 待实现 }, { key: "_setRings_valRange", value: function _setRings_valRange() {} }, { key: "draw", value: function draw() { var me = this; var _coord = this.app.getComponent({ name: 'coord' }); _.each(this._rings, function (ring, i) { var _ringCtx = { rotation: 0 }; if (ring.arcs.length == 1 && ring.arcs[0][0].radian == 0 && ring.arcs[0][1].radian == Math.PI * 2) { //如果这个是一个整个的内圆,那么就做个随机的旋转 _ringCtx.rotation = parseInt(Math.random() * 360); } ; var _ringSp = new _canvax.default.Display.Sprite({ context: _ringCtx }); _.each(ring.planets, function (p, ii) { if (!p.pit) { //如果这个萝卜没有足够的坑位可以放,很遗憾,只能扔掉了 return; } ; var point = _coord.getPointInRadianOfR(p.pit.middle, ring.radius); var r = me._getRProp(me.node.radius, i, ii, p); //计算该萝卜在坑位(pit)中围绕pit的圆心可以随机移动的范围(r) var _transR = me.node.maxRadius - r; //然后围绕pit的圆心随机找一个点位,重新设置Point var _randomTransR = parseInt(Math.random() * _transR); var _randomAngle = parseInt(Math.random() * 360); var _randomRadian = _randomAngle * Math.PI / 180; if (_randomTransR != 0) { //说明还是在圆心, 就没必要重新计算point point.x += Math.sin(_randomRadian) * _randomTransR; point.y += Math.cos(_randomRadian) * _randomTransR; } ; var node = me.node; if (p.selected) { node = me.node.select; } ; var _fillStyle = me._getProp(me.node.fillStyle, p); var _strokeStyle = me._getProp(node.strokeStyle, p); var _lineAlpha = me._getProp(node.strokeAlpha, p); var _lineWidth = me._getProp(node.lineWidth, p); var circleCtx = { x: point.x, y: point.y, r: r, fillStyle: _fillStyle, lineWidth: _lineWidth, strokeStyle: _strokeStyle, strokeAlpha: _lineAlpha, cursor: "pointer" }; //设置好p上面的fillStyle 和 strokeStyle p.color = p.fillStyle = _fillStyle; p.strokeStyle = _strokeStyle; p.iRing = i; p.iPlanet = ii; var _circle = new Circle({ hoverClone: false, context: circleCtx }); _circle.on(event.types.get(), function (e) { e.eventInfo = { title: null, trigger: 'this.node', //me.node, nodes: [this.nodeData] }; if (this.nodeData.label) { e.eventInfo.title = this.nodeData.label; } ; if (me.node.focus.enabled) { if (e.type == "mouseover") { me.focusAt(this.nodeData); } if (e.type == "mouseout") { me.unfocusAt(this.nodeData); } } ; if (me.node.select.enabled && me.node.select.triggerEventType.indexOf(e.type) > -1) { //如果开启了图表的选中交互 //TODO:这里不能 var onbefore = me.node.select.onbefore; var onend = me.node.select.onend; if (!onbefore || typeof onbefore == 'function' && onbefore.apply(me, [this.nodeData]) !== false) { if (this.nodeData.selected) { //说明已经选中了 me.unselectAt(this.nodeData); } else { me.selectAt(this.nodeData); } onend && typeof onend == 'function' && onend.apply(me, [this.nodeData]); } } ; //fire到root上面去的是为了让root去处理tips me.app.fire(e.type, e); }); //互相用属性引用起来 _circle.nodeData = p; p.nodeElement = _circle; _circle.ringInd = i; _circle.planetIndInRing = ii; _ringSp.addChild(_circle); //如果有开启入场动画 if (me._graphs.animation) { var _r = _circle.context.r; var _globalAlpha = _circle.context.globalAlpha; _circle.context.r = 1; _circle.context.globalAlpha = 0.1; _circle.animate({ r: _r, globalAlpha: _globalAlpha }, { delay: Math.round(Math.random() * 1500), onComplete: function onComplete() { //这个时候再把label现实出来 _circle.labelElement && (_circle.labelElement.context.visible = true); var _cloneNode = _circle.clone(); _ringSp.addChildAt(_cloneNode, 0); _cloneNode.animate({ r: _r + 10, globalAlpha: 0 }, { onComplete: function onComplete() { _cloneNode.destroy(); } }); } }); } ; //然后添加label //绘制实心圆上面的文案 //x,y 默认安装圆心定位,也就是position == 'center' var _labelCtx = { x: point.x, y: point.y, //point.y + r +3 fontSize: me.label.fontSize, textAlign: me.label.textAlign, textBaseline: me.label.verticalAlign, fillStyle: me.label.fontColor, rotation: -_ringCtx.rotation, rotateOrigin: { x: 0, y: 0 //-(r + 3) } }; var _label = new _canvax.default.Display.Text(p.label, { context: _labelCtx }); var _labelWidth = _label.getTextWidth(); var _labelHeight = _label.getTextHeight(); if (_labelWidth > r * 2) { _labelCtx.fontSize = me.label.fontSize - 3; } ; //最开始提供这个function模式,是因为还没有实现center,bottom,auto //只能用function的形式用户自定义实现 //现在已经实现了center,bottom,auto,但是也还是先留着吧,也不碍事 if (_.isFunction(me.label.position)) { var _pos = me.label.position({ node: _circle, circleR: r, circleCenter: { x: point.x, y: point.y }, textWidth: _labelWidth, textHeight: _labelHeight }); _labelCtx.rotation = -_ringCtx.rotation; _labelCtx.rotateOrigin = { x: -(_pos.x - _labelCtx.x), y: -(_pos.y - _labelCtx.y) }; _labelCtx.x = _pos.x; _labelCtx.y = _pos.y; } ; if (me.label.position == 'auto') { if (_labelWidth > r * 2) { setPositionToBottom(); } ; } ; if (me.label.position == 'bottom') { setPositionToBottom(); } ; function setPositionToBottom() { _labelCtx.y = point.y + r + 3; //_labelCtx.textBaseline = "top"; _labelCtx.rotation = -_ringCtx.rotation; _labelCtx.rotateOrigin = { x: 0, y: -(r + _labelHeight * 0.7) }; } ; _labelCtx.x += me.label.offsetX; _labelCtx.y += me.label.offsetY; //TODO:这里其实应该是直接可以修改 _label.context. 属性的 //但是这里版本的canvax有问题。先重新创建文本对象吧 _label = new _canvax.default.Display.Text(p.label, { context: _labelCtx }); //互相用属性引用起来 _circle.labelElement = _label; _label.nodeData = p; p.labelElement = _label; if (me._graphs.animation) { _label.context.visible = false; } ; _ringSp.addChild(_label); }); me.sprite.addChild(_ringSp); }); } }, { key: "_getRProp", value: function _getRProp(r, ringInd, iNode, nodeData) { var me = this; if (_.isString(r) && _.indexOf(me.dataFrame.fields, r) > -1) { if (this.__rValMax == undefined && this.__rValMax == undefined) { this.__rValMax = 0; this.__rValMin = 0; _.each(me.planets, function (planet) { me.__rValMax = Math.max(me.__rValMax, planet.rowData[r]); me.__rValMin = Math.min(me.__rValMin, planet.rowData[r]); }); } ; var rVal = nodeData.rowData[r]; return me.node.minRadius + (rVal - this.__rValMin) / (this.__rValMax - this.__rValMin) * (me.node.maxRadius - me.node.minRadius); } ; return me._getProp(r, nodeData); } }, { key: "_getProp", value: function _getProp(p, nodeData) { var iGroup = this.iGroup; if (_.isFunction(p)) { return p.apply(this, [nodeData, iGroup]); //return p( nodeData ); } ; return p; } }, { key: "getPlanetAt", value: function getPlanetAt(target) { var planet = target; if (_.isNumber(target)) { _.each(this.planets, function (_planet) { if (_planet.rowData.__index__ == target) { planet = _planet; return false; } }); } ; return planet; } //这里的ind是原始的__index__ }, { key: "selectAt", value: function selectAt(ind) { if (!this.node.select.enabled) return; var planet = this.getPlanetAt(ind); planet.selected = true; //可能这个数据没有显示的,就没有nodeElement if (planet.nodeElement) { planet.nodeElement.context.lineWidth = this._getProp(this.node.select.lineWidth, planet); planet.nodeElement.context.strokeStyle = this._getProp(this.node.select.strokeStyle, planet); planet.nodeElement.context.strokeAlpha = this._getProp(this.node.select.strokeAlpha, planet); } ; for (var i = 0; i < this.selectInds.length; i++) { if (ind === this.selectInds[i]) { this.selectInds.splice(i--, 1); break; } ; } ; } //这里的ind是原始的__index__ }, { key: "unselectAt", value: function unselectAt(ind) { if (!this.node.select.enabled) return; var planet = this.getPlanetAt(ind); planet.selected = false; if (planet.nodeElement) { planet.nodeElement.context.lineWidth = this._getProp(this.node.lineWidth, planet); planet.nodeElement.context.strokeAlpha = this._getProp(this.node.strokeAlpha, planet); } ; this.selectInds.push(ind); } }, { key: "getSelectedNodes", value: function getSelectedNodes() { return _.filter(this.planets, function (planet) { return planet.selected; }); } }, { key: "focusAt", value: function focusAt(ind) { if (!this.node.focus.enabled) return; var planet = this.getPlanetAt(ind); if (planet.selected) return; planet.focused = true; if (planet.nodeElement) { planet.nodeElement.context.lineWidth = this._getProp(this.node.focus.lineWidth, planet); planet.nodeElement.context.strokeStyle = this._getProp(this.node.focus.strokeStyle, planet); planet.nodeElement.context.strokeAlpha = this._getProp(this.node.focus.strokeAlpha, planet); } ; } }, { key: "unfocusAt", value: function unfocusAt(ind) { if (!this.node.focus.enabled) return; var planet = this.getPlanetAt(ind); if (planet.selected) return; planet.focused = false; if (planet.nodeElement) { planet.nodeElement.context.lineWidth = this._getProp(this.node.lineWidth, planet); planet.nodeElement.context.strokeAlpha = this._getProp(this.node.strokeAlpha, planet); } ; } }], [{ key: "defaultProps", value: function defaultProps() { return { sort: { detail: '排序', default: 'desc' }, sortField: { detail: '用来排序的字段', default: 'null' }, node: { detail: '单个数据节点图形配置', propertys: { maxRadius: { detail: '最大半径', default: 30 }, minRadius: { detail: '最小半径', default: 5 }, radius: { detail: '半径', default: 15, documentation: '也可以是个function,也可以配置{field:"pv"}来设置字段, 自动计算r' }, lineWidth: { detail: '描边线宽', default: 1 }, strokeStyle: { detail: '描边颜色', default: '#ffffff' }, fillStyle: { detail: '图形填充色', default: '#f2fbfb' }, strokeAlpha: { detail: '边框透明度', default: 0.6 }, focus: { detail: 'hover态设置', propertys: { enabled: { detail: '是否开启', default: true }, strokeAlpha: { detail: 'hover时候边框透明度', default: 0.7 }, lineWidth: { detail: 'hover时候边框大小', default: 2 }, strokeStyle: { detail: 'hover时候边框颜色', default: '#fff' } } }, select: { detail: '选中态设置', propertys: { enabled: { detail: '是否开启', default: false }, strokeAlpha: { detail: '选中时候边框透明度', default: 1 }, lineWidth: { detail: '选中时候边框大小', default: 2 }, strokeStyle: { detail: '选中时候边框颜色', default: '#fff' }, triggerEventType: { detail: '触发事件', default: 'click,tap' }, onbefore: { detail: '执行select处理函数的前处理函数,返回false则取消执行select', default: null }, onend: { detail: '执行select处理函数的后处理函数', default: null } } } } }, label: { detail: '文本设置', propertys: { enabled: { detail: '是否开启', default: true }, fontColor: { detail: '文本颜色', default: '#666666' }, fontSize: { detail: '文本字体大小', default: 13 }, textAlign: { detail: '水平对齐方式', default: 'center' }, verticalAlign: { detail: '基线对齐方式', default: 'middle' }, position: { detail: '文本布局位置', default: 'center' }, offsetX: { detail: 'x方向偏移量', default: 0 }, offsetY: { detail: 'y方向偏移量', default: 0 } } } }; } }]); }();