|  | /* | 
|  | * Licensed to the Apache Software Foundation (ASF) under one | 
|  | * or more contributor license agreements.  See the NOTICE file | 
|  | * distributed with this work for additional information | 
|  | * regarding copyright ownership.  The ASF licenses this file | 
|  | * to you under the Apache License, Version 2.0 (the | 
|  | * "License"); you may not use this file except in compliance | 
|  | * with the License.  You may obtain a copy of the License at | 
|  | * | 
|  | *   http://www.apache.org/licenses/LICENSE-2.0 | 
|  | * | 
|  | * Unless required by applicable law or agreed to in writing, | 
|  | * software distributed under the License is distributed on an | 
|  | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | 
|  | * KIND, either express or implied.  See the License for the | 
|  | * specific language governing permissions and limitations | 
|  | * under the License. | 
|  | */ | 
|  |  | 
|  |  | 
|  | /** | 
|  | * Link lists and struct (graph or tree) | 
|  | */ | 
|  |  | 
|  | import { curry, each, assert, extend, map, keys } from 'zrender/src/core/util'; | 
|  | import SeriesData from '../SeriesData'; | 
|  | import { makeInner } from '../../util/model'; | 
|  | import { SeriesDataType } from '../../util/types'; | 
|  |  | 
|  | // That is: { dataType: data }, | 
|  | // like: { node: nodeList, edge: edgeList }. | 
|  | // Should contain mainData. | 
|  | type Datas = { [key in SeriesDataType]?: SeriesData }; | 
|  | type StructReferDataAttr = 'data' | 'edgeData'; | 
|  | type StructAttr = 'tree' | 'graph'; | 
|  |  | 
|  | const inner = makeInner<{ | 
|  | datas: Datas; | 
|  | mainData: SeriesData; | 
|  | }, SeriesData>(); | 
|  |  | 
|  |  | 
|  | // Caution: | 
|  | // In most case, either seriesData or its shallow clones (see seriesData.cloneShallow) | 
|  | // is active in echarts process. So considering heap memory consumption, | 
|  | // we do not clone tree or graph, but share them among seriesData and its shallow clones. | 
|  | // But in some rare case, we have to keep old seriesData (like do animation in chart). So | 
|  | // please take care that both the old seriesData and the new seriesData share the same tree/graph. | 
|  |  | 
|  | type LinkSeriesDataOpt = { | 
|  | mainData: SeriesData; | 
|  | // For example, instance of Graph or Tree. | 
|  | struct: { | 
|  | update: () => void; | 
|  | } & { | 
|  | [key in StructReferDataAttr]?: SeriesData | 
|  | }; | 
|  | // Will designate: `mainData[structAttr] = struct;` | 
|  | structAttr: StructAttr; | 
|  | datas?: Datas; | 
|  | // { dataType: attr }, | 
|  | // Will designate: `struct[datasAttr[dataType]] = list;` | 
|  | datasAttr?: { [key in SeriesDataType]?: StructReferDataAttr }; | 
|  | }; | 
|  |  | 
|  | function linkSeriesData(opt: LinkSeriesDataOpt): void { | 
|  | const mainData = opt.mainData; | 
|  | let datas = opt.datas; | 
|  |  | 
|  | if (!datas) { | 
|  | datas = { main: mainData }; | 
|  | opt.datasAttr = { main: 'data' }; | 
|  | } | 
|  | opt.datas = opt.mainData = null; | 
|  |  | 
|  | linkAll(mainData, datas, opt); | 
|  |  | 
|  | // Porxy data original methods. | 
|  | each(datas, function (data: SeriesData) { | 
|  | each(mainData.TRANSFERABLE_METHODS, function (methodName) { | 
|  | data.wrapMethod(methodName, curry(transferInjection, opt)); | 
|  | }); | 
|  | }); | 
|  |  | 
|  | // Beyond transfer, additional features should be added to `cloneShallow`. | 
|  | mainData.wrapMethod('cloneShallow', curry(cloneShallowInjection, opt)); | 
|  |  | 
|  | // Only mainData trigger change, because struct.update may trigger | 
|  | // another changable methods, which may bring about dead lock. | 
|  | each(mainData.CHANGABLE_METHODS, function (methodName) { | 
|  | mainData.wrapMethod(methodName, curry(changeInjection, opt)); | 
|  | }); | 
|  |  | 
|  | // Make sure datas contains mainData. | 
|  | assert(datas[mainData.dataType] === mainData); | 
|  | } | 
|  |  | 
|  | function transferInjection(this: SeriesData, opt: LinkSeriesDataOpt, res: SeriesData): unknown { | 
|  | if (isMainData(this)) { | 
|  | // Transfer datas to new main data. | 
|  | const datas = extend({}, inner(this).datas); | 
|  | datas[this.dataType] = res; | 
|  | linkAll(res, datas, opt); | 
|  | } | 
|  | else { | 
|  | // Modify the reference in main data to point newData. | 
|  | linkSingle(res, this.dataType, inner(this).mainData, opt); | 
|  | } | 
|  | return res; | 
|  | } | 
|  |  | 
|  | function changeInjection(opt: LinkSeriesDataOpt, res: unknown): unknown { | 
|  | opt.struct && opt.struct.update(); | 
|  | return res; | 
|  | } | 
|  |  | 
|  | function cloneShallowInjection(opt: LinkSeriesDataOpt, res: SeriesData): SeriesData { | 
|  | // cloneShallow, which brings about some fragilities, may be inappropriate | 
|  | // to be exposed as an API. So for implementation simplicity we can make | 
|  | // the restriction that cloneShallow of not-mainData should not be invoked | 
|  | // outside, but only be invoked here. | 
|  | each(inner(res).datas, function (data: SeriesData, dataType) { | 
|  | data !== res && linkSingle(data.cloneShallow(), dataType, res, opt); | 
|  | }); | 
|  | return res; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Supplement method to List. | 
|  | * | 
|  | * @public | 
|  | * @param [dataType] If not specified, return mainData. | 
|  | */ | 
|  | function getLinkedData(this: SeriesData, dataType?: SeriesDataType): SeriesData { | 
|  | const mainData = inner(this).mainData; | 
|  | return (dataType == null || mainData == null) | 
|  | ? mainData | 
|  | : inner(mainData).datas[dataType]; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Get list of all linked data | 
|  | */ | 
|  | function getLinkedDataAll(this: SeriesData): { | 
|  | data: SeriesData, | 
|  | type?: SeriesDataType | 
|  | }[] { | 
|  | const mainData = inner(this).mainData; | 
|  | return (mainData == null) | 
|  | ? [{ data: mainData }] | 
|  | : map(keys(inner(mainData).datas), function (type) { | 
|  | return { | 
|  | type, | 
|  | data: inner(mainData).datas[type] | 
|  | }; | 
|  | }); | 
|  | } | 
|  |  | 
|  | function isMainData(data: SeriesData): boolean { | 
|  | return inner(data).mainData === data; | 
|  | } | 
|  |  | 
|  | function linkAll(mainData: SeriesData, datas: Datas, opt: LinkSeriesDataOpt): void { | 
|  | inner(mainData).datas = {}; | 
|  | each(datas, function (data: SeriesData, dataType) { | 
|  | linkSingle(data, dataType, mainData, opt); | 
|  | }); | 
|  | } | 
|  |  | 
|  | function linkSingle(data: SeriesData, dataType: SeriesDataType, mainData: SeriesData, opt: LinkSeriesDataOpt): void { | 
|  | inner(mainData).datas[dataType] = data; | 
|  | inner(data).mainData = mainData; | 
|  |  | 
|  | data.dataType = dataType; | 
|  |  | 
|  | if (opt.struct) { | 
|  | data[opt.structAttr] = opt.struct as any; | 
|  | opt.struct[opt.datasAttr[dataType]] = data; | 
|  | } | 
|  |  | 
|  | // Supplement method. | 
|  | data.getLinkedData = getLinkedData; | 
|  | data.getLinkedDataAll = getLinkedDataAll; | 
|  | } | 
|  |  | 
|  | export default linkSeriesData; |