UNPKG

chartx3d

Version:

Data Visualization Chart Library

590 lines (467 loc) 18.9 kB
import { InertialSystem } from './inertial'; import { Vector3, Box3, Matrix3, Matrix4, Math as _Math, AmbientLight, PointLight, DirectionalLight } from 'mmgl/src/index' import { Cartesian3DUI } from '../../components/cartesian3dUI/index' import { _, dataSection, } from 'mmvis'; import { AxisAttribute } from './model/axisAttribute'; /** note: * 获取所有的配置信息,取去配置中影响布局的相关参数 * coord{ * xAsix:{} * yAxis:[] * zAxis:{} * } * * graphs{} * * makeline其他组件 * * 通过Data和相关的配置,给出各个坐标轴的DataSection,计算出各个轴上数据点对应的位置 * * ***/ const DEFAULT_AXIS = 'default_axis_for_Y'; class Cartesian3D extends InertialSystem { constructor(root) { super(root); this.type = "Cartesian3D"; this.offset = new Vector3(0, 0, 0); this._coordUI = null; this.group.name = 'cartesian3dSystem'; } setDefaultOpts(opts) { var me = this; this._zSection = []; me.coord = { xAxis: { //波峰波谷布局模型,默认是柱状图的,折线图种需要做覆盖 layoutType: "rule", //"peak", //默认为false,x轴的计量是否需要取整, 这样 比如某些情况下得柱状图的柱子间隔才均匀。 //比如一像素间隔的柱状图,如果需要精确的绘制出来每个柱子的间距是1px, 就必须要把这里设置为true posParseToInt: false }, yAxis: [], //y轴至少有一个 zAxis: { enabled: true, field: '', layoutType: "rule", // depth: 50 //最大深度是1000 } }; opts = _.clone(opts); //规范Y轴的定义,采用数组形式,如果没有定义就初始化为空数组 if (opts.coord.yAxis) { var _nyarr = []; _.each(_.flatten([opts.coord.yAxis]), function (yopt, index) { //标记定义的Y轴信息是否在绘图中使用 yopt._used = false; //如果坐标轴没有置顶名称,第一个为默认坐标轴,其余的将被舍弃 if (_.isEmpty(yopt.name)) { if (index == 0) { yopt.name = DEFAULT_AXIS; } else { return; } } _nyarr.push(_.clone(yopt)); }); opts.coord.yAxis = _nyarr; } else { opts.coord.yAxis = []; } let getYaxisInfo = (name) => { let _opt = null; if (opts.coord.yAxis) { _.each(_.flatten([opts.coord.yAxis]), function (yopt) { if (yopt.name == name) { yopt._used = true; _opt = yopt } }) } return _opt; } //根据opt中得Graphs配置,来设置 coord.yAxis if (opts.graphs) { //有graphs的就要用找到这个graphs.field来设置coord.yAxis for (var i = 0; i < opts.graphs.length; i++) { var graphs = opts.graphs[i]; this._zSection.push(graphs.field.toString()); if (graphs.type == "bar") { //如果graphs里面有柱状图,那么就整个xAxis都强制使用 peak 的layoutType me.coord.xAxis.layoutType = "peak"; me.coord.zAxis.layoutType = "peak"; } if (graphs.field) { //没有配置field的话就不绘制这个 graphs了 //根据graphs中的数据整理y轴的数据 let _axisName = graphs.yAxisName; if (!graphs.yAxisName) { //没有指定坐标轴的名称,取默认轴 _axisName = DEFAULT_AXIS } //增加Y轴 let _tAxis = getYaxisInfo(_axisName) if (!_tAxis) { let _yAxisNew = { field: [], name: _axisName, _used: true } if (_.isArray(graphs.field)) { _yAxisNew.field = _yAxisNew.field.concat(graphs.field); } else { _yAxisNew.field.push(graphs.field) } opts.coord.yAxis.push(_yAxisNew); } else { if (_.isEmpty(_tAxis.field)) { _tAxis.field = []; } if (_.isArray(_tAxis.field)) { if (_.isArray(graphs.field)) { _tAxis.field = _tAxis.field.concat(graphs.field); } else { _tAxis.field.push(graphs.field); } } else { if (_.isArray(graphs.field)) { _tAxis.field = [_tAxis.field].concat(graphs.field); } else { _tAxis.field = [_tAxis.field].push(graphs.field); } } } } else { //在,直角坐标系中,每个graphs一定要有一个field设置,如果没有,就去掉这个graphs opts.graphs.splice(i--, 1); } } }; //初始化Y轴的相关参数 for (var i = 0; i < opts.coord.yAxis.length; i++) { if (!opts.coord.yAxis[i].layoutType) { opts.coord.yAxis[i].layoutType = 'proportion'; //默认布局 } //没有field的Y轴是无效的配置 if (_.isEmpty(opts.coord.yAxis[i].field) || opts.coord.yAxis[i]._used == false) { opts.coord.yAxis.splice(i--, 1); } if (opts.coord.yAxis[i]) { delete opts.coord.yAxis[i]._used; } }; return opts; } init() { //先计算一次空间范围供计算坐标轴宽高使用 this.getBoundbox(); let { x: widith, y: height, z: depth } = this.boundbox.getSize(); let opt = _.clone(this.coord); this.xAxisAttribute = new AxisAttribute(opt.xAxis, this.getAxisDataFrame(opt.xAxis.field)); //new AxisAttribute(this._root); this.xAxisAttribute.setDataSection(); this.xAxisAttribute.setAxisLength(widith); //默认Y轴 this.yAxisAttribute = Object.create(null); let axisCount = 0; opt.yAxis.forEach((yopt) => { let _yAxisAttr = this.yAxisAttribute[yopt.name]; if (!_yAxisAttr) { _yAxisAttr = new AxisAttribute(yopt, this.getAxisDataFrame(yopt.field)) //new AxisAttribute(this._root); this.yAxisAttribute[yopt.name] = _yAxisAttr; _yAxisAttr.setDataSection(); _yAxisAttr.setAxisLength(height); axisCount++; } else { console.error('Y轴设置报错了') } }); //如果是多Y轴的情况 if (axisCount > 1) { AxisAttribute.resetDataSection(this.yAxisAttribute); } //Z轴如果设置了filed,按照数据轴的正常逻辑进行,否则Z轴按照Graphs配置中 //的索引显示对应的名称 if (!this.isExistZAxisField()) { let _sectionZ = []; this._root.opt.graphs.forEach((yOps) => { _sectionZ.push(yOps.field.toString()); }) this.zAxisAttribute = new AxisAttribute(opt.zAxis, [[_sectionZ]]); // this.zAxisAttribute.setDataSection(); // this.zAxisAttribute.calculateProps(); } else { this.zAxisAttribute = new AxisAttribute(opt.zAxis, this.getAxisDataFrame(opt.zAxis.field)); } this.zAxisAttribute.setDataSection() this.zAxisAttribute.setAxisLength(depth); this.fieldsMap = this._setFieldsMap(); this.addLights(); this.bindEvent(); } _setFieldsMap() { var me = this; var fieldInd = 0; let opt = _.clone(this.coord); let getTheme = this._root.getTheme.bind(this._root); let _set = (fields) => { let _zField = this.isExistZAxisField(); if (!fields) { var yAxis = opt.yAxis; if (!_.isArray(yAxis)) { yAxis = [yAxis]; }; fields = []; _.each(yAxis, function (item, i) { if (item.field) { fields = fields.concat(item.field); }; }); }; if (_.isString(fields)) { fields = [fields]; }; var clone_fields = _.clone(fields); for (var i = 0, l = fields.length; i < l; i++) { if (_zField) { //三维数据 let zDataSection = this.zAxisAttribute.getDataSection(); let len = zDataSection.length; zDataSection.forEach((zf, zInd) => { if (_.isString(fields[i])) { clone_fields[i * len + zInd] = { field: fields[i], enabled: true, // yAxis: me._getYaxisOfField(fields[i]), color: getTheme(fieldInd), ind: fieldInd++, zInd: zInd, [_zField]: zf } } else { clone_fields[i] = _set(fields[i], fieldInd); } }); } else { //二维数据 if (_.isString(fields[i])) { clone_fields[i] = { field: fields[i], enabled: true, // yAxis: me._getYaxisOfField(fields[i]), color: getTheme(fieldInd), ind: fieldInd++, zInd: 0 } } if (_.isArray(fields[i])) { clone_fields[i] = _set(fields[i], fieldInd); } } }; return clone_fields; }; return _set(); } isExistZAxisField() { if (this.coord.zAxis && !_.isEmpty(this.coord.zAxis.field) && this.coord.zAxis.layoutType !== "proportion") { return this.coord.zAxis.field } return false; } getYAxis(name = DEFAULT_AXIS) { let yAxisAttr = this.yAxisAttribute[name]; //如果没有指定名称,通知默认名称不存在,取第一个配置的Name if (!yAxisAttr) { name = this.coord.yAxis[0].name; yAxisAttr = this.yAxisAttribute[name]; } let yOpts = _.clone(yAxisAttr._opt); return { attr: yAxisAttr, opts: yOpts } } getBoundbox() { //笛卡尔坐标的原点默认为左下方 let baseBoundbox = super.getBoundbox(); let offset = this.offset.clone(); this.baseBoundbox = baseBoundbox; this.boundbox.min.set(0, 0, 0); this.boundbox.max.set(baseBoundbox.max.x - baseBoundbox.min.x - offset.x, baseBoundbox.max.y - baseBoundbox.min.y - offset.y, baseBoundbox.max.z - baseBoundbox.min.z - offset.z ) //如果指定了Z轴的宽度就不采用默认计算的宽度 if (this._root.opt.coord.zAxis && this._root.opt.coord.zAxis.depth) { this.boundbox.max.z = this._root.opt.coord.zAxis.depth; } this.center = this.boundbox.getCenter(); this.center.setZ(-this.center.z); return this.boundbox; } //粗略计算在原点位置的世界线段的长度与屏幕像素的长度比 getRatioPixelToWorldByOrigin(_origin) { let baseBoundbox = super.getBoundbox(); if (_origin === undefined) { _origin = baseBoundbox.min.clone(); _origin.setZ(baseBoundbox.max.z); } let ratio = this._root.renderView.getVisableSize(_origin).ratio; return ratio; } //更新坐标原点 updateOrigin(offset) { this.offset = offset.clone(); this.boundbox = this.getBoundbox(); this.setWorldOrigin(); this.updatePosition(); } // updatePosition() { // //更新相机姿态 // let center = this.center.clone(); // center = this._getWorldPos(center); // let _renderView = this._root.renderView; // let _camera = _renderView._camera; // //相机默认的旋转角度 // let dist = _camera.position.distanceTo(center); // let phi = _Math.degToRad(_renderView.controls.alpha); //(90-lat)*(Math.PI/180), // let theta = _Math.degToRad(_renderView.controls.beta); // (lng+180)*(Math.PI/180), // let y = dist * Math.sin(phi); // let temp = dist * Math.cos(phi); // let x = temp * Math.sin(theta); // let z = temp * Math.cos(theta); // //平移实现以中心点为圆心的旋转结果 // let newPos = new Vector3(x, y, z); // newPos.add(center); // _camera.position.copy(newPos); // //相机朝向中心点 // _camera.lookAt(center); // //orbite target position // this._root.orbitControls.target.copy(center); // } addLights() { //加入灯光 var ambientlight = new AmbientLight(0xffffff, 0.5); // soft white light this._root.rootStage.add(ambientlight); let center = this.center.clone(); center = this._getWorldPos(center); //center.setY(0); let dirLights = []; let intensity = 0.5; let lightColor = 0xFFFFFF; let position = new Vector3(-1, -1, 1); let pointLight = []; pointLight[0] = new PointLight(lightColor, intensity); position = new Vector3(-1, 1, 1); position.multiplyScalar(10000); pointLight[0].position.copy(position); this._root.rootStage.add(pointLight[0]); pointLight[1] = new PointLight(lightColor, intensity); position = new Vector3(1, 1, 1); position.multiplyScalar(10000); pointLight[1].position.copy(position); this._root.rootStage.add(pointLight[1]); pointLight[2] = new PointLight(lightColor, intensity); position = new Vector3(-1, 1, -1); position.multiplyScalar(10000); pointLight[2].position.copy(position); this._root.rootStage.add(pointLight[2]); pointLight[3] = new PointLight(lightColor, intensity); position = new Vector3(1, 1, -1); position.multiplyScalar(1000); pointLight[3].position.copy(position); this._root.rootStage.add(pointLight[3]); } setWorldOrigin() { let baseBoundbox = super.getBoundbox(); let offset = this.offset.clone(); let pos = baseBoundbox.min.clone(); pos.setZ(baseBoundbox.max.z); pos.add(offset); this.group.position.copy(pos); } getOrigin() { return this.origin.clone(); } initCoordUI() { this._coordUI = new Cartesian3DUI(this); this.group.add(this._coordUI.group); } drawUI() { super.drawUI(); this._coordUI.draw(); } getXAxisPosition(data) { return this.xAxisAttribute.getPosOfVal(data); } getYAxisPosition(data, yAxisAttribute) { return yAxisAttribute.getPosOfVal(data); } getZAxisPosition(data) { return this.zAxisAttribute.getPosOfVal(data); } getCeilSize() { let ceil = new Vector3(); let size = this.boundbox.getSize(); let dataLenX = this.xAxisAttribute.getDataSection().length; let dataLenY = this.getYAxis().attr.getDataSection().length; let dataLenZ = this.zAxisAttribute.getDataSection().length; // dataLenX = dataLenX - 1 > 0 ? dataLenX : 3; // dataLenY = dataLenY - 1 > 0 ? dataLenY : 3; // dataLenZ = dataLenZ - 1 > 0 ? dataLenZ : 3; if (this.coord.xAxis.layoutType == 'peak') { ceil.setX(size.x / (dataLenX)); } else { ceil.setX(size.x / (dataLenX + 1)); } ceil.setY(size.y / (dataLenY + 1)); if (this.coord.zAxis.layoutType == 'peak') { ceil.setZ(size.z / (dataLenZ)); } else { ceil.setZ(size.z / (dataLenZ + 1)); } return ceil; } bindEvent() { let interaction = this._root.interaction; if (interaction) { interaction.on('move', (e) => { let dx = e.event.offsetX; let dy = e.event.offsetY; let pos = this.screenToWorld(dx, dy); // console.log(dx, dy, pos); }) } } dispose() { this._coordUI.dispose(); } resetData() { let opt = _.clone(this.coord); this.xAxisAttribute.resetDataOrg(this.getAxisDataFrame(opt.xAxis.field)); this.xAxisAttribute.setDataSection(); this.xAxisAttribute.calculateProps(); opt.yAxis.forEach((yopt) => { let _yAxisAttr = this.yAxisAttribute[yopt.name]; if (_yAxisAttr) { _yAxisAttr.resetDataOrg(this.getAxisDataFrame(yopt.field)); _yAxisAttr.setDataSection(); _yAxisAttr.calculateProps(); } }) if (_.isEmpty(opt.zAxis.field)) { let _sectionZ = []; this._root.opt.graphs.forEach((yOps) => { _sectionZ.push(yOps.field.toString()); }) this.zAxisAttribute.resetDataOrg([[_sectionZ]]); } else { this.zAxisAttribute.resetDataOrg(this.getAxisDataFrame(opt.zAxis.field)); } this.zAxisAttribute.setDataSection(); this.zAxisAttribute.calculateProps(); //UI组件resetData this._coordUI.resetData(); } } export { Cartesian3D, DEFAULT_AXIS };