import Model from './Model';
import Series from './Series';
import globalDefault from './globalDefault';
import { clone, merge, each, isString, filter, isArray, map } from '../util';
import { formatTextStyle } from '../util/graphic';
import ComponentModel from './Component';
import dirtyActions from '../action/dirty';
const TYPE = 'global';
/**
* 全局管理调度Model类
*
* @class GlobalModel
* @extends {Model}
*/
class GlobalModel extends Model {
type = TYPE;
static type = TYPE;
_option = {};
_componentsMap = {};
/**
* 设置新的option
*
* @param {Object} newOption
*/
setOption(newOption) {
let { _componentsMap: componentsMap, global } = this;
this._option = merge(newOption, clone(globalDefault), false);
each(newOption, (componentsOption, componentType) => {
if (ComponentModel.hasClass(componentType)) {
let isSeries = componentType === 'series'
componentsMap[componentType] = [];
!isArray(componentsOption) && (componentsOption = [componentsOption]);
each(componentsOption, (componentOption, index) => {
let { id, name, type } = componentOption;
let ComponentModelClass, componentModel;
let classType = isSeries ? type : componentType;
ComponentModelClass = ComponentModel.getClass(classType);
if (ComponentModelClass) {
componentModel = new ComponentModelClass(componentOption, this, this, global, {
id: `${componentType}_${index}_${isSeries ? type : ' '}_${id || name || ' '}`,
index
});
componentModel.type = classType;
componentsMap[componentType][index] = componentModel;
} else {
console.warn(`图表类型${classType}未注册!`);
}
});
} else {
!globalDefault.hasOwnProperty(componentType) && console.warn(`组件类型${componentType}未注册!`);
}
});
this._afterSetOption();
}
/**
* 清除所有model
*/
clear() {
this._componentsMap = {};
}
/**
* setOption后执行
*
* @private
*/
_afterSetOption() {
this._setDepAndSup();
this._setAxisIndex();
this.updateData();
this._setSeriesSelected();
this.dirty();
}
/**
* 设置所有组件/图表的依赖和被依赖
*
* @private
*/
_setDepAndSup() {
if (this.global.isError()) return;
let { _componentsMap: componentsMap, global } = this;
let reg = /^\$(.*)Index$/;
let sup = {};
// 使y轴依赖于x轴
this.eachComponent('series', component => {
let { $axisIndex } = component._option;
if ($axisIndex && $axisIndex.length === 2) {
let axisModel0 = this.getComponentByIndex('axis', $axisIndex[0]);
let axisModel1 = this.getComponentByIndex('axis', $axisIndex[1]);
if (axisModel0.get('xOrY') === 'y') {
axisModel0._option.$axisIndex = $axisIndex[1];
} else {
axisModel1._option.$axisIndex = $axisIndex[0];
}
}
})
this.eachComponent((componentType, component, index) => {
let componentOption = component._option;
let dep = {};
each(componentOption, (value, key) => {
let matched = key.match(reg);
if (matched && typeof value !== 'undefined') {
if (isArray(value)) {
value = map(value, (v) => {
if (isString(v)) {
let matchedModel = this.getComponentById(matched[1], v);
if (matchedModel) {
return matchedModel.index;
} else {
return v;
}
} else {
return v;
}
})
} else {
if (isString(value)) {
let matchedModel = this.getComponentById(matched[1], value);
matchedModel && (value = matchedModel.index);
}
}
dep[matched[1]] = value;
!sup[matched[1]] && (sup[matched[1]] = []);
if (isArray(value)) {
each(value, function (v) {
!sup[matched[1]][v] && (sup[matched[1]][v] = {});
!sup[matched[1]][v][componentType] && (sup[matched[1]][v][componentType] = []);
sup[matched[1]][v][componentType].push(index);
});
} else {
!sup[matched[1]][value] && (sup[matched[1]][value] = {});
!sup[matched[1]][value][componentType] && (sup[matched[1]][value][componentType] = []);
sup[matched[1]][value][componentType].push(index);
}
}
});
component.dependencies = dep;
}, false);
for (let type in sup) {
for (let i = sup[type].length; i--;) {
if (sup[type][i]) {
if (componentsMap[type] && componentsMap[type][i]) {
componentsMap[type][i].supports = sup[type][i];
} else {
let warnInfo = '';
// global.error();
each(sup[type][i], function (indexs, supType) {
warnInfo += `${supType}[${indexs.toString()}], `;
});
warnInfo += `所依赖的${type}[${i}]未在setOption方法中进行配置!`;
console.warn('依赖错误:', warnInfo)
return;
}
}
}
}
}
/**
* 设置gridIndex,在_setSeriesIndex中使用
*
* @private
*/
_setAxisIndex() {
if (this.global.isError()) return;
this.eachComponent('series', (model) => {
let xAxisModel = model.getAxisModel('x');
if (xAxisModel) {
model.axisIndex = xAxisModel.index;
let yAxisModel = model.getAxisModel('y');
if (yAxisModel) {
model.axisIndex += '/' + (model.get('stack') ? yAxisModel.index : '_');
model.axisIndex += '/' + (model.get('overlap') ? yAxisModel.index : '_');
}
}
});
}
/**
* 设置series的selected状态
*
* @private
*/
_setSeriesSelected() {
if (this.global.isError()) return;
this.eachComponent('series', (seriesModel) => {
this.eachComponent('legend', (legendModel) => {
let legendSelected = legendModel.get('selected');
if (legendSelected) {
each(legendSelected, (bool, name) => {
if (seriesModel.get('name') === name) {
seriesModel._option.selected = bool;
} else if (seriesModel.findDataNamed(name)) {
!seriesModel._option.selected && (seriesModel._option.selected = {});
seriesModel._option.selected[name] = bool;
}
});
} else {
let legendData = legendModel.get('data')
each(legendData, (name) => {
if (seriesModel.get('name') === name) {
seriesModel._option.selected = true;
} else if (seriesModel.findDataNamed(name)) {
!seriesModel._option.selected && (seriesModel._option.selected = {});
seriesModel._option.selected[name] = true;
}
});
}
});
})
}
/**
* 更新所有model或依赖某个dataModel的所有model的数据
*
* @param {Object} [dataModel] 要更新的dataModel
*/
updateData(dataModel) {
if (this.global.isError()) return;
let { _componentsMap: componentsMap } = this;
let dataUpdated = [];
if (dataModel) {
if (dataModel.type === 'data' && dataModel.isDirty()) {
dataModel.__update(); // 更新该dataModel
updateSupportsData(componentsMap, dataModel.supports); // 以及依赖该dataModel的所有model的数据
}
} else {
// 更新所有data模块及data模块所依赖的模块
this.eachComponent('data', function (component) {
if (component.isDirty()) {
updateDependencies(componentsMap, component.dependencies, component.type, component.index, true);
component.__update();
}
});
// 更新所有model的数据
this.eachComponent(function (componentType, component) {
componentType !== 'data' && component.isDataDirty() && component.updateData();
});
}
}
/**
* 在更新前执行
*/
beforeUpdate() {
this._setSeriesIndex('bar');
this._setSeriesIndex('line');
this._setStackData();
}
/**
* 设置bar类型图表的barIndex和barTotal,用于后续计算绘图
*
* @private
*/
_setSeriesIndex(type) {
if (this.global.isError()) return;
let gridTotal = {};
let gridIndex = {};
this.eachComponent('series', function (model) {
if (model.type === type && model.axisIndex !== undefined) {
let overlap = model.get('overlap');
let stack = model.get('stack');
let modelAxisIndex = model.axisIndex;
let indexName = `${type}Index`;
let Type = type.slice(0, 1).toUpperCase() + type.slice(1); // 大写首字母
let yAxisModel = model.getAxisModel('y');
if (yAxisModel) {
let $gridIndex = yAxisModel.get('$gridIndex');
let axisIndex = gridIndex[$gridIndex] = gridIndex[$gridIndex] || {};
!gridTotal[$gridIndex] && (gridTotal[$gridIndex] = 0);
if (stack || overlap) {
axisIndex[modelAxisIndex] === undefined &&
(axisIndex[modelAxisIndex] = gridTotal[$gridIndex]++);
} else {
axisIndex[modelAxisIndex] = gridTotal[$gridIndex]++;
}
model[`last${Type}Index`] = model[indexName];
model[indexName] = axisIndex[modelAxisIndex];
}
}
});
this.eachComponent('series', function (model) {
if (model.type === type) {
let yAxisModel = model.getAxisModel('y');
if (yAxisModel) {
let $gridIndex = yAxisModel.get('$gridIndex');
let Type = type.slice(0, 1).toUpperCase() + type.slice(1); // 大写首字母
if (model.get('selected') === false) {
model[`last${Type}Total`] = undefined;
model[`${type}Total`] = undefined;
} else {
model[`last${Type}Total`] = model[`${type}Total`];
model[`${type}Total`] = gridTotal[$gridIndex];
}
}
}
}, false);
}
/**
* 计算设置堆积类型数据
*
* @private
*/
_setStackData() {
if (this.global.isError()) return;
let stacks = {};
let xList = []
this.eachComponent('series', function (model) {
let data = model.getData();
if (!model.get('stack') || model.get('selected') === false || !data) return;
if (!xList.length) {
xList = map(data, d => d[0]);
} else {
let newXList = [];
let i, j;
for (i = 0, j = 0; i < data.length && j < xList.length;) {
let cur = data[i][0];
let last = xList[j];
if (cur < last) {
newXList.push(data[i][0]);
i++;
} else if (cur === last) {
newXList.push(data[i][0]);
i++;
j++;
} else {
newXList.push(xList[j]);
j++;
}
}
while (i < data.length) {
newXList.push(data[i][0]);
i++;
}
while (j < xList.length) {
newXList.push(xList[j]);
j++;
}
xList = newXList;
}
});
this.eachComponent('series', function (model) {
let stackName = model.get('stack');
if (stackName) {
let data = model.getData();
model._stackName = stackName = stackName + '|' + model.axisIndex;
!stacks[stackName] && (stacks[stackName] = []);
if (model.get('selected') === false || !data) {
model.stackTotal = undefined;
return;
}
let stack = stacks[stackName];
let len = stack.length;
let stackData = [];
let newData = [];
let i, j;
for (i = 0, j = 0; i < data.length && j < xList.length;) {
let cur = data[i][0];
let last = xList[j];
if (cur < last) {
newData.push(data[i]);
i++;
} else if (cur === last) {
newData.push(data[i]);
i++;
j++;
} else {
if (i === 0) {
newData.push([xList[j], 0]);
} else {
newData.push([xList[j], data[i - 1][1]]);
}
j++;
}
}
while (j < xList.length) {
if (i === 0) {
newData.push([xList[j], 0]);
} else {
newData.push([xList[j], data[i - 1][1]]);
}
j++;
}
if (len) {
let lastStack = stack[len - 1];
let i, j;
stackData = map(lastStack, function (d, index) {
let startValue = 0;
if (newData[index][1] >= 0) {
for (let k = stack.length; k--;) {
if (stack[k][index][1][1] >= 0) {
startValue = stack[k][index][1][1];
break;
}
}
} else {
for (let k = stack.length; k--;) {
if (stack[k][index][1][1] < 0) {
startValue = stack[k][index][1][1];
break;
}
}
}
return [d[0], [startValue, startValue + (typeof newData[index][1] === 'number' ? newData[index][1] : 0)], newData[index]];
});
} else {
stackData = map(newData, d => [d[0], [0, typeof d[1] === 'number' ? d[1] : 0], d]);
}
stack.push(stackData);
model.lastStackIndex = model.stackIndex;
model.stackIndex = len;
model.stackData = stackData;
}
}, false);
this.eachComponent('series', function (model) {
let stackName = model._stackName;
if (stackName) {
model.lastStackTotal = model.stackTotal;
model.stackTotal = stacks[stackName].length;
}
});
}
/**
* 更新所有被标记为脏的model
*
* @param {bool} force 是否强制更新
*/
update(force) {
if (this.global.isError()) return;
let { _componentsMap: componentsMap } = this;
if (force) {
this.eachComponent(function (componentType, component) {
component.__dirty = true;
});
}
this.eachComponent(function (componentType, component) {
if (component.isDirty() && !component.isPending) {
updateDependencies(componentsMap, component.dependencies, component.type, component.index); // 先更新其依赖
component.__update();
}
});
}
/**
* 更新某个模model及依赖该model的model
*
* @param {Object} model
* @param {Object}
*/
dirty(model, action) {
if (this.global.isError()) return;
let { _componentsMap: componentsMap } = this;
if (model) {
if (model.type === 'data') { // 若是dataModel则直接更新数据
dirtySupports(componentsMap, model, action, 'dataDirty'); // 标记所有依赖该dataModel的model的__dataDirty为脏
this.updateData(model); // 更新该dataModel及依赖该dataModel的数据
} else {
model.isDataDirty() && model.updateData(); // TODO
dirtySupports(componentsMap, model, action); // 标记所有依赖该model的__dirty为脏
}
// model继承自Series的特殊处理
if (model instanceof Series && model.get('$axisIndex')) {
let xAxisModel = model.getAxisModel('x');
let yAxisModel = model.getAxisModel('y');
let axisAction = {
type: dirtyActions.seriesWillUpdate,
payload: {
series: model,
action
}
};
!xAxisModel.isDirty() && xAxisModel.dirty(axisAction);
yAxisModel && !yAxisModel.isDirty() && yAxisModel.dirty(axisAction);
}
}
this.__dirty = true;
}
/**
* 遍历所有component类型
*
* @param {Function} callback 回调函数
* @param {any} context
*/
eachComponentType(callback, context) {
let { _componentsMap: componentsMap } = this;
each(componentsMap, (components, componentType) => {
callback.call(context, componentType, filterHideSeries(components));
});
}
/**
* 遍历所有component
*
* @param {string} mainType 要遍历的类型,不传将直接遍历所有component
* @param {Function} callback 回调函数
* @param {boolean} filterHide 是否过滤隐藏的series
* @param {any} context
*/
eachComponent(mainType, cb, filterHide, context) {
let { _componentsMap: componentsMap } = this;
if (typeof mainType === 'function') {
context = filterHide;
filterHide = typeof cb === 'undefined' ? true : cb;
cb = mainType;
each(componentsMap, (components, componentType) => {
each(filterHide ? filterHideSeries(components) : components, (component, index) => {
cb.call(context, componentType, component, index);
});
});
} else if (isString(mainType)) {
typeof filterHide === 'undefined' && (filterHide = true);
each(filterHide ? filterHideSeries(componentsMap[mainType]) : componentsMap[mainType], cb, context);
}
}
/**
* 通过组件类型和index获取model
*
* @param {string} type 组件类型名称
* @param {number|string} index 组件的index或id
* @returns {Object} model
*/
getComponentByIndex(type, index) {
if (typeof index === 'string') {
return this.getComponentById(type, index);
} else {
return this._componentsMap[type] && this._componentsMap[type][index];
}
}
/**
* 通过name获取model
*
* @param {string} type 组件类型
* @param {string} name 组件name
* @param {boolean} [filterHide=false] 是否过滤隐藏的series
* @returns {Object[]} model数组
*/
getComponentByName(type, name, filterHide = false) {
return this.getComponent(type, seriesModel => seriesModel.get('name') === name, filterHide);
}
/**
* 通过id获取model
*
* @param {string} type 组件类型
* @param {string} id 组件id
* @returns {Object[]} model数组
*/
getComponentById(type, id) {
return this.getComponent(type, seriesModel => seriesModel.get('id') === id, false)[0];
}
/**
* 通过axis的index获取相应series model
*
* @param {any} index axis的index
* @param {boolean} [filterHide=true] 是否过滤隐藏的series
* @returns {Object[]} series model数组
*/
getSeriesByAxis(index, filterHide = true) {
return filter(filterHide ? filterHideSeries(this._componentsMap.series) : this._componentsMap.series, item => {
let $axisIndex = item.get('$axisIndex');
if ($axisIndex) {
for (let i = 0; i < $axisIndex.length; i++) {
let axisModel = this.getComponentByIndex('axis', $axisIndex[i]);
if (axisModel && axisModel.index === index) {
return true;
}
}
}
});
}
/**
* 获取model
*
* @param {string} type
* @param {any} [func=m => m] 筛选函数
* @param {boolean} [filterHide=true] 是否过滤隐藏的series
* @returns {Object[]} model数组
*/
getComponent(type, func = m => m, filterHide = true) {
return filter(filterHide ? filterHideSeries(this._componentsMap[type]) : this._componentsMap[type], func);
}
/**
* 通过name获取series
*
* @param {string} name series的name
* @param {boolean} [filterHide=false] 是否过滤隐藏的series
* @returns {Object[]} series model数组
*/
getSeriesByName(name, filterHide = false) {
return this.getComponent('series', seriesModel => seriesModel.get('name') === name, filterHide);
}
/**
* 获取格式化后的text
*
* @param {Object} text
* @returns {Object} new text
*/
getFormattedText(text) {
let style = text.style ? text.style : text;
let globalTextStyle = this.get('textStyle');
(style.color && !style.fill) && (style.fill = style.color);
each(globalTextStyle, function (value, key) {
typeof style[key] === 'undefined' && (style[key] = value);
});
(style.color && !style.fill) && (style.fill = style.color);
style = formatTextStyle(style);
return text;
}
}
function filterHideSeries(seriesModes) {
return filter(seriesModes, (seriesModel) => !(seriesModel instanceof Series) || seriesModel.get('selected') !== false);
}
/**
* 递归更新依赖
*
* @param {any} componentsMap
* @param {any} dependencies
* @private
*/
function updateDependencies(componentsMap, dependencies, componentType, componentIndex, needUpdateData) {
each(dependencies, function (index, type) {
if (isArray(index)) {
for (let i = index.length; i--;) {
let component = componentsMap[type][index[i]];
if (component && component.isDirty()) {
updateDependencies(componentsMap, component.dependencies, component.type, component.index, needUpdateData);
needUpdateData && component.updateData();
!component.isPending && component.__update();
}
}
} else {
let component = componentsMap[type][index];
if (component.isDirty()) {
updateDependencies(componentsMap, component.dependencies, component.type, component.index, needUpdateData);
needUpdateData && component.updateData();
!component.isPending && component.__update();
}
}
})
}
/**
* 递归设置被依赖model为dirty或dataDirty
*
* @param {any} componentsMap
* @param {any} supports
* @private
*/
function dirtySupports(componentsMap, model, action, updateType = 'dirty') {
let supports = model.supports;
let _action = {
type: dirtyActions.dependentWillUpdate,
payload: {
dependent: model,
action: action
}
};
each(supports, (indexs, type) => {
for (let i = indexs.length; i--;) {
let component = componentsMap[type][indexs[i]];
if (component && !component[updateType === 'dirty' ? 'isDirty' : 'isDataDirty']()) { // 且未被标记为dirty
component[updateType](_action) && dirtySupports(componentsMap, component, _action, updateType);
}
}
})
}
/**
* 递归更新被依赖model的数据
*
* @param {any} componentsMap
* @param {any} supports
* @private
*/
function updateSupportsData(componentsMap, supports) {
each(supports, (indexs, type) => {
for (let i = indexs.length; i--;) {
let component = componentsMap[type][indexs[i]];
if (component && component.isDataDirty()) {
if (component.type === 'data') {
component.__update();
updateSupportsData(componentsMap, component.supports);
} else {
component.updateData();
}
}
}
})
}
export default GlobalModel;