UNPKG

color-data

Version:

使用Canvas/SVG完成Json数据转换为拓扑图和流程图的插件

525 lines (510 loc) 16.1 kB
import BulletEvents from '../libs/bullet-events/bullet-events'; /** * Canvas绘制类 * * @export * @class Draw */ export default class Draw { /** *Creates an instance of Draw. * @param {*} argsObj * @memberof Draw */ constructor(argsObj) { this._args = argsObj; this._container = null; this._area = null; this._canvas = null; this._context = null; this._scaleStep = 0.1; this._scale = 1; this._style = this._args.style; this._initDom(); } /** * 初始化参数数据 * * @param {*} nodes 节点数据 * @memberof Draw */ _initArgsData(...nodes) { if (nodes.length > 0) { for (let i = 0; i < nodes.length; i++) { const obj = nodes[i]; const _obj = { name: obj['name'] || `node-${new Date().getTime() * Math.random()}`, // 节点图名称/id text: obj['text'] || '', // 图中显示字样 isShow: obj['isShow'] || true, // 是否默认显示图 type: obj['type'] || 0, // 0:方框图 1:菱形图 2:椭圆图 3:四边形图 value: obj['value'] || null, // 节点图所代表值 [object | string | number | boolean] events: obj['events'] || {}, // 节点图所绑定事件 x: obj['x'] || 0, // 节点图中心点x坐标值 y: obj['y'] || 0, // 节点图中心点y坐标值 width: obj['width'] || 160, // 节点图宽度 height: obj['height'] || 40, // 节点图高度 children: obj['children'] || [], // 节点图子节点 }; this._initArgsData(..._obj.children); nodes[i] = Object.assign(obj, _obj); } } } /** * 初始化Dom元素 * * @memberof Draw */ _initDom() { if (this._args.data && this._args.data.length > 0) { this._initArgsData(...this._args.data); } this._canvas = document.createElement('canvas'); this._canvas.id = `ColorData-Canvas-${Math.random() * (new Date().getTime())}`; this._container = document.querySelector(this._args.container); if (this._container) { this._area = { x: this._container.offsetLeft, y: this._container.offsetTop, w: this._container.offsetWidth, h: this._container.offsetHeight, }; this._canvas.style.width = `${this._area.w}px`; this._canvas.style.height = `${this._area.h}px`; this._canvas.width = this._area.w this._canvas.height = this._area.h this._container.appendChild(this._canvas); this._context = this._canvas.getContext('2d'); this._bindEvents(); this.arrange(...this._args.data); this.render(); return this; } return null; } /** * 触发事件 * * @param {*} element 绑定事件的元素 * @param {*} event 绑定的事件名称 * @returns * @memberof Draw */ _dispatchEvent(element, event) { if (document.createEventObject) { // IE浏览器支持fireEvent方法 var evt = document.createEventObject(); return element.fireEvent('on' + event, evt) } else { // 其他标准浏览器使用dispatchEvent方法 var evt = document.createEvent('HTMLEvents'); evt.initEvent(event, true, true); return !element.dispatchEvent(evt); } } /** * 元素事件触发控制器 * * @param {*} ev 父级鼠标对象 * @param {*} evname 触发事件名称 * @memberof Draw */ _eventsEmitter(ev, name, ...ctx) { for (let i = 0; i < Object.keys(BulletEvents._list).length; i++) { const _k = Object.keys(BulletEvents._list)[i], xy = _k.split('-')[0].split('.'), wh = _k.split('-')[1].split('.'), eX = ev.clientX, eY = ev.clientY; if (eX > Number(xy[0]) - Number(wh[0]) / 2 && eX < Number(xy[0]) + Number(wh[0]) / 2 && eY > Number(xy[1]) - Number(wh[1]) / 2 && eY < Number(xy[1]) + Number(wh[1]) / 2) { BulletEvents.emit(_k, name, ctx.length > 0 ? ctx[0] : null, ev); break; } } } /** * 绑定Canvas自身事件 * * @memberof Draw */ _bindEvents() { if (!this._canvas || !this._context) { return; } // 缩放监听事件 this._canvas.addEventListener('wheel', ev => { this._scale = 1 + (ev.deltaY < 0 ? this._scaleStep : -1 * this._scaleStep); if (this._scale - this._scaleStep <= 0) { this._scale += this._scaleStep; } this.scale(); }, true); // 点击监听事件 this._canvas.addEventListener('click', (ev) => { this._eventsEmitter(ev, 'click'); }, true); // 移动监听事件 let _down = false, _tX = 0, _tY = 0; this._canvas.addEventListener('mousedown', (ev) => { if (!_down) { _down = true; } this._eventsEmitter(ev, 'mousedown'); }, true); this._canvas.addEventListener('mousemove', (ev) => { if (_down) { let dX = ev.clientX - _tX, dY = ev.clientY - _tY; dX = dX > 0 ? Math.min(dX, 20) : Math.max(dX, -20); dY = dY > 0 ? Math.min(dY, 20) : Math.max(dY, -20); this.move(dX, dY); _tX = ev.clientX; _tY = ev.clientY; } this._eventsEmitter(ev, 'mousemove'); }, true); this._canvas.addEventListener('mouseup', (ev) => { _down = false; _tX = 0; _tY = 0; this._eventsEmitter(ev, 'mouseup'); }, true); // 双击自动排版事件 this._canvas.addEventListener('dblclick', () => { this._dispatchEvent(this._canvas, 'mouseup'); this.arrange(...this._args.data); }, true); } /** * 渲染 * * @returns * @memberof Draw */ render() { this._draw(); if (window.requestAnimationFrame) { window.requestAnimationFrame(() => { this.render(); }); } else { setInterval(() => { this.render(); }, 1000 / 60); } } /** * 清空画布 * * @param {*} area 画布区域 * @returns * @memberof Draw */ clean(...area) { if (!this._canvas || !this._context) { return; } this._context.save(); this._context.setTransform(1, 0, 0, 1, 0, 0); this._context.clearRect(0, 0, this._canvas.width, this._canvas.height); this._context.restore(); } /** * 设置canvas缩放比例 * * @param {*} scale 缩放比例 * @memberof Draw */ scale(...scale) { if (!this._canvas || !this._context) { return; } if (scale.length > 0) { this._scale = scale[0]; } const _self = this; function scaleNodes(...nodes) { for (let i = 0; i < nodes.length; i++) { nodes[i]['x'] *= _self._scale; nodes[i]['y'] *= _self._scale; nodes[i]['width'] *= _self._scale; nodes[i]['height'] *= _self._scale; if (nodes[i]['children'] && nodes[i]['children'].length > 0) { scaleNodes(...nodes[i]['children']); } } } if (this._args.data && this._args.data.length > 0) { BulletEvents.clearAll(); scaleNodes(...this._args.data) } // this._context.scale(this._scale, this._scale); } /** * 移动canvas画布 * * @param {*} position xy轴移动值 [dX,dY] * @memberof Draw */ move(...dMove) { if (!this._canvas || !this._context) { return; } if (dMove.length > 0) { function moveNodes(...nodes) { for (let i = 0; i < nodes.length; i++) { nodes[i]['x'] += dMove[0]; nodes[i]['y'] += dMove[1]; if (nodes[i]['children'] && nodes[i]['children'].length > 0) { moveNodes(...nodes[i]['children']); } } } if (this._args.data && this._args.data.length > 0) { BulletEvents.clearAll(); moveNodes(...this._args.data) } // this._context.translate(dMove[0], dMove[1]); } } /** * 自动排布节点图 * * @param {*} nodes 节点数据 * @memberof Draw */ arrange(...nodes) { if (!nodes) { return; } if (nodes.length > 0) { let _margin = 30, _lineH = 160, _rowY = 0, _colX = 0, _maxW = 0; /** * 自动排布子级节点 * * @param {*} node 父级节点数据 */ function _arrangeChild(node, _rY) { if (node['children'] && node['children'].length > 0) { let _crowY = _rY, _cX = node['x']; for (let j = 0; j < node['children'].length; j++) { const child = node['children'][j]; _rY += _lineH + _margin; child['x'] = _cX - child['width'] / 2 + _margin; child['y'] = _rY; _cX += child['width'] + _margin; _maxW = Math.max(_maxW, _cX); _arrangeChild(child, _rY); _rY = _crowY; } } } for (let i = 0; i < nodes.length; i++) { const node = nodes[i]; node['x'] = _maxW + node['width'] / 2 + _margin; node['y'] = _rowY + node['height'] / 2 + _margin; _colX = node['x'] - node['width'] / 2; _rowY = node['y']; _maxW = Math.max(_maxW, _colX) + _margin; _arrangeChild(node, _rowY); _rowY = 0; } } } /** * 绘制 * * @returns * @memberof Draw */ _draw() { this.clean(); if (!this._canvas || !this._context || !this._args.data) { return; } this.drawLine(...this._args.data); this.drawNode(...this._args.data); } /** * 设置画线样式 * * @memberof Draw */ _setLine() { this._context.lineWidth = this._style.lineSize; this._context.lineCap = 'round'; this._context.lineJoin = 'round'; this._context.strokeStyle = this._style.lineColor; } /** * 设置画多边形样式 * * @memberof Draw */ _setPolygon() { this._context.lineWidth = this._style.strokeSize; this._context.lineCap = 'butt'; this._context.lineJoin = 'miter'; this._context.strokeStyle = this._style.strokeColor; this._context.fillStyle = this._style.fillColor; } /** * 设置字体样式 * * @memberof Draw */ _setFonts() { this._context.font = `${this._style.fontSize}px ${this._style.fontStyle}`; this._context.fillStyle = this._style.fontColor; } /** * 初始化多边形点集 * * @param {*} pObj 多边形数据对象 * @returns * @memberof Draw */ _initPoints(pObj) { if (pObj['type'] === 1) { // 菱形图 return [ [pObj['x'], pObj['y'] - pObj['height'] / 2], [pObj['x'] + pObj['width'] / 2, pObj['y']], [pObj['x'], pObj['y'] + pObj['height'] / 2], [pObj['x'] - pObj['width'] / 2, pObj['y']], ]; } else if (pObj['type'] === 2) { // 椭圆图 let step = (pObj['width'] > pObj['height']) ? 1 / pObj['width'] : 1 / pObj['height']; let arr = []; for (var i = 0; i < 2 * Math.PI; i += step) { arr.push([pObj['x'] + pObj['width'] / 2 * Math.cos(i), pObj['y'] + pObj['height'] / 2 * Math.sin(i)]); } arr.unshift([pObj['x'] + pObj['width'] / 2, pObj['y']]); return arr; } else if (pObj['type'] === 3) { // 四边形图 return [ [pObj['x'] - pObj['width'] / 2, pObj['y'] - pObj['height'] / 2], [pObj['x'] + pObj['width'] / 2, pObj['y'] - pObj['height'] / 2], [pObj['x'] + pObj['width'] / 2 + 30, pObj['y'] + pObj['height'] / 2], [pObj['x'] - pObj['width'] / 2 + 30, pObj['y'] + pObj['height'] / 2], ] } else { // 方框图 return [ [pObj['x'] - pObj['width'] / 2, pObj['y'] - pObj['height'] / 2], [pObj['x'] + pObj['width'] / 2, pObj['y'] - pObj['height'] / 2], [pObj['x'] + pObj['width'] / 2, pObj['y'] + pObj['height'] / 2], [pObj['x'] - pObj['width'] / 2, pObj['y'] + pObj['height'] / 2], ] } } /** * 绘制节点图像 * * @param {*} nodes 节点数据 * @returns * @memberof Draw */ drawNode(...nodes) { if (!this._canvas || !this._context || !nodes) { return; } if (nodes.length > 0) { for (let i = 0; i < nodes.length; i++) { const obj = nodes[i]; this._drawPolygon(obj); this._drawText(obj); if (obj['children'] && obj['children'].length > 0) { this.drawNode(...obj['children']); } } } } /** * 绘制多边形 * * @param {*} pObj 多边形数据对象 * @returns * @memberof Draw */ _drawPolygon(pObj) { if (!pObj || !pObj['isShow']) { return; } if (typeof pObj['type'] !== 'number') { pObj['type'] = 0; } this._setPolygon(); this._context.beginPath(); const points = this._initPoints(pObj); for (let i = 0; i < points.length; i++) { const point = points[i]; if (i === 0) { this._context.moveTo(point[0], point[1]); } else { this._context.lineTo(point[0], point[1]) } } if (pObj['events'] && Object.keys(pObj['events']).length > 0) { for (let i = 0; i < Object.keys(pObj['events']).length; i++) { const _fk = Object.keys(pObj['events'])[i]; BulletEvents.on(`${pObj['x']}.${pObj['y']}-${pObj['width']}.${pObj['height']}`, _fk, pObj['events'][_fk]); } } else { BulletEvents.on(`${pObj['x']}.${pObj['y']}-${pObj['width']}.${pObj['height']}`, 'click', (ev) => { console.log('click', pObj, ev); }); } this._context.closePath(); this._context.stroke(); this._context.fill(); } /** * 绘制文字(含换行) * * @param {*} tObj 文字数据对象 * @returns * @memberof Draw */ _drawText(tObj) { if (!tObj || !tObj['text'] || !tObj['x'] || !tObj['y'] || !tObj['width']) { return; } this._setFonts(); let _mW = this._context.measureText(tObj['text']).width, // 测量的文字总长度 _tW = 0, // 最大一行的长度 _cI = 0, // 最大一行末位索引 row = 1; // 总行数 if (_mW > tObj['width']) { for (let i = 0; i < tObj['text'].length; i++) { if (i === tObj['text'].length - 1) { this._context.fillText(tObj['text'].substring(_cI, i), tObj['x'] - tObj['width'] / 2, tObj['y'] - tObj['height'] / 2 + 10 + row * this._style.fontSize, tObj['width']); } _tW += this._context.measureText(tObj['text'][i]).width; if (_tW >= tObj['width'] - 10) { this._context.fillText(tObj['text'].substring(_cI, i), tObj['x'] - tObj['width'] / 2, tObj['y'] - tObj['height'] / 2 + 10 + row * this._style.fontSize, tObj['width']); _cI = i; _tW = 0; row += 1; } } } else { this._context.fillText(tObj['text'], tObj['x'] - tObj['width'] / 2 + ((tObj['width'] - _mW) / 2), tObj['y'], tObj['width']); } } /** * 绘制线 * * @param {*} nodes 节点数据 * @returns * @memberof Draw */ drawLine(...nodes) { if (!this._canvas || !this._context) { return; } if (nodes.length > 0) { this._setLine(); const _self = this; function drawChildLine(node) { if (node && node['children'] && node['children'].length > 0) { for (let j = 0; j < node['children'].length; j++) { const nodeN = node['children'][j] _self._context.beginPath(); _self._context.moveTo(node.x, node.y + node.height / 2); _self._context.lineTo(nodeN.x, nodeN.y - nodeN.height / 2); _self._context.closePath(); _self._context.stroke(); drawChildLine(nodeN); } } } for (let i = 0; i < nodes.length; i++) { const node = nodes[i]; drawChildLine(node); } } } }