From 474565445fcfd0dfe32f4d2b2f4f54a695de7e1b Mon Sep 17 00:00:00 2001 From: pah100 Date: Fri, 25 Sep 2015 13:37:03 +0800 Subject: [PATCH] fix topology bug --- src/model/Component.js | 5 +- src/model/Global.js | 32 ++++++++----- src/util/component.js | 84 ++++++++++++++++++++------------- test/ut/spec/model/Component.js | 60 ++++++++++++++++++----- 4 files changed, 122 insertions(+), 59 deletions(-) diff --git a/src/model/Component.js b/src/model/Component.js index 8ee434a54..e4a22a138 100644 --- a/src/model/Component.js +++ b/src/model/Component.js @@ -97,7 +97,10 @@ define(function(require) { zrUtil.each(ComponentModel.getClassesByMainType(componentType), function (Clazz) { arrayPush.apply(deps, Clazz.prototype.dependencies || []); }); - return deps; + // Ensure main type + return zrUtil.map(deps, function (type) { + return ComponentModel.parseComponentType(type).main; + }); } return ComponentModel; diff --git a/src/model/Global.js b/src/model/Global.js index 7e3e04d54..acce4a8a7 100644 --- a/src/model/Global.js +++ b/src/model/Global.js @@ -97,7 +97,7 @@ define(function (require) { var option = this.option; var componentsMap = this._componentsMap; - var componentTypes = []; + var newCptTypes = []; // 如果不存在对应的 component model 则直接 merge zrUtil.each(newOption, function (componentOption, componentType) { @@ -112,13 +112,16 @@ define(function (require) { } } else { - componentTypes.push(componentType); + newCptTypes.push(componentType); } }); - // FIXME 这里 componentTypes 是新的 Option,在依赖处理上是否会有问题 // FIXME OPTION 同步是否要改回原来的 - ComponentModel.topologicalTravel(componentTypes, function (componentType, dependencies) { + ComponentModel.topologicalTravel( + newCptTypes, ComponentModel.getAllClassMainTypes(), visitComponent, this + ); + + function visitComponent(componentType, dependencies) { var newCptOptionList = newOption[componentType]; // Normalize @@ -158,7 +161,7 @@ define(function (require) { this._componentsIdMap[componentModel.uid] = componentModel; } } - }, this); + } // Backup data zrUtil.each(componentsMap, function (components, componentType) { @@ -177,7 +180,10 @@ define(function (require) { // Use determinSubType only when there is no existComponent. : ComponentModel.determineSubType(componentType, newCptOption); - return newCptOption.type = subType; + // Dont make option.type === undefined, otherwise some problem will occur in merge. + subType && (newCptOption.type = subType); + + return subType; }, /** @@ -373,11 +379,15 @@ define(function (require) { componentTypes.push(componentType); }); - ComponentModel.topologicalTravel(componentTypes, function (componentType, dependencies) { - zrUtil.each(componentsMap[componentType], function (component) { - component.restoreData(); - }); - }); + ComponentModel.topologicalTravel( + componentTypes, + ComponentModel.getAllClassMainTypes(), + function (componentType, dependencies) { + zrUtil.each(componentsMap[componentType], function (component) { + component.restoreData(); + }); + } + ); }, /** diff --git a/src/util/component.js b/src/util/component.js index e3686b092..b5d3829bc 100644 --- a/src/util/component.js +++ b/src/util/component.js @@ -123,10 +123,8 @@ define(function(require) { var obj = storage[componentType.main]; if (obj && obj[IS_CONTAINER]) { - zrUtil.each(obj, function (ComponentClass, componentType) { - if (componentType !== IS_CONTAINER) { - result.push(ComponentClass); - } + zrUtil.each(obj, function (o, type) { + type !== IS_CONTAINER && result.push(o); }); } else { @@ -142,6 +140,17 @@ define(function(require) { return !!storage[componentType.main]; }; + /** + * @return {Array.} Like ['aa', 'bb'], but can not be ['aa.xx'] + */ + entity.getAllClassMainTypes = function () { + var types = []; + zrUtil.each(storage, function (obj, type) { + types.push(type); + }); + return types; + }; + entity.parseComponentType = parseComponentType; function makeContainer(componentType) { @@ -187,10 +196,9 @@ define(function(require) { if (!type) { var componentTypeMain = parseComponentType(componentType).main; var Clazz = storage[componentTypeMain]; - Clazz - && Clazz[IS_CONTAINER] - && subTypeDefaulters[componentTypeMain] - && subTypeDefaulters[componentTypeMain](option); + if (Clazz && Clazz[IS_CONTAINER] && subTypeDefaulters[componentTypeMain]) { + type = subTypeDefaulters[componentTypeMain](option); + } } return type; }; @@ -201,34 +209,42 @@ define(function(require) { /** * Topological travel on Activity Network (Activity On Vertices). * Dependencies is defined in Model.prototype.dependencies, like ['xAxis', 'yAxis']. + * * If 'xAxis' or 'yAxis' is absent in componentTypeList, just ignore it in topology. * + * If there are circle dependenceis, just ignore them. + * */ util.enableTopologicalTravel = function (entity, dependencyGetter) { /** * @public - * @param {Array.} componentTypeList Target Component type list. + * @param {Array.} targetNameList Target Component type list. * Can be ['aa', 'bb', 'aa.xx'] + * @param {Array.} fullNameList By which we can build dependency graph. * @param {Function} callback Params: componentType, dependencies. * @param {Object} context Scope of callback. */ - entity.topologicalTravel = function (componentTypeList, callback, context) { - if (!componentTypeList.length) { + entity.topologicalTravel = function (targetNameList, fullNameList, callback, context) { + if (!targetNameList.length) { return; } - var result = makeDepndencyGraph(componentTypeList); + + var result = makeDepndencyGraph(fullNameList); var graph = result.graph; var stack = result.noEntryList; - if (!stack.length) { - throw new Error('Circle exists in dependency graph.'); - } + var targetNameSet = {}; + zrUtil.each(targetNameList, function (name) { + targetNameSet[name] = true; + }); while (stack.length) { var currComponentType = stack.pop(); var currVertex = graph[currComponentType]; - callback.call(context, currComponentType, currVertex.originalDeps.slice()); + if (targetNameSet[currComponentType]) { + callback.call(context, currComponentType, currVertex.originalDeps.slice()); + } zrUtil.each(currVertex.successor, removeEdge); } @@ -249,28 +265,28 @@ define(function(require) { * entryCount: {number} * } */ - function makeDepndencyGraph(componentTypeList) { + function makeDepndencyGraph(fullNameList) { var graph = {}; var noEntryList = []; - zrUtil.each(componentTypeList, function (componentType) { + zrUtil.each(fullNameList, function (name) { - var thisItem = createDependencyGraphItem(graph, componentType); - var originalDeps = thisItem.originalDeps = dependencyGetter(componentType); + var thisItem = createDependencyGraphItem(graph, name); + var originalDeps = thisItem.originalDeps = dependencyGetter(name); - var availableDeps = getAvailableDependencies(originalDeps, componentTypeList); + var availableDeps = getAvailableDependencies(originalDeps, fullNameList); thisItem.entryCount = availableDeps.length; if (thisItem.entryCount === 0) { - noEntryList.push(componentType); + noEntryList.push(name); } - zrUtil.each(availableDeps, function (depComponentType) { - if (zrUtil.indexOf(thisItem.predecessor, depComponentType) < 0) { - thisItem.predecessor.push(depComponentType); + zrUtil.each(availableDeps, function (dependentName) { + if (zrUtil.indexOf(thisItem.predecessor, dependentName) < 0) { + thisItem.predecessor.push(dependentName); } - var thatItem = createDependencyGraphItem(graph, depComponentType); - if (zrUtil.indexOf(thatItem.successor, depComponentType) < 0) { - thatItem.successor.push(componentType); + var thatItem = createDependencyGraphItem(graph, dependentName); + if (zrUtil.indexOf(thatItem.successor, dependentName) < 0) { + thatItem.successor.push(name); } }); }); @@ -278,17 +294,17 @@ define(function(require) { return {graph: graph, noEntryList: noEntryList}; } - function createDependencyGraphItem(graph, componentType) { - if (!graph[componentType]) { - graph[componentType] = {predecessor: [], successor: []}; + function createDependencyGraphItem(graph, name) { + if (!graph[name]) { + graph[name] = {predecessor: [], successor: []}; } - return graph[componentType]; + return graph[name]; } - function getAvailableDependencies(originalDeps, componentTypeList) { + function getAvailableDependencies(originalDeps, fullNameList) { var availableDeps = []; zrUtil.each(originalDeps, function (dep) { - zrUtil.indexOf(componentTypeList, dep) >= 0 && availableDeps.push(dep); + zrUtil.indexOf(fullNameList, dep) >= 0 && availableDeps.push(dep); }); return availableDeps; } diff --git a/test/ut/spec/model/Component.js b/test/ut/spec/model/Component.js index b8005169b..1a17b3f40 100755 --- a/test/ut/spec/model/Component.js +++ b/test/ut/spec/model/Component.js @@ -23,7 +23,8 @@ describe('Component', function() { ComponentModel.extend({type: 'a1'}); ComponentModel.extend({type: 'a2'}); var result = []; - ComponentModel.topologicalTravel(['m1', 'a1', 'a2'], function (componentType, dependencies) { + var allList = ComponentModel.getAllClassMainTypes(); + ComponentModel.topologicalTravel(['m1', 'a1', 'a2'], allList, function (componentType, dependencies) { result.push([componentType, dependencies]); }); expect(result).toEqual([['a2', []], ['a1', []], ['m1', ['a1', 'a2']]]); @@ -32,8 +33,9 @@ describe('Component', function() { testCase('topologicalTravel_a1IsAbsent', function (ComponentModel) { ComponentModel.extend({type: 'm1', dependencies: ['a1', 'a2']}); ComponentModel.extend({type: 'a2'}); + var allList = ComponentModel.getAllClassMainTypes(); var result = []; - ComponentModel.topologicalTravel(['m1', 'a2'], function (componentType, dependencies) { + ComponentModel.topologicalTravel(['m1', 'a2'], allList, function (componentType, dependencies) { result.push([componentType, dependencies]); }); expect(result).toEqual([['a2', []], ['m1', ['a1', 'a2']]]); @@ -43,8 +45,9 @@ describe('Component', function() { ComponentModel.extend({type: 'm1', dependencies: ['a1', 'a2']}); ComponentModel.extend({type: 'a1'}); ComponentModel.extend({type: 'a2'}); + var allList = ComponentModel.getAllClassMainTypes(); var result = []; - ComponentModel.topologicalTravel([], function (componentType, dependencies) { + ComponentModel.topologicalTravel([], allList, function (componentType, dependencies) { result.push([componentType, dependencies]); }); expect(result).toEqual([]); @@ -54,11 +57,12 @@ describe('Component', function() { ComponentModel.extend({type: 'a2'}); ComponentModel.extend({type: 'a1'}); ComponentModel.extend({type: 'm1', dependencies: ['a2']}); + var allList = ComponentModel.getAllClassMainTypes(); var result = []; - ComponentModel.topologicalTravel(['a1', 'a2', 'm1'], function (componentType, dependencies) { + ComponentModel.topologicalTravel(['a1', 'a2', 'm1'], allList, function (componentType, dependencies) { result.push([componentType, dependencies]); }); - expect(result).toEqual([['a2', []], ['m1', ['a2']], ['a1', []]]); + expect(result).toEqual([['a1', []], ['a2', []], ['m1', ['a2']]]); }); testCase('topologicalTravel_diamond', function (ComponentModel) { @@ -66,8 +70,9 @@ describe('Component', function() { ComponentModel.extend({type: 'a2', dependencies: ['a1']}); ComponentModel.extend({type: 'a3', dependencies: ['a1']}); ComponentModel.extend({type: 'm1', dependencies: ['a2', 'a3']}); + var allList = ComponentModel.getAllClassMainTypes(); var result = []; - ComponentModel.topologicalTravel(['m1', 'a1', 'a2', 'a3'], function (componentType, dependencies) { + ComponentModel.topologicalTravel(['m1', 'a1', 'a2', 'a3'], allList, function (componentType, dependencies) { result.push([componentType, dependencies]); }); expect(result).toEqual([['a1', []], ['a3', ['a1']], ['a2', ['a1']], ['m1', ['a2', 'a3']]]); @@ -76,31 +81,59 @@ describe('Component', function() { testCase('topologicalTravel_loop', function (ComponentModel) { ComponentModel.extend({type: 'm1', dependencies: ['a1', 'a2']}); ComponentModel.extend({type: 'm2', dependencies: ['m1', 'a2']}); - ComponentModel.extend({type: 'a1', dependencies: ['m2', 'a2']}); + ComponentModel.extend({type: 'a1', dependencies: ['m2', 'a2', 'a3']}); ComponentModel.extend({type: 'a2'}); - expect(function () { - ComponentModel.topologicalTravel(['m1', 'm2', 'a1']); - }).toThrowError(/Circl/); + ComponentModel.extend({type: 'a3'}); + var allList = ComponentModel.getAllClassMainTypes(); + var result = []; + ComponentModel.topologicalTravel(['m1', 'm2', 'a1', 'a2'], allList, function (componentType, dependencies) { + result.push([componentType, dependencies]); + }); + expect(result).toEqual([['a2', []]]); + // expect(function () { + // ComponentModel.topologicalTravel(['m1', 'm2', 'a1'], allList); + // }).toThrowError(/Circl/); }); testCase('topologicalTravel_multipleEchartsInstance', function (ComponentModel) { ComponentModel.extend({type: 'm1', dependencies: ['a1', 'a2']}); ComponentModel.extend({type: 'a1'}); ComponentModel.extend({type: 'a2'}); + var allList = ComponentModel.getAllClassMainTypes(); var result = []; - ComponentModel.topologicalTravel(['m1', 'a1', 'a2'], function (componentType, dependencies) { + ComponentModel.topologicalTravel(['m1', 'a1', 'a2'], allList, function (componentType, dependencies) { result.push([componentType, dependencies]); }); expect(result).toEqual([['a2', []], ['a1', []], ['m1', ['a1', 'a2']]]); result = []; ComponentModel.extend({type: 'm2', dependencies: ['a1', 'm1']}); - ComponentModel.topologicalTravel(['m2', 'm1', 'a1', 'a2'], function (componentType, dependencies) { + var allList = ComponentModel.getAllClassMainTypes(); + ComponentModel.topologicalTravel(['m2', 'm1', 'a1', 'a2'], allList, function (componentType, dependencies) { result.push([componentType, dependencies]); }); expect(result).toEqual([['a2', []], ['a1', []], ['m1', ['a1', 'a2']], ['m2', ['a1', 'm1']]]); }); + testCase('topologicalTravel_missingSomeNodeButHasDependencies', function (ComponentModel) { + ComponentModel.extend({type: 'm1', dependencies: ['a1', 'a2']}); + ComponentModel.extend({type: 'a2', dependencies: ['a3']}); + ComponentModel.extend({type: 'a3'}); + ComponentModel.extend({type: 'a4'}); + var result = []; + var allList = ComponentModel.getAllClassMainTypes(); + ComponentModel.topologicalTravel(['a3', 'm1'], allList, function (componentType, dependencies) { + result.push([componentType, dependencies]); + }); + expect(result).toEqual([['a3', []], ['m1', ['a1', 'a2']]]); + var result = []; + var allList = ComponentModel.getAllClassMainTypes(); + ComponentModel.topologicalTravel(['m1', 'a3'], allList, function (componentType, dependencies) { + result.push([componentType, dependencies]); + }); + expect(result).toEqual([['a3', []], ['m1', ['a1', 'a2']]]); + }); + testCase('topologicalTravel_subType', function (ComponentModel) { ComponentModel.extend({type: 'm1', dependencies: ['a1', 'a2']}); ComponentModel.extend({type: 'a1.aaa', dependencies: ['a2']}); @@ -109,7 +142,8 @@ describe('Component', function() { ComponentModel.extend({type: 'a3'}); ComponentModel.extend({type: 'a4'}); var result = []; - ComponentModel.topologicalTravel(['m1', 'a1', 'a2', 'a4'], function (componentType, dependencies) { + var allList = ComponentModel.getAllClassMainTypes(); + ComponentModel.topologicalTravel(['m1', 'a1', 'a2', 'a4'], allList, function (componentType, dependencies) { result.push([componentType, dependencies]); }); expect(result).toEqual([['a4', []], ['a2',[]], ['a1', ['a2','a3','a4']], ['m1', ['a1', 'a2']]]); -- GitLab