UNPKG

chartx3d

Version:

Data Visualization Chart Library

571 lines (433 loc) 16.2 kB
import { _, dataFrame, global, $ } from 'mmvis'; import { Application } from './framework/application'; import { InertialSystem } from './framework/coord/inertial'; import { Cartesian3D } from './framework/coord/cartesian3d'; import { Component } from './components/Component'; import { Events } from 'mmgl/src/index'; import { OrbitControls } from './framework/OrbitControls'; import { Interaction } from './framework/interaction'; import { MainView, LabelView, VERSION } from './constants'; let __tipShowEvent = null, __tipHideEvent = null, __tipMoveEvent = null, __redraw = null; let _cid = 0; class Chart3d extends Events { constructor(node, data, opt, componentModules) { super(); console.log('Chart3D ', VERSION); this.domSelector = node; this.opt = opt; this.data = data; this.componentModules = componentModules; this.el = null; this.view = null; this.domView = null; this.stageView = null; this.canvasDom = null; //画布DOM元素 this.width = 0; //画布的宽 this.height = 0; //画布的高 this.renderer = null; this.renderView = null; this.app = null; this.currCoord = null; //初始化画布 this._createDomContainer(node); // //初始化数据 // //不管传入的是data = [ ['xfield','yfield'] , ['2016', 111]] // //还是 data = [ {xfiled, 2016, yfield: 1111} ],这样的格式, // //通过parse2MatrixData最终转换的是data = [ ['xfield','yfield'] , ['2016', 111]] 这样 chartx的数据格式 // //后面有些地方比如 一些graphs中会使用dataFrame.org,, 那么这个dataFrame.org和_data的区别是, // //_data是全量数据, dataFrame.org是_data经过dataZoom运算过后的子集 // this._data = parse2MatrixData(data); //三维引擎初始化 this.app = new Application(this.width, this.height); //初始化渲染器 this.renderer = this.app._framework.renderer; //组件管理机制,所有的组件都绘制在这个地方 this.components = []; this.inited = false; this.dataFrame = this._initData(this.data, {}); //每个图表的数据集合 都 存放在dataFrame中。 this.initComponent(); } init(DefaultControls) { let me = this; let rendererOpts = _.extend({}, DefaultControls); this.opt.coord.controls = this.opt.coord.controls || {}; let controlOpts = this.opt.coord.controls = _.extend(rendererOpts, this.opt.coord.controls); this._initRenderer(rendererOpts); let controls = this.orbitControls = new OrbitControls(this.renderView._camera, this.view); let interaction = this.interaction = new Interaction(this.view); interaction.addView(this.labelView); interaction.addView(this.renderView); // controls.minDistance = controlOpts.minDistance; // controls.maxDistance = controlOpts.maxDistance; // controls.minZoom = controlOpts.minZoom; // controls.maxZoom = controlOpts.maxZoom; // controls.enableDamping = true; // controls.enablePan = false; // controls.enableKeys = false; // controls.autoRotate = controlOpts.autoRotate; // controls.autoRotateSpeed = 1.0; _.extend(true, controls, controlOpts); //自动旋转时间 if (controls.autoRotate) { window.setTimeout(() => { controls.autoRotate = false; }, 15000); } //如果发生交互停止自动旋转 controls.on('start', onStart); //有交互开始渲染 this._onChangeBind = onChange.bind(me); controls.on('change', this._onChangeBind); this._onRenderBeforeBind = onRenderBefore.bind(controls); this.app._framework.on('renderbefore', this._onRenderBeforeBind); this._onRenderAfterBind = onRenderAfter.bind(interaction); this.app._framework.on('renderafter', this._onRenderAfterBind) interaction.on('refresh', this._onChangeBind); // //同步主相机的位置和方向 // controls.on('change', (e) => { // this.labelView._camera.position.copy(e.target.object.position); // this.labelView._camera.lookAt(e.target.target); // }) //启动渲染进程 this.app.launch(); this._onWindowResizeBind = me.resize.bind(me); window.addEventListener('resize', this._onWindowResizeBind, false); //绑定tip事件 this.bindEvent() } setCoord(_Coord) { //初始化物体的惯性坐标(放在具体的坐标系下) if (_Coord === InertialSystem || _Coord.prototype instanceof InertialSystem) { this.currCoord = new _Coord(this); // this.currCoord = _Coord; this.rootStage.add(this.currCoord.group); } } getCoord() { return this.currCoord; } initComponent() { let opts = this.opt; this.components = []; //先初始化皮肤组件 if ('theme' in opts) { let theme = this.componentModules.get('theme'); if (theme) { this.addComponent(theme, opts.theme); } } //先初始化坐标系 let coord = this.componentModules.get('coord', opts.coord.type) if (!coord) { coord = InertialSystem; } this.setCoord(coord); //加载graph组件 for (let t = 0; t < opts.graphs.length; t++) { let key = opts.graphs[t].type; let comp = this.componentModules.get('graphs', key); if (comp) { this.addComponent(comp, opts.graphs[t]); } } //加载其他组件 for (let p in opts) { if (p === 'coord') continue; if (p === 'graphs') continue; if (_.isArray(opts[p])) { for (let t = 0; t < opts[p].length; t++) { let key = opts[p][t].type; let comp = this.componentModules.get(p, key); this.addComponent(comp, opts[p][t]); } } else { let comp = this.componentModules.get(p); if (comp) { this.addComponent(comp, opts[p]); } } } } //添加组件 addComponent(cmp, opts) { //todo 图像是否要分开,目前没有分开共用Component一个基类 if (cmp.prototype instanceof Component) { let instance = new cmp(this, opts); this.components.push(instance); } } drawComponent() { //先绘制坐标系 this.currCoord.drawUI(); this.components.forEach(cmp => { this.currCoord.group.add(cmp.group); cmp.draw(); }) } draw() { this.currCoord.initCoordUI(); this.drawComponent(); this.app.forceRender(); } getComponent(query) { let cmp = this.getComponents(query)[0]; return !cmp ? null : cmp } getComponents(query) { let arr = []; let result = true; this.components.forEach(cmp => { result = true; for (let key in query) { if (_.isString(cmp[key]) && _.isString(query[key])) { result = result && cmp[key].toLowerCase() == query[key].toLowerCase(); } else { result = result && cmp[key] == query[key]; } } if (result) { arr.push(cmp); } }); return arr; } bindEvent() { __redraw = (e) => { if (this.currCoord) { this.currCoord.fire({ type: 'legendchange', data: e.data }); } this.draw(); } this.on('legendchange', __redraw); const TipName = 'Tips'; __tipShowEvent = (e) => { let tips = this.getComponent({ name: TipName }); let { offsetX: x, offsetY: y } = e.event; if (tips !== null) { let _title = e.data.xField ? e.data.rowData[e.data.xField] : ""; tips.show({ eventInfo: _.extend({ title: _title, }, e.data), pos: { x, y } }) } }; __tipHideEvent = (e) => { let tips = this.getComponent({ name: TipName }); if (tips !== null) { tips.hide(); } }; __tipMoveEvent = (e) => { let tips = this.getComponent({ name: TipName }); let { offsetX: x, offsetY: y } = e.event; let _title = e.data.xField ? e.data.rowData[e.data.xField] : ""; if (tips !== null) { tips.show({ eventInfo: _.extend({ title: _title, }, e.data), pos: { x, y } }) } } this.on('tipShow', __tipShowEvent); this.on('tipHide', __tipHideEvent) this.on('tipMove', __tipMoveEvent) } _createDomContainer(_domSelector) { let viewObj = null; this._cid = "chartx3d_" + _cid++; this.el = $.query(_domSelector); this.width = this.el.offsetWidth; this.height = this.el.offsetHeight; viewObj = $.createView(this.width, this.height, this._cid); this.view = viewObj.view; this.stageView = viewObj.stageView; this.domView = viewObj.domView; this.el.innerHTML = ""; this.el.appendChild(this.view); this.id = "chartx_" + this._cid; this.el.setAttribute("chart_id", this.id); this.el.setAttribute("chartx3d_version", "1.0"); } _initData(data, opt) { return dataFrame.call(this, data, opt); } _initRenderer(rendererOpts) { let app = this.app; //正常绘制的view let renderView = this.renderView = app.createView(MainView); //label 绘制的View let labelView = this.labelView = app.createView(LabelView); // let renderView = this.renderView = app.getView(MainView); // let labelView = this.labelView = app.getView(LabelView); this.rootStage = app.addGroup({ name: 'rootStage' }); renderView.addObject(this.rootStage); renderView.setSize(this.width, this.height); renderView.setBackground(0xFFFFFF); //默认透视投影 renderView.setControls(rendererOpts); renderView.project('perspective'); //'ortho' | 'perspective', //初始化labelView this.labelGroup = app.addGroup({ name: 'labelsGroup', flipY: true }); labelView.addObject(this.labelGroup); labelView.setSize(this.width, this.height); //labelView.setBackground("rgba(0,0,0,0)"); labelView.setControls(rendererOpts); //labelView.project('ortho'); //'ortho' | 'perspective', labelView.createScreenProject(); if (this.canvasDom) { this.stageView.removeChild(this.canvasDom) this.canvasDom = null; } this.stageView.appendChild(this.renderer.domElement); this.canvasDom = this.renderer.domElement; } resize() { this.width = this.el.offsetWidth; this.height = this.el.offsetHeight; this.app.resize(this.width, this.height, this.opt.coord.controls.boxHeight); } //ind 如果有就获取对应索引的具体颜色值 getTheme(ind) { var colors = global.getGlobalTheme(); var _theme = this.getComponent({ name: 'theme' }); if (_theme) { colors = _theme.get(); }; if (ind != undefined) { return colors[ind % colors.length] || "#ccc"; }; return colors; } //数据变更后调用reset reset(opt, data) { !opt && (opt = {}); //配置初始化 _.extend(true, this.opt, opt); //数据初始化 if (data) { this.data = data; //this._data = parse2MatrixData(data); this.dataFrame = this._initData(this.data, {}); }; this.dispose(); //三维引擎初始化 this.app = new Application(this.width, this.height); //初始化渲染器 this.renderer = this.app._framework.renderer; this.init(); //坐标系重新初始化 let coord = null; if (this.opt.coord.type == 'box') { coord = new Cartesian3D(this); } this.setCoord(coord); //组件重新初始化 this.components = []; this.initComponent(); this.draw(); } /* * 只响应数据的变化,不涉及配置变化 * * @dataTrigger 一般是触发这个data reset的一些场景数据, * 比如如果是 datazoom 触发的, 就会有 trigger数据{ name:'datazoom', left:1,right:1 } */ resetData(data, dataTrigger) { //销毁默认绑定的事件 // this.off('tipShow', __tipShowEvent); // this.off('tipHide', __tipHideEvent) // this.off('tipMove', __tipMoveEvent) //this.off('legendchange', __redraw); if (data) { this.data = data; //this._data = parse2MatrixData(data); this.dataFrame = this._initData(this.data, this.opt); }; this.currCoord.resetData(); this.components.forEach(cmp => { cmp.resetData(); }) this.fire({ type: 'resetData' }); this.app.forceRender(); } destroy() { //外部调用适配 this.dispose(); this.fire({ type: 'destroy' }); } dispose() { //销毁默认绑定的事件 this.off('tipShow', __tipShowEvent); this.off('tipHide', __tipHideEvent) this.off('tipMove', __tipMoveEvent) //先销毁坐标系统 if (this.currCoord) { this.currCoord.dispose(); } //销毁组件 this.components.forEach(cmp => { cmp.dispose(); }); this.components = []; //初始化渲染状态 if (this.app) { this.app.resetState(); } //清理渲染数据 if (this.renderer) { this.renderer.dispose(); } //清理事件 if (this.orbitControls) { this.orbitControls.off('start', onStart); this.orbitControls.off('change', this._onChangeBind); this.orbitControls.dispose(); } if (this.app) { this.app._framework.off('renderbefore', this._onRenderBeforeBind); this._onRenderBeforeBind = null; this.app._framework.off('renderafter', this._onRenderAfterBind) this._onRenderAfterBind = null; this.app.dispose(); } if (this.interaction) { this.interaction.off('refresh', this._onChangeBind); this._onChangeBind = null; this.interaction.dispose(); } window.removeEventListener('resize', this._onWindowResizeBind, false); this._onWindowResizeBind = null; this.app = null; this.renderer = null; this.currCoord = null; //todo 内存对象清除优化 } } function onStart() { this.autoRotate = false }; function onChange(e) { this.app.forceRender(); } function onRenderBefore() { if (this.position0.equals(this.object.position) && this.zoom0 === this.object.zoom) { return; } this.saveState(); this.update(); } function onRenderAfter() { this.update(); } global.registerComponent(Chart3d, 'chart', 3); export default Chart3d;