/*
|
* 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 createDimensions from '../../data/helper/createDimensions';
|
import {getDimensionTypeByAxis} from '../../data/helper/dimensionHelper';
|
import List from '../../data/List';
|
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
|
} 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];
|
|
export interface ThemeRiverStateOption {
|
label?: ThemeRiverSeriesLabelOption
|
itemStyle?: ItemStyleOption
|
}
|
|
export interface ThemeRiverSeriesOption extends SeriesOption<ThemeRiverStateOption>, ThemeRiverStateOption,
|
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;
|
|
useColorPaletteOnData = true;
|
|
/**
|
* @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 gived
|
* @param ecModel the model object for themeRiver option
|
*/
|
getInitialData(option: ThemeRiverSeriesOption, ecModel: GlobalModel): List {
|
|
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 dimensionsInfo = createDimensions(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 List(dimensionsInfo, 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,
|
|
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;
|