/** @module D3Charts */
import * as zrender from 'zrender/src/zrender';
import View from './view/View';
import ChartView from './view/Chart';
import ComponentView from './view/Component';
import ComponentModel from './model/Component';
import SeriesModel from './model/Series';
import * as util from './util';
import log from './util/track';
import * as graphic from './util/graphic';
import * as symbol from './util/symbol';
import GlobalModel from './model/Global';
import Eventful from 'zrender/src/mixin/Eventful';
import * as colorTool from 'zrender/src/tool/color';
import env from 'zrender/src/core/env';
import * as d3Time from 'd3-time';
import * as action from './action/event';
let { isString, each, mixin, findIndex, isFunction, isArray } = util;
const PREFIX = '__';
let instances = {}; // init生成的实例
let idBase = new Date() - 0;
let event = new Eventful();
let stat = {};
function doStat(option) {
each(option, function(item, name) {
if (name === 'series') {
if (SeriesModel.hasClass && SeriesModel.hasClass(name)) {
if (isArray(item)) {
each(item, function(seriesItem) {
if (!stat[seriesItem.type]) {
stat[seriesItem.type] = 0;
}
stat[seriesItem.type]++;
});
} else {
if (!stat[item.type]) {
stat[item.type] = 0;
}
stat[item.type]++;
}
}
} else {
if (ComponentModel.hasClass(name)) {
if (!stat[name]) {
stat[name] = 0;
}
if (isArray(item)) {
stat[name] += item.length;
} else {
stat[name]++;
}
}
}
});
}
function statToString() {
let str = '';
each(stat, function(value, name) {
str += `${name}-${value}|`;
});
str = str.slice(0, -1);
return str;
}
/**
* Charts类
*
* @class Charts
* @extends {Eventful}
*/
class Charts extends Eventful {
id = PREFIX + idBase++;
// view视图存储对象
_viewMap = {};
// 全局global model
_model = undefined;
// 是否开启编辑
isEnableEdit = false;
// 是否开启绘图
isEnableSketch = false;
constructor(dom, theme, opts = {}) {
super(...arguments);
this._dom = isString(dom) ? document.getElementById(dom) : dom;
let zr = (this._zr = zrender.init(this._dom, opts));
opts.backgroundColor && zr.setBackgroundColor(opts.backgroundColor);
this._model = new GlobalModel(null, null, null, this);
this._actionQueue = [];
initEvents.call(this);
// 每一帧
zr.animation.on(
'frame',
function() {
if (this.isError()) return;
try {
let actionQueue = this._actionQueue;
// 事件在每帧进行调用
while (actionQueue.length) {
let action = actionQueue.shift();
this.trigger(action[0], action[1]);
}
if (this._model.isDirty()) {
// 根据是否为脏来进行更新
this.update.call(this);
}
} catch (e) {
this.error();
throw e;
}
},
this
);
}
/**
* 设置参数
*
* @param {Object} option 配置参数
* @param {Object} [lazyUpdate=true] 配置参数
* @returns this
*/
setOption(option, lazyUpdate = true) {
doStat(option);
this.clear();
this._model.setOption(option);
this.toggleEdit(this.isEnableEdit);
prepareView.call(this);
this.__AFTER_SET_OPTION = true;
if (!lazyUpdate) {
this.update();
}
return this;
}
/**
* 调用globalModel的更新对标记为脏的model进行更新,并对有更新的model对应的view重新进行渲染
*
* @param {boolean} [force] 是否强制更新
*/
update(force) {
this._model.__update(force);
doRender.call(this);
let backgroundColor = this._model.get('backgroundColor');
backgroundColor && this._zr.setBackgroundColor(backgroundColor);
}
/**
* 清除所有view、model以及事件
*
* @returns this
*/
clear() {
let zr = this._zr;
// 清除所有model
this._model.clear();
// 删除所有视图
each(this._viewMap, function(view) {
view.remove();
zr.remove(view.group);
});
this._viewMap = {};
// 解绑全部事件
this.off();
this.error(false);
return this;
}
/**
* 标记错误状态
*
* @param {bool} [bool] true或false
* @private
*/
error(bool) {
this._isError = typeof bool !== 'undefined' ? bool : true;
}
/**
* 是否错误状态
*
* @returns {boolean} 是否是错误状态
*/
isError() {
return this._isError;
}
/**
* Resize the canvas.
* Should be invoked when container size is changed
*
* @param {Object} [opts]
* @param {number|string} [opts.width] Can be 'auto' (the same as null/undefined)
* @param {number|string} [opts.height] Can be 'auto' (the same as null/undefined)
*/
resize(opts) {
this._zr.resize(opts);
this.update(true);
}
/**
* 触发事件
*
* @param {Object} model 组件model
* @param {string} type 事件名称
* @param {any} payload
* @param {boolean} [throttle=true]
*/
dispatchAction(model, type, payload, throttle = true) {
if (throttle) {
let index = findIndex(this._actionQueue, function(ele) {
return ele[0] === model.id && ele[1] === type;
});
index > -1 && this._actionQueue.splice(index, 1); // 过滤了之前的相同模块的相同事件,仅触发最新的事件
}
this._actionQueue.push([`${model.id}_${type}`, payload]);
}
/**
* 注册事件
*
* @param {Object} model 组件model
* @param {string} type 事件名称
* @param {function} callback 回调函数
* @param {any} context
*/
registerAction(model, type, callback, context) {
this.on(`${model.id}_${type}`, callback, context || model);
}
/**
* Get container width
*
* @returns {number} width of container
*/
getWidth() {
return this._zr.getWidth();
}
/**
* Get container height
*
* @returns {number} height of container
*/
getHeight() {
return this._zr.getHeight();
}
/**
* 获取指定的model,或者globalModel(无参数时)
*
* @param {string} [type] 组件名称
* @param {number|string|Function} [index] 对应组件的index、name或方法选取
* @returns {Object} 返回指定的model
*/
getModel(type, index) {
if (arguments.length === 2) {
if (typeof index === 'number') {
return this._model.getComponentByIndex(type, index);
} else if (isString(index)) {
return this._model.getComponentById(type, index);
} else if (isFunction(index)) {
return this._model.getComponent(type, index, false);
}
} else if (arguments.length === 1) {
return this._model._componentsMap[type];
} else {
return this._model;
}
}
/**
* 获取指定的dataModel
*
* @deprecated
* @param {number} index dataModel的序数
* @returns data model
*/
getDataModel(index) {
return this._model.getComponentByIndex('data', index);
}
/**
* 获取zr实例
*
* @returns instance of zr
*/
getZr() {
return this._zr;
}
/**
* 获取dom
*
* @returns dom
*/
getDom() {
return this._dom;
}
/**
* 获取model对应的view
*
* @param {Object} componentModel 组件model
* @returns {Object} 返回model对应的view
*/
getViewOfComponentModel(componentModel) {
return this._viewMap[componentModel.id];
}
hideSeries(index) {
let seriesModel = this._model.getComponentByIndex('series', index);
if (seriesModel) {
seriesModel.set('selected', false);
seriesModel.__dirty = false;
this._viewMap[seriesModel.id].remove();
}
}
showSeries(index) {
let seriesModel = this._model.getComponentByIndex('series', index);
if (seriesModel) {
seriesModel.set('selected', true);
}
}
getRenderedCanvas(opts) {
if (!env.canvasSupported) {
return;
}
opts = opts || {};
opts.pixelRatio = opts.pixelRatio || 1;
opts.backgroundColor = opts.backgroundColor;
this.stopAnimation();
return this._zr.painter.getRenderedCanvas(opts);
}
/**
* 停止所有动画
*/
stopAnimation() {
let zr = this._zr;
let list = zr.storage.getDisplayList();
// Stop animations
each(list, function(el) {
el.stopAnimation(true);
});
}
/**
* 生成dataUrl
*
* @param {Object} [opts]
* @param {number|string} [opts.width] Can be 'auto' (the same as null/undefined)
* @param {number|string} [opts.height] Can be 'auto' (the same as null/undefined)
* @param {string} [opts.type] jpg | png
* @param {string} [opts.backgroundColor]
* @returns data url
*/
getDataUrl(opts) {
let lastSize = { width: this.getWidth(), height: this.getHeight() };
opts = opts || {};
if (opts.size) {
this.resize(opts.size);
this.stopAnimation();
}
let url = this.getRenderedCanvas(opts).toDataURL(
'image/' + ((opts && opts.type) || 'png')
);
if (opts.size) {
this.resize(lastSize);
this.stopAnimation();
}
return url;
}
/**
* 切换编辑状态
*
* @param {boolean} [bool] true或false
*/
toggleEdit(bool) {
bool === undefined && (bool = !this.isEnableEdit);
this._model.eachComponent(function(name, model) {
model.set('enableEdit', bool);
});
this.isEnableEdit = bool;
}
}
// let updateMethods = {
// update: function () {
// this._model.__update();
// doRender.call(this);
// },
// prepareAndUpdate: function () {
// prepareView.call(this);
// updateMethods.update.call(this);
// }
// }
function prepareOneView(model) {
let { _viewMap: viewMap, _model: globalModel, _zr: zr } = this;
let _View, view;
_View = View.getClass(model.type);
if (_View) {
view = new _View(model, globalModel, this);
view.type = model.type;
viewMap[model.id] = view;
zr.add(view.group);
}
return view;
}
// 准备视图
function prepareView() {
this._model.eachComponent((componentType, model) => {
prepareOneView.call(this, model);
});
}
// 进行渲染
function doRender() {
let { _viewMap: viewMap, _model: globalModel } = this;
globalModel.eachComponent((componentType, model) => {
let view = viewMap[model.id];
if (!view) {
view = prepareOneView.call(this, model);
}
if (view) {
view.__render(model, globalModel, this);
updateZ(model, view);
}
});
if (this.__AFTER_SET_OPTION) {
this.__AFTER_SET_OPTION = false;
this.trigger('RENDER_END_AFTER_SET_OPTION');
this.trigger('VIEW_DID_RENDER_AFTER_SET_OPTION');
}
this.trigger('RENDER_END');
this.trigger('VIEW_DID_RENDER');
}
// 统一更新组件的z和zlevel
function updateZ(model, view) {
var z = model.get('z');
var zlevel = model.get('zlevel');
view.group.traverse(function(el) {
if (el.type !== 'group') {
!isNaN(z) && (el.z = z);
!isNaN(zlevel) && (el.zlevel = zlevel);
}
});
}
const MOUSE_EVENT_NAMES = [
'click',
'dblclick',
'mouseover',
'mouseout',
'mousemove',
'mousedown',
'mouseup',
'globalout',
'mousewheel',
'pinch'
];
// 代理zrender事件
function initEvents() {
each(MOUSE_EVENT_NAMES, eveName => {
this._zr.on(eveName, e => {
this.trigger(eveName, e);
});
});
}
/**
* 初始化Charts
*
* @param {string|dom} dom 接收dom的id或者dom
* @returns {Object} 返回{@link module:D3Charts~Charts}实例
*/
function init(dom, opts) {
let chart = new Charts(dom, undefined, opts);
instances[chart.id] = chart;
return chart;
}
/**
* 注册Model
*
* @param {Object} Model 要注册的Model
*/
function registerModel(Model_) {
ComponentModel.registerClass(Model_, Model_.type);
}
/**
* 注册view
*
* @param {Object} View Model对应的View
*/
function registerView(View_) {
View.registerClass(View_, View_.type);
}
/**
* 注册Model及View
*
* @param {Object} Model 要注册的Model
* @param {Object} View Model对应的View
*/
function registerComponent(Model_, View_) {
Model_ && registerModel(Model_);
View_ && registerView(View_);
if (!Model_ && View_) {
registerModel(
class extends ComponentModel {
static type = View_.type;
}
);
}
}
function initMap(dom) {
var map = document.createElement('div');
map.id = dom;
map.width = '100%';
map.height = '600px';
return map;
}
let tracking = true;
/**
* 是否开启统计
*
* @param {boolean} bool true或false
*/
function track(bool) {
tracking = bool;
}
/**
* version of d3-charts
*
* @static
* @constant
*/
const VERSION = '0.3.4';
setTimeout(function() {
if (tracking) {
log({
_sid: 'iwencai_datav_d3charts.load',
id: 'iwencai_datav_d3charts.load',
version: VERSION,
widgetN: statToString(),
fid: 'iwencai_datav'
});
}
}, 3e3);
export {
VERSION,
init,
track,
event,
initMap,
registerModel,
registerView,
registerComponent,
/**
* 绘图引擎zrender
*
* @see https://ecomfe.github.io/zrender-doc/public/
*/
zrender,
/**
* 工具函数
*
* @see module:util/index
*/
util,
/**
* 图形相关函数
*
* @see module:util/graphic
*/
graphic,
colorTool,
symbol,
/**
* @see ChartView
*/
ChartView,
/**
* @see ComponentView
*/
ComponentView,
d3Time,
/**
* @see SeriesModel
*/
SeriesModel as ChartModel,
/**
* @see ComponentModel
*/
ComponentModel,
action
};