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. | |
*/ | |
/* global Uint32Array, Float64Array, Float32Array */ | |
import SeriesModel from '../../model/Series'; | |
import SeriesData from '../../data/SeriesData'; | |
import { concatArray, mergeAll, map, isNumber } from 'zrender/src/core/util'; | |
import CoordinateSystem from '../../core/CoordinateSystem'; | |
import { | |
SeriesOption, | |
SeriesOnCartesianOptionMixin, | |
SeriesOnGeoOptionMixin, | |
SeriesOnPolarOptionMixin, | |
SeriesOnCalendarOptionMixin, | |
SeriesLargeOptionMixin, | |
LineStyleOption, | |
OptionDataValue, | |
StatesOptionMixin, | |
SeriesLineLabelOption, | |
DimensionDefinitionLoose, | |
DefaultStatesMixinEmphasis, | |
ZRColor, | |
CallbackDataParams | |
} from '../../util/types'; | |
import GlobalModel from '../../model/Global'; | |
import type { LineDrawModelOption } from '../helper/LineDraw'; | |
import { createTooltipMarkup } from '../../component/tooltip/tooltipMarkup'; | |
const Uint32Arr = typeof Uint32Array === 'undefined' ? Array : Uint32Array; | |
const Float64Arr = typeof Float64Array === 'undefined' ? Array : Float64Array; | |
function compatEc2(seriesOpt: LinesSeriesOption) { | |
const data = seriesOpt.data; | |
if (data && data[0] && (data as LegacyDataItemOption[][])[0][0] && (data as LegacyDataItemOption[][])[0][0].coord) { | |
if (__DEV__) { | |
console.warn('Lines data configuration has been changed to' | |
+ ' { coords:[[1,2],[2,3]] }'); | |
} | |
seriesOpt.data = map(data as LegacyDataItemOption[][], function (itemOpt) { | |
const coords = [ | |
itemOpt[0].coord, itemOpt[1].coord | |
]; | |
const target: LinesDataItemOption = { | |
coords: coords | |
}; | |
if (itemOpt[0].name) { | |
target.fromName = itemOpt[0].name; | |
} | |
if (itemOpt[1].name) { | |
target.toName = itemOpt[1].name; | |
} | |
return mergeAll([target, itemOpt[0], itemOpt[1]]); | |
}); | |
} | |
} | |
type LinesCoords = number[][]; | |
type LinesValue = OptionDataValue | OptionDataValue[]; | |
interface LinesLineStyleOption<TClr> extends LineStyleOption<TClr> { | |
curveness?: number | |
} | |
// @deprecated | |
interface LegacyDataItemOption { | |
coord: number[] | |
name: string | |
} | |
interface LinesStatesMixin { | |
emphasis?: DefaultStatesMixinEmphasis | |
} | |
export interface LinesStateOption<TCbParams = never> { | |
lineStyle?: LinesLineStyleOption<(TCbParams extends never ? never : (params: TCbParams) => ZRColor) | ZRColor> | |
label?: SeriesLineLabelOption | |
} | |
export interface LinesDataItemOption extends LinesStateOption, | |
StatesOptionMixin<LinesStateOption, LinesStatesMixin> { | |
name?: string | |
fromName?: string | |
toName?: string | |
symbol?: string[] | string | |
symbolSize?: number[] | number | |
coords?: LinesCoords | |
value?: LinesValue | |
effect?: LineDrawModelOption['effect'] | |
} | |
export interface LinesSeriesOption | |
extends SeriesOption<LinesStateOption, LinesStatesMixin>, LinesStateOption<CallbackDataParams>, | |
SeriesOnCartesianOptionMixin, SeriesOnGeoOptionMixin, SeriesOnPolarOptionMixin, | |
SeriesOnCalendarOptionMixin, SeriesLargeOptionMixin { | |
type?: 'lines' | |
coordinateSystem?: string | |
symbol?: string[] | string | |
symbolSize?: number[] | number | |
effect?: LineDrawModelOption['effect'] | |
/** | |
* If lines are polyline | |
* polyline not support curveness, label, animation | |
*/ | |
polyline?: boolean | |
/** | |
* If clip the overflow. | |
* Available when coordinateSystem is cartesian or polar. | |
*/ | |
clip?: boolean | |
data?: LinesDataItemOption[] | |
// Stored as a flat array. In format | |
// Points Count(2) | x | y | x | y | Points Count(3) | x | y | x | y | x | y | | |
| ArrayLike<number> | |
dimensions?: DimensionDefinitionLoose | DimensionDefinitionLoose[] | |
} | |
class LinesSeriesModel extends SeriesModel<LinesSeriesOption> { | |
static readonly type = 'series.lines'; | |
readonly type = LinesSeriesModel.type; | |
static readonly dependencies = ['grid', 'polar', 'geo', 'calendar']; | |
visualStyleAccessPath = 'lineStyle'; | |
visualDrawType = 'stroke' as const; | |
private _flatCoords: ArrayLike<number>; | |
private _flatCoordsOffset: ArrayLike<number>; | |
init(option: LinesSeriesOption) { | |
// The input data may be null/undefined. | |
option.data = option.data || []; | |
// Not using preprocessor because mergeOption may not have series.type | |
compatEc2(option); | |
const result = this._processFlatCoordsArray(option.data); | |
this._flatCoords = result.flatCoords; | |
this._flatCoordsOffset = result.flatCoordsOffset; | |
if (result.flatCoords) { | |
option.data = new Float32Array(result.count); | |
} | |
super.init.apply(this, arguments as any); | |
} | |
mergeOption(option: LinesSeriesOption) { | |
compatEc2(option); | |
if (option.data) { | |
// Only update when have option data to merge. | |
const result = this._processFlatCoordsArray(option.data); | |
this._flatCoords = result.flatCoords; | |
this._flatCoordsOffset = result.flatCoordsOffset; | |
if (result.flatCoords) { | |
option.data = new Float32Array(result.count); | |
} | |
} | |
super.mergeOption.apply(this, arguments as any); | |
} | |
appendData(params: Pick<LinesSeriesOption, 'data'>) { | |
const result = this._processFlatCoordsArray(params.data); | |
if (result.flatCoords) { | |
if (!this._flatCoords) { | |
this._flatCoords = result.flatCoords; | |
this._flatCoordsOffset = result.flatCoordsOffset; | |
} | |
else { | |
this._flatCoords = concatArray(this._flatCoords, result.flatCoords); | |
this._flatCoordsOffset = concatArray(this._flatCoordsOffset, result.flatCoordsOffset); | |
} | |
params.data = new Float32Array(result.count); | |
} | |
this.getRawData().appendData(params.data); | |
} | |
_getCoordsFromItemModel(idx: number) { | |
const itemModel = this.getData().getItemModel<LinesDataItemOption>(idx); | |
const coords = (itemModel.option instanceof Array) | |
? itemModel.option : itemModel.getShallow('coords'); | |
if (__DEV__) { | |
if (!(coords instanceof Array && coords.length > 0 && coords[0] instanceof Array)) { | |
throw new Error( | |
'Invalid coords ' + JSON.stringify(coords) + '. Lines must have 2d coords array in data item.' | |
); | |
} | |
} | |
return coords; | |
} | |
getLineCoordsCount(idx: number) { | |
if (this._flatCoordsOffset) { | |
return this._flatCoordsOffset[idx * 2 + 1]; | |
} | |
else { | |
return this._getCoordsFromItemModel(idx).length; | |
} | |
} | |
getLineCoords(idx: number, out: number[][]) { | |
if (this._flatCoordsOffset) { | |
const offset = this._flatCoordsOffset[idx * 2]; | |
const len = this._flatCoordsOffset[idx * 2 + 1]; | |
for (let i = 0; i < len; i++) { | |
out[i] = out[i] || []; | |
out[i][0] = this._flatCoords[offset + i * 2]; | |
out[i][1] = this._flatCoords[offset + i * 2 + 1]; | |
} | |
return len; | |
} | |
else { | |
const coords = this._getCoordsFromItemModel(idx); | |
for (let i = 0; i < coords.length; i++) { | |
out[i] = out[i] || []; | |
out[i][0] = coords[i][0]; | |
out[i][1] = coords[i][1]; | |
} | |
return coords.length; | |
} | |
} | |
_processFlatCoordsArray(data: LinesSeriesOption['data']) { | |
let startOffset = 0; | |
if (this._flatCoords) { | |
startOffset = this._flatCoords.length; | |
} | |
// Stored as a typed array. In format | |
// Points Count(2) | x | y | x | y | Points Count(3) | x | y | x | y | x | y | | |
if (isNumber(data[0])) { | |
const len = data.length; | |
// Store offset and len of each segment | |
const coordsOffsetAndLenStorage = new Uint32Arr(len) as Uint32Array; | |
const coordsStorage = new Float64Arr(len) as Float64Array; | |
let coordsCursor = 0; | |
let offsetCursor = 0; | |
let dataCount = 0; | |
for (let i = 0; i < len;) { | |
dataCount++; | |
const count = data[i++] as number; | |
// Offset | |
coordsOffsetAndLenStorage[offsetCursor++] = coordsCursor + startOffset; | |
// Len | |
coordsOffsetAndLenStorage[offsetCursor++] = count; | |
for (let k = 0; k < count; k++) { | |
const x = data[i++] as number; | |
const y = data[i++] as number; | |
coordsStorage[coordsCursor++] = x; | |
coordsStorage[coordsCursor++] = y; | |
if (i > len) { | |
if (__DEV__) { | |
throw new Error('Invalid data format.'); | |
} | |
} | |
} | |
} | |
return { | |
flatCoordsOffset: new Uint32Array(coordsOffsetAndLenStorage.buffer, 0, offsetCursor), | |
flatCoords: coordsStorage, | |
count: dataCount | |
}; | |
} | |
return { | |
flatCoordsOffset: null, | |
flatCoords: null, | |
count: data.length | |
}; | |
} | |
getInitialData(option: LinesSeriesOption, ecModel: GlobalModel) { | |
if (__DEV__) { | |
const CoordSys = CoordinateSystem.get(option.coordinateSystem); | |
if (!CoordSys) { | |
throw new Error('Unknown coordinate system ' + option.coordinateSystem); | |
} | |
} | |
const lineData = new SeriesData(['value'], this); | |
lineData.hasItemOption = false; | |
lineData.initData(option.data, [], function (dataItem, dimName, dataIndex, dimIndex) { | |
// dataItem is simply coords | |
if (dataItem instanceof Array) { | |
return NaN; | |
} | |
else { | |
lineData.hasItemOption = true; | |
const value = dataItem.value; | |
if (value != null) { | |
return value instanceof Array ? value[dimIndex] : value; | |
} | |
} | |
}); | |
return lineData; | |
} | |
formatTooltip( | |
dataIndex: number, | |
multipleSeries: boolean, | |
dataType: string | |
) { | |
const data = this.getData(); | |
const itemModel = data.getItemModel<LinesDataItemOption>(dataIndex); | |
const name = itemModel.get('name'); | |
if (name) { | |
return name; | |
} | |
const fromName = itemModel.get('fromName'); | |
const toName = itemModel.get('toName'); | |
const nameArr = []; | |
fromName != null && nameArr.push(fromName); | |
toName != null && nameArr.push(toName); | |
return createTooltipMarkup('nameValue', { | |
name: nameArr.join(' > ') | |
}); | |
} | |
preventIncremental() { | |
return !!this.get(['effect', 'show']); | |
} | |
getProgressive() { | |
const progressive = this.option.progressive; | |
if (progressive == null) { | |
return this.option.large ? 1e4 : this.get('progressive'); | |
} | |
return progressive; | |
} | |
getProgressiveThreshold() { | |
const progressiveThreshold = this.option.progressiveThreshold; | |
if (progressiveThreshold == null) { | |
return this.option.large ? 2e4 : this.get('progressiveThreshold'); | |
} | |
return progressiveThreshold; | |
} | |
getZLevelKey() { | |
const effectModel = this.getModel('effect'); | |
const trailLength = effectModel.get('trailLength'); | |
return this.getData().count() > this.getProgressiveThreshold() | |
// Each progressive series has individual key. | |
? this.id | |
: (effectModel.get('show') && trailLength > 0 ? trailLength + '' : ''); | |
} | |
static defaultOption: LinesSeriesOption = { | |
coordinateSystem: 'geo', | |
// zlevel: 0, | |
z: 2, | |
legendHoverLink: true, | |
// Cartesian coordinate system | |
xAxisIndex: 0, | |
yAxisIndex: 0, | |
symbol: ['none', 'none'], | |
symbolSize: [10, 10], | |
// Geo coordinate system | |
geoIndex: 0, | |
effect: { | |
show: false, | |
period: 4, | |
constantSpeed: 0, | |
symbol: 'circle', | |
symbolSize: 3, | |
loop: true, | |
trailLength: 0.2 | |
}, | |
large: false, | |
// Available when large is true | |
largeThreshold: 2000, | |
polyline: false, | |
clip: true, | |
label: { | |
show: false, | |
position: 'end' | |
// distance: 5, | |
// formatter: 标签文本格式器,同Tooltip.formatter,不支持异步回调 | |
}, | |
lineStyle: { | |
opacity: 0.5 | |
} | |
}; | |
} | |
export default LinesSeriesModel; | |