|   | 
| /* | 
| * 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. | 
| */ | 
|   | 
|   | 
| /** | 
|  * AUTO-GENERATED FILE. DO NOT MODIFY. | 
|  */ | 
|   | 
| /* | 
| * 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. | 
| */ | 
| import { normalizeToArray // , MappingExistingItem, setComponentTypeToKeyInfo, mappingToExists | 
| } from '../util/model.js'; | 
| import { each, clone, map, isTypedArray, setAsPrimitive, isArray, isObject // , HashMap , createHashMap, extend, merge, | 
| } from 'zrender/lib/core/util.js'; | 
| import { error } from '../util/log.js'; | 
| var QUERY_REG = /^(min|max)?(.+)$/; // Key: mainType | 
| // type FakeComponentsMap = HashMap<(MappingExistingItem & { subType: string })[]>; | 
|   | 
| /** | 
|  * TERM EXPLANATIONS: | 
|  * See `ECOption` and `ECUnitOption` in `src/util/types.ts`. | 
|  */ | 
|   | 
| var OptionManager = | 
| /** @class */ | 
| function () { | 
|   // timeline.notMerge is not supported in ec3. Firstly there is rearly | 
|   // case that notMerge is needed. Secondly supporting 'notMerge' requires | 
|   // rawOption cloned and backuped when timeline changed, which does no | 
|   // good to performance. What's more, that both timeline and setOption | 
|   // method supply 'notMerge' brings complex and some problems. | 
|   // Consider this case: | 
|   // (step1) chart.setOption({timeline: {notMerge: false}, ...}, false); | 
|   // (step2) chart.setOption({timeline: {notMerge: true}, ...}, false); | 
|   function OptionManager(api) { | 
|     this._timelineOptions = []; | 
|     this._mediaList = []; | 
|     /** | 
|      * -1, means default. | 
|      * empty means no media. | 
|      */ | 
|   | 
|     this._currentMediaIndices = []; | 
|     this._api = api; | 
|   } | 
|   | 
|   OptionManager.prototype.setOption = function (rawOption, optionPreprocessorFuncs, opt) { | 
|     if (rawOption) { | 
|       // That set dat primitive is dangerous if user reuse the data when setOption again. | 
|       each(normalizeToArray(rawOption.series), function (series) { | 
|         series && series.data && isTypedArray(series.data) && setAsPrimitive(series.data); | 
|       }); | 
|       each(normalizeToArray(rawOption.dataset), function (dataset) { | 
|         dataset && dataset.source && isTypedArray(dataset.source) && setAsPrimitive(dataset.source); | 
|       }); | 
|     } // Caution: some series modify option data, if do not clone, | 
|     // it should ensure that the repeat modify correctly | 
|     // (create a new object when modify itself). | 
|   | 
|   | 
|     rawOption = clone(rawOption); // FIXME | 
|     // If some property is set in timeline options or media option but | 
|     // not set in baseOption, a warning should be given. | 
|   | 
|     var optionBackup = this._optionBackup; | 
|     var newParsedOption = parseRawOption(rawOption, optionPreprocessorFuncs, !optionBackup); | 
|     this._newBaseOption = newParsedOption.baseOption; // For setOption at second time (using merge mode); | 
|   | 
|     if (optionBackup) { | 
|       // FIXME | 
|       // the restore merge solution is essentially incorrect. | 
|       // the mapping can not be 100% consistent with ecModel, which probably brings | 
|       // potential bug! | 
|       // The first merge is delayed, becuase in most cases, users do not call `setOption` twice. | 
|       // let fakeCmptsMap = this._fakeCmptsMap; | 
|       // if (!fakeCmptsMap) { | 
|       //     fakeCmptsMap = this._fakeCmptsMap = createHashMap(); | 
|       //     mergeToBackupOption(fakeCmptsMap, null, optionBackup.baseOption, null); | 
|       // } | 
|       // mergeToBackupOption( | 
|       //     fakeCmptsMap, optionBackup.baseOption, newParsedOption.baseOption, opt | 
|       // ); | 
|       // For simplicity, timeline options and media options do not support merge, | 
|       // that is, if you `setOption` twice and both has timeline options, the latter | 
|       // timeline opitons will not be merged to the formers, but just substitude them. | 
|       if (newParsedOption.timelineOptions.length) { | 
|         optionBackup.timelineOptions = newParsedOption.timelineOptions; | 
|       } | 
|   | 
|       if (newParsedOption.mediaList.length) { | 
|         optionBackup.mediaList = newParsedOption.mediaList; | 
|       } | 
|   | 
|       if (newParsedOption.mediaDefault) { | 
|         optionBackup.mediaDefault = newParsedOption.mediaDefault; | 
|       } | 
|     } else { | 
|       this._optionBackup = newParsedOption; | 
|     } | 
|   }; | 
|   | 
|   OptionManager.prototype.mountOption = function (isRecreate) { | 
|     var optionBackup = this._optionBackup; | 
|     this._timelineOptions = optionBackup.timelineOptions; | 
|     this._mediaList = optionBackup.mediaList; | 
|     this._mediaDefault = optionBackup.mediaDefault; | 
|     this._currentMediaIndices = []; | 
|     return clone(isRecreate // this._optionBackup.baseOption, which is created at the first `setOption` | 
|     // called, and is merged into every new option by inner method `mergeToBackupOption` | 
|     // each time `setOption` called, can be only used in `isRecreate`, because | 
|     // its reliability is under suspicion. In other cases option merge is | 
|     // performed by `model.mergeOption`. | 
|     ? optionBackup.baseOption : this._newBaseOption); | 
|   }; | 
|   | 
|   OptionManager.prototype.getTimelineOption = function (ecModel) { | 
|     var option; | 
|     var timelineOptions = this._timelineOptions; | 
|   | 
|     if (timelineOptions.length) { | 
|       // getTimelineOption can only be called after ecModel inited, | 
|       // so we can get currentIndex from timelineModel. | 
|       var timelineModel = ecModel.getComponent('timeline'); | 
|   | 
|       if (timelineModel) { | 
|         option = clone( // FIXME:TS as TimelineModel or quivlant interface | 
|         timelineOptions[timelineModel.getCurrentIndex()]); | 
|       } | 
|     } | 
|   | 
|     return option; | 
|   }; | 
|   | 
|   OptionManager.prototype.getMediaOption = function (ecModel) { | 
|     var ecWidth = this._api.getWidth(); | 
|   | 
|     var ecHeight = this._api.getHeight(); | 
|   | 
|     var mediaList = this._mediaList; | 
|     var mediaDefault = this._mediaDefault; | 
|     var indices = []; | 
|     var result = []; // No media defined. | 
|   | 
|     if (!mediaList.length && !mediaDefault) { | 
|       return result; | 
|     } // Multi media may be applied, the latter defined media has higher priority. | 
|   | 
|   | 
|     for (var i = 0, len = mediaList.length; i < len; i++) { | 
|       if (applyMediaQuery(mediaList[i].query, ecWidth, ecHeight)) { | 
|         indices.push(i); | 
|       } | 
|     } // FIXME | 
|     // Whether mediaDefault should force users to provide? Otherwise | 
|     // the change by media query can not be recorvered. | 
|   | 
|   | 
|     if (!indices.length && mediaDefault) { | 
|       indices = [-1]; | 
|     } | 
|   | 
|     if (indices.length && !indicesEquals(indices, this._currentMediaIndices)) { | 
|       result = map(indices, function (index) { | 
|         return clone(index === -1 ? mediaDefault.option : mediaList[index].option); | 
|       }); | 
|     } // Otherwise return nothing. | 
|   | 
|   | 
|     this._currentMediaIndices = indices; | 
|     return result; | 
|   }; | 
|   | 
|   return OptionManager; | 
| }(); | 
| /** | 
|  * [RAW_OPTION_PATTERNS] | 
|  * (Note: "series: []" represents all other props in `ECUnitOption`) | 
|  * | 
|  * (1) No prop "baseOption" declared: | 
|  * Root option is used as "baseOption" (except prop "options" and "media"). | 
|  * ```js | 
|  * option = { | 
|  *     series: [], | 
|  *     timeline: {}, | 
|  *     options: [], | 
|  * }; | 
|  * option = { | 
|  *     series: [], | 
|  *     media: {}, | 
|  * }; | 
|  * option = { | 
|  *     series: [], | 
|  *     timeline: {}, | 
|  *     options: [], | 
|  *     media: {}, | 
|  * } | 
|  * ``` | 
|  * | 
|  * (2) Prop "baseOption" declared: | 
|  * If "baseOption" declared, `ECUnitOption` props can only be declared | 
|  * inside "baseOption" except prop "timeline" (compat ec2). | 
|  * ```js | 
|  * option = { | 
|  *     baseOption: { | 
|  *         timeline: {}, | 
|  *         series: [], | 
|  *     }, | 
|  *     options: [] | 
|  * }; | 
|  * option = { | 
|  *     baseOption: { | 
|  *         series: [], | 
|  *     }, | 
|  *     media: [] | 
|  * }; | 
|  * option = { | 
|  *     baseOption: { | 
|  *         timeline: {}, | 
|  *         series: [], | 
|  *     }, | 
|  *     options: [] | 
|  *     media: [] | 
|  * }; | 
|  * option = { | 
|  *     // ec3 compat ec2: allow (only) `timeline` declared | 
|  *     // outside baseOption. Keep this setting for compat. | 
|  *     timeline: {}, | 
|  *     baseOption: { | 
|  *         series: [], | 
|  *     }, | 
|  *     options: [], | 
|  *     media: [] | 
|  * }; | 
|  * ``` | 
|  */ | 
|   | 
|   | 
| function parseRawOption( // `rawOption` May be modified | 
| rawOption, optionPreprocessorFuncs, isNew) { | 
|   var mediaList = []; | 
|   var mediaDefault; | 
|   var baseOption; | 
|   var declaredBaseOption = rawOption.baseOption; // Compatible with ec2, [RAW_OPTION_PATTERNS] above. | 
|   | 
|   var timelineOnRoot = rawOption.timeline; | 
|   var timelineOptionsOnRoot = rawOption.options; | 
|   var mediaOnRoot = rawOption.media; | 
|   var hasMedia = !!rawOption.media; | 
|   var hasTimeline = !!(timelineOptionsOnRoot || timelineOnRoot || declaredBaseOption && declaredBaseOption.timeline); | 
|   | 
|   if (declaredBaseOption) { | 
|     baseOption = declaredBaseOption; // For merge option. | 
|   | 
|     if (!baseOption.timeline) { | 
|       baseOption.timeline = timelineOnRoot; | 
|     } | 
|   } // For convenience, enable to use the root option as the `baseOption`: | 
|   // `{ ...normalOptionProps, media: [{ ... }, { ... }] }` | 
|   else { | 
|       if (hasTimeline || hasMedia) { | 
|         rawOption.options = rawOption.media = null; | 
|       } | 
|   | 
|       baseOption = rawOption; | 
|     } | 
|   | 
|   if (hasMedia) { | 
|     if (isArray(mediaOnRoot)) { | 
|       each(mediaOnRoot, function (singleMedia) { | 
|         if (process.env.NODE_ENV !== 'production') { | 
|           // Real case of wrong config. | 
|           if (singleMedia && !singleMedia.option && isObject(singleMedia.query) && isObject(singleMedia.query.option)) { | 
|             error('Illegal media option. Must be like { media: [ { query: {}, option: {} } ] }'); | 
|           } | 
|         } | 
|   | 
|         if (singleMedia && singleMedia.option) { | 
|           if (singleMedia.query) { | 
|             mediaList.push(singleMedia); | 
|           } else if (!mediaDefault) { | 
|             // Use the first media default. | 
|             mediaDefault = singleMedia; | 
|           } | 
|         } | 
|       }); | 
|     } else { | 
|       if (process.env.NODE_ENV !== 'production') { | 
|         // Real case of wrong config. | 
|         error('Illegal media option. Must be an array. Like { media: [ {...}, {...} ] }'); | 
|       } | 
|     } | 
|   } | 
|   | 
|   doPreprocess(baseOption); | 
|   each(timelineOptionsOnRoot, function (option) { | 
|     return doPreprocess(option); | 
|   }); | 
|   each(mediaList, function (media) { | 
|     return doPreprocess(media.option); | 
|   }); | 
|   | 
|   function doPreprocess(option) { | 
|     each(optionPreprocessorFuncs, function (preProcess) { | 
|       preProcess(option, isNew); | 
|     }); | 
|   } | 
|   | 
|   return { | 
|     baseOption: baseOption, | 
|     timelineOptions: timelineOptionsOnRoot || [], | 
|     mediaDefault: mediaDefault, | 
|     mediaList: mediaList | 
|   }; | 
| } | 
| /** | 
|  * @see <http://www.w3.org/TR/css3-mediaqueries/#media1> | 
|  * Support: width, height, aspectRatio | 
|  * Can use max or min as prefix. | 
|  */ | 
|   | 
|   | 
| function applyMediaQuery(query, ecWidth, ecHeight) { | 
|   var realMap = { | 
|     width: ecWidth, | 
|     height: ecHeight, | 
|     aspectratio: ecWidth / ecHeight // lowser case for convenientce. | 
|   | 
|   }; | 
|   var applicatable = true; | 
|   each(query, function (value, attr) { | 
|     var matched = attr.match(QUERY_REG); | 
|   | 
|     if (!matched || !matched[1] || !matched[2]) { | 
|       return; | 
|     } | 
|   | 
|     var operator = matched[1]; | 
|     var realAttr = matched[2].toLowerCase(); | 
|   | 
|     if (!compare(realMap[realAttr], value, operator)) { | 
|       applicatable = false; | 
|     } | 
|   }); | 
|   return applicatable; | 
| } | 
|   | 
| function compare(real, expect, operator) { | 
|   if (operator === 'min') { | 
|     return real >= expect; | 
|   } else if (operator === 'max') { | 
|     return real <= expect; | 
|   } else { | 
|     // Equals | 
|     return real === expect; | 
|   } | 
| } | 
|   | 
| function indicesEquals(indices1, indices2) { | 
|   // indices is always order by asc and has only finite number. | 
|   return indices1.join(',') === indices2.join(','); | 
| } | 
| /** | 
|  * Consider case: | 
|  * `chart.setOption(opt1);` | 
|  * Then user do some interaction like dataZoom, dataView changing. | 
|  * `chart.setOption(opt2);` | 
|  * Then user press 'reset button' in toolbox. | 
|  * | 
|  * After doing that all of the interaction effects should be reset, the | 
|  * chart should be the same as the result of invoke | 
|  * `chart.setOption(opt1); chart.setOption(opt2);`. | 
|  * | 
|  * Although it is not able ensure that | 
|  * `chart.setOption(opt1); chart.setOption(opt2);` is equivalents to | 
|  * `chart.setOption(merge(opt1, opt2));` exactly, | 
|  * this might be the only simple way to implement that feature. | 
|  * | 
|  * MEMO: We've considered some other approaches: | 
|  * 1. Each model handle its self restoration but not uniform treatment. | 
|  *     (Too complex in logic and error-prone) | 
|  * 2. Use a shadow ecModel. (Performace expensive) | 
|  * | 
|  * FIXME: A possible solution: | 
|  * Add a extra level of model for each component model. The inheritance chain would be: | 
|  * ecModel <- componentModel <- componentActionModel <- dataItemModel | 
|  * And all of the actions can only modify the `componentActionModel` rather than | 
|  * `componentModel`. `setOption` will only modify the `ecModel` and `componentModel`. | 
|  * When "resotre" action triggered, model from `componentActionModel` will be discarded | 
|  * instead of recreating the "ecModel" from the "_optionBackup". | 
|  */ | 
| // function mergeToBackupOption( | 
| //     fakeCmptsMap: FakeComponentsMap, | 
| //     // `tarOption` Can be null/undefined, means init | 
| //     tarOption: ECUnitOption, | 
| //     newOption: ECUnitOption, | 
| //     // Can be null/undefined | 
| //     opt: InnerSetOptionOpts | 
| // ): void { | 
| //     newOption = newOption || {} as ECUnitOption; | 
| //     const notInit = !!tarOption; | 
| //     each(newOption, function (newOptsInMainType, mainType) { | 
| //         if (newOptsInMainType == null) { | 
| //             return; | 
| //         } | 
| //         if (!ComponentModel.hasClass(mainType)) { | 
| //             if (tarOption) { | 
| //                 tarOption[mainType] = merge(tarOption[mainType], newOptsInMainType, true); | 
| //             } | 
| //         } | 
| //         else { | 
| //             const oldTarOptsInMainType = notInit ? normalizeToArray(tarOption[mainType]) : null; | 
| //             const oldFakeCmptsInMainType = fakeCmptsMap.get(mainType) || []; | 
| //             const resultTarOptsInMainType = notInit ? (tarOption[mainType] = [] as ComponentOption[]) : null; | 
| //             const resultFakeCmptsInMainType = fakeCmptsMap.set(mainType, []); | 
| //             const mappingResult = mappingToExists( | 
| //                 oldFakeCmptsInMainType, | 
| //                 normalizeToArray(newOptsInMainType), | 
| //                 (opt && opt.replaceMergeMainTypeMap.get(mainType)) ? 'replaceMerge' : 'normalMerge' | 
| //             ); | 
| //             setComponentTypeToKeyInfo(mappingResult, mainType, ComponentModel as ComponentModelConstructor); | 
| //             each(mappingResult, function (resultItem, index) { | 
| //                 // The same logic as `Global.ts#_mergeOption`. | 
| //                 let fakeCmpt = resultItem.existing; | 
| //                 const newOption = resultItem.newOption; | 
| //                 const keyInfo = resultItem.keyInfo; | 
| //                 let fakeCmptOpt; | 
| //                 if (!newOption) { | 
| //                     fakeCmptOpt = oldTarOptsInMainType[index]; | 
| //                 } | 
| //                 else { | 
| //                     if (fakeCmpt && fakeCmpt.subType === keyInfo.subType) { | 
| //                         fakeCmpt.name = keyInfo.name; | 
| //                         if (notInit) { | 
| //                             fakeCmptOpt = merge(oldTarOptsInMainType[index], newOption, true); | 
| //                         } | 
| //                     } | 
| //                     else { | 
| //                         fakeCmpt = extend({}, keyInfo); | 
| //                         if (notInit) { | 
| //                             fakeCmptOpt = clone(newOption); | 
| //                         } | 
| //                     } | 
| //                 } | 
| //                 if (fakeCmpt) { | 
| //                     notInit && resultTarOptsInMainType.push(fakeCmptOpt); | 
| //                     resultFakeCmptsInMainType.push(fakeCmpt); | 
| //                 } | 
| //                 else { | 
| //                     notInit && resultTarOptsInMainType.push(void 0); | 
| //                     resultFakeCmptsInMainType.push(void 0); | 
| //                 } | 
| //             }); | 
| //         } | 
| //     }); | 
| // } | 
|   | 
|   | 
| export default OptionManager; |