Spaces:
Running
Running
/* | |
* 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; | |