| /* | 
 | * 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 SeriesModel from '../../model/Series'; | 
 | import prepareSeriesDataSchema from '../../data/helper/createDimensions'; | 
 | import {getDimensionTypeByAxis} from '../../data/helper/dimensionHelper'; | 
 | import SeriesData from '../../data/SeriesData'; | 
 | import * as zrUtil from 'zrender/src/core/util'; | 
 | import {groupData, SINGLE_REFERRING} from '../../util/model'; | 
 | import LegendVisualProvider from '../../visual/LegendVisualProvider'; | 
 | import { | 
 |     SeriesOption, | 
 |     SeriesOnSingleOptionMixin, | 
 |     OptionDataValueDate, | 
 |     OptionDataValueNumeric, | 
 |     ItemStyleOption, | 
 |     BoxLayoutOptionMixin, | 
 |     ZRColor, | 
 |     Dictionary, | 
 |     SeriesLabelOption, | 
 |     CallbackDataParams, | 
 |     DefaultStatesMixinEmphasis | 
 | } from '../../util/types'; | 
 | import SingleAxis from '../../coord/single/SingleAxis'; | 
 | import GlobalModel from '../../model/Global'; | 
 | import Single from '../../coord/single/Single'; | 
 | import { createTooltipMarkup } from '../../component/tooltip/tooltipMarkup'; | 
 |  | 
 | const DATA_NAME_INDEX = 2; | 
 |  | 
 | interface ThemeRiverSeriesLabelOption extends SeriesLabelOption { | 
 |     margin?: number | 
 | } | 
 |  | 
 | type ThemerRiverDataItem = [OptionDataValueDate, OptionDataValueNumeric, string]; | 
 |  | 
 | interface ThemeRiverStatesMixin { | 
 |     emphasis?: DefaultStatesMixinEmphasis | 
 | } | 
 | export interface ThemeRiverStateOption<TCbParams = never> { | 
 |     label?: ThemeRiverSeriesLabelOption | 
 |     itemStyle?: ItemStyleOption<TCbParams> | 
 | } | 
 |  | 
 | export interface ThemeRiverSeriesOption | 
 |     extends SeriesOption<ThemeRiverStateOption<CallbackDataParams>, ThemeRiverStatesMixin>, | 
 |     ThemeRiverStateOption<CallbackDataParams>, | 
 |     SeriesOnSingleOptionMixin, BoxLayoutOptionMixin { | 
 |     type?: 'themeRiver' | 
 |  | 
 |     color?: ZRColor[] | 
 |  | 
 |     coordinateSystem?: 'singleAxis' | 
 |  | 
 |     /** | 
 |      * gap in axis's orthogonal orientation | 
 |      */ | 
 |     boundaryGap?: (string | number)[] | 
 |     /** | 
 |      * [date, value, name] | 
 |      */ | 
 |     data?: ThemerRiverDataItem[] | 
 | } | 
 |  | 
 | class ThemeRiverSeriesModel extends SeriesModel<ThemeRiverSeriesOption> { | 
 |     static readonly type = 'series.themeRiver'; | 
 |     readonly type = ThemeRiverSeriesModel.type; | 
 |  | 
 |     static readonly dependencies = ['singleAxis']; | 
 |  | 
 |     nameMap: zrUtil.HashMap<number, string>; | 
 |  | 
 |     coordinateSystem: Single; | 
 |  | 
 |     /** | 
 |      * @override | 
 |      */ | 
 |     init(option: ThemeRiverSeriesOption) { | 
 |         // eslint-disable-next-line | 
 |         super.init.apply(this, arguments as any); | 
 |  | 
 |         // Put this function here is for the sake of consistency of code style. | 
 |         // Enable legend selection for each data item | 
 |         // Use a function instead of direct access because data reference may changed | 
 |         this.legendVisualProvider = new LegendVisualProvider( | 
 |             zrUtil.bind(this.getData, this), zrUtil.bind(this.getRawData, this) | 
 |         ); | 
 |     } | 
 |  | 
 |     /** | 
 |      * If there is no value of a certain point in the time for some event,set it value to 0. | 
 |      * | 
 |      * @param {Array} data  initial data in the option | 
 |      * @return {Array} | 
 |      */ | 
 |     fixData(data: ThemeRiverSeriesOption['data']) { | 
 |         let rawDataLength = data.length; | 
 |         /** | 
 |          * Make sure every layer data get the same keys. | 
 |          * The value index tells which layer has visited. | 
 |          * { | 
 |          *  2014/01/01: -1 | 
 |          * } | 
 |          */ | 
 |         const timeValueKeys: Dictionary<number> = {}; | 
 |  | 
 |         // grouped data by name | 
 |         const groupResult = groupData(data, (item: ThemerRiverDataItem) => { | 
 |             if (!timeValueKeys.hasOwnProperty(item[0] + '')) { | 
 |                 timeValueKeys[item[0] + ''] = -1; | 
 |             } | 
 |             return item[2]; | 
 |         }); | 
 |         const layerData: {name: string, dataList: ThemerRiverDataItem[]}[] = []; | 
 |         groupResult.buckets.each(function (items, key) { | 
 |             layerData.push({ | 
 |                 name: key, dataList: items | 
 |             }); | 
 |         }); | 
 |         const layerNum = layerData.length; | 
 |  | 
 |         for (let k = 0; k < layerNum; ++k) { | 
 |             const name = layerData[k].name; | 
 |             for (let j = 0; j < layerData[k].dataList.length; ++j) { | 
 |                 const timeValue = layerData[k].dataList[j][0] + ''; | 
 |                 timeValueKeys[timeValue] = k; | 
 |             } | 
 |  | 
 |             for (const timeValue in timeValueKeys) { | 
 |                 if (timeValueKeys.hasOwnProperty(timeValue) && timeValueKeys[timeValue] !== k) { | 
 |                     timeValueKeys[timeValue] = k; | 
 |                     data[rawDataLength] = [timeValue, 0, name]; | 
 |                     rawDataLength++; | 
 |                 } | 
 |             } | 
 |  | 
 |         } | 
 |         return data; | 
 |     } | 
 |  | 
 |     /** | 
 |      * @override | 
 |      * @param  option  the initial option that user gave | 
 |      * @param  ecModel  the model object for themeRiver option | 
 |      */ | 
 |     getInitialData(option: ThemeRiverSeriesOption, ecModel: GlobalModel): SeriesData { | 
 |  | 
 |         const singleAxisModel = this.getReferringComponents('singleAxis', SINGLE_REFERRING).models[0]; | 
 |  | 
 |         const axisType = singleAxisModel.get('type'); | 
 |  | 
 |         // filter the data item with the value of label is undefined | 
 |         const filterData = zrUtil.filter(option.data, function (dataItem) { | 
 |             return dataItem[2] !== undefined; | 
 |         }); | 
 |  | 
 |         // ??? TODO design a stage to transfer data for themeRiver and lines? | 
 |         const data = this.fixData(filterData || []); | 
 |         const nameList = []; | 
 |         const nameMap = this.nameMap = zrUtil.createHashMap(); | 
 |         let count = 0; | 
 |  | 
 |         for (let i = 0; i < data.length; ++i) { | 
 |             nameList.push(data[i][DATA_NAME_INDEX]); | 
 |             if (!nameMap.get(data[i][DATA_NAME_INDEX] as string)) { | 
 |                 nameMap.set(data[i][DATA_NAME_INDEX] as string, count); | 
 |                 count++; | 
 |             } | 
 |         } | 
 |  | 
 |         const { dimensions } = prepareSeriesDataSchema(data, { | 
 |             coordDimensions: ['single'], | 
 |             dimensionsDefine: [ | 
 |                 { | 
 |                     name: 'time', | 
 |                     type: getDimensionTypeByAxis(axisType) | 
 |                 }, | 
 |                 { | 
 |                     name: 'value', | 
 |                     type: 'float' | 
 |                 }, | 
 |                 { | 
 |                     name: 'name', | 
 |                     type: 'ordinal' | 
 |                 } | 
 |             ], | 
 |             encodeDefine: { | 
 |                 single: 0, | 
 |                 value: 1, | 
 |                 itemName: 2 | 
 |             } | 
 |         }); | 
 |  | 
 |         const list = new SeriesData(dimensions, this); | 
 |         list.initData(data); | 
 |  | 
 |         return list; | 
 |     } | 
 |  | 
 |     /** | 
 |      * The raw data is divided into multiple layers and each layer | 
 |      *     has same name. | 
 |      */ | 
 |     getLayerSeries() { | 
 |         const data = this.getData(); | 
 |         const lenCount = data.count(); | 
 |         const indexArr = []; | 
 |  | 
 |         for (let i = 0; i < lenCount; ++i) { | 
 |             indexArr[i] = i; | 
 |         } | 
 |  | 
 |         const timeDim = data.mapDimension('single'); | 
 |  | 
 |         // data group by name | 
 |         const groupResult = groupData(indexArr, function (index) { | 
 |             return data.get('name', index) as string; | 
 |         }); | 
 |         const layerSeries: { | 
 |             name: string | 
 |             indices: number[] | 
 |         }[] = []; | 
 |         groupResult.buckets.each(function (items: number[], key: string) { | 
 |             items.sort(function (index1: number, index2: number) { | 
 |                 return data.get(timeDim, index1) as number - (data.get(timeDim, index2) as number); | 
 |             }); | 
 |             layerSeries.push({ | 
 |                 name: key, | 
 |                 indices: items | 
 |             }); | 
 |         }); | 
 |  | 
 |         return layerSeries; | 
 |     } | 
 |  | 
 |     /** | 
 |      * Get data indices for show tooltip content | 
 |      */ | 
 |     getAxisTooltipData(dim: string | string[], value: number, baseAxis: SingleAxis) { | 
 |         if (!zrUtil.isArray(dim)) { | 
 |             dim = dim ? [dim] : []; | 
 |         } | 
 |  | 
 |         const data = this.getData(); | 
 |         const layerSeries = this.getLayerSeries(); | 
 |         const indices = []; | 
 |         const layerNum = layerSeries.length; | 
 |         let nestestValue; | 
 |  | 
 |         for (let i = 0; i < layerNum; ++i) { | 
 |             let minDist = Number.MAX_VALUE; | 
 |             let nearestIdx = -1; | 
 |             const pointNum = layerSeries[i].indices.length; | 
 |             for (let j = 0; j < pointNum; ++j) { | 
 |                 const theValue = data.get(dim[0], layerSeries[i].indices[j]) as number; | 
 |                 const dist = Math.abs(theValue - value); | 
 |                 if (dist <= minDist) { | 
 |                     nestestValue = theValue; | 
 |                     minDist = dist; | 
 |                     nearestIdx = layerSeries[i].indices[j]; | 
 |                 } | 
 |             } | 
 |             indices.push(nearestIdx); | 
 |         } | 
 |  | 
 |         return {dataIndices: indices, nestestValue: nestestValue}; | 
 |     } | 
 |  | 
 |     formatTooltip( | 
 |         dataIndex: number, | 
 |         multipleSeries: boolean, | 
 |         dataType: string | 
 |     ) { | 
 |         const data = this.getData(); | 
 |         const name = data.getName(dataIndex); | 
 |         const value = data.get(data.mapDimension('value'), dataIndex); | 
 |  | 
 |         return createTooltipMarkup('nameValue', { name: name, value: value }); | 
 |     } | 
 |  | 
 |     static defaultOption: ThemeRiverSeriesOption = { | 
 |         // zlevel: 0, | 
 |         z: 2, | 
 |  | 
 |         colorBy: 'data', | 
 |         coordinateSystem: 'singleAxis', | 
 |  | 
 |         // gap in axis's orthogonal orientation | 
 |         boundaryGap: ['10%', '10%'], | 
 |  | 
 |         // legendHoverLink: true, | 
 |  | 
 |         singleAxisIndex: 0, | 
 |  | 
 |         animationEasing: 'linear', | 
 |  | 
 |         label: { | 
 |             margin: 4, | 
 |             show: true, | 
 |             position: 'left', | 
 |             fontSize: 11 | 
 |         }, | 
 |  | 
 |         emphasis: { | 
 |  | 
 |             label: { | 
 |                 show: true | 
 |             } | 
 |         } | 
 |     }; | 
 | } | 
 |  | 
 | export default ThemeRiverSeriesModel; |