|  | /* | 
|  | * 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; |