/* * 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 ComponentModel from '../../model/Component'; import SeriesData from '../../data/SeriesData'; import { ComponentOption, BoxLayoutOptionMixin, LayoutOrient, SymbolOptionMixin, LineStyleOption, ItemStyleOption, LabelOption, OptionDataValue, ZRColor, ColorString, CommonTooltipOption, CallbackDataParams, ZREasing } from '../../util/types'; import Model from '../../model/Model'; import GlobalModel, { GlobalModelSetOptionOpts } from '../../model/Global'; import { each, isObject, clone } from 'zrender/src/core/util'; import { convertOptionIdName, getDataItemValue } from '../../util/model'; export interface TimelineControlStyle extends ItemStyleOption { show?: boolean showPlayBtn?: boolean showPrevBtn?: boolean showNextBtn?: boolean itemSize?: number itemGap?: number position?: 'left' | 'right' | 'top' | 'bottom' playIcon?: string stopIcon?: string prevIcon?: string nextIcon?: string // Can be a percent value relative to itemSize playBtnSize?: number | string stopBtnSize?: number | string nextBtnSize?: number | string prevBtnSize?: number | string } export interface TimelineCheckpointStyle extends ItemStyleOption, SymbolOptionMixin { animation?: boolean animationDuration?: number animationEasing?: ZREasing } interface TimelineLineStyleOption extends LineStyleOption { show?: boolean } interface TimelineLabelOption extends Omit { show?: boolean // number can be distance to the timeline axis. sign will determine the side. position?: 'auto' | 'left' | 'right' | 'top' | 'bottom' | number interval?: 'auto' | number formatter?: string | ((value: string | number, index: number) => string) } export interface TimelineDataItemOption extends SymbolOptionMixin { value?: OptionDataValue itemStyle?: ItemStyleOption label?: TimelineLabelOption checkpointStyle?: TimelineCheckpointStyle emphasis?: { itemStyle?: ItemStyleOption label?: TimelineLabelOption checkpointStyle?: TimelineCheckpointStyle } // Style in progress progress?: { lineStyle?: TimelineLineStyleOption itemStyle?: ItemStyleOption label?: TimelineLabelOption } tooltip?: boolean } export interface TimelineOption extends ComponentOption, BoxLayoutOptionMixin, SymbolOptionMixin { mainType?: 'timeline' backgroundColor?: ZRColor borderColor?: ColorString borderWidth?: number tooltip?: CommonTooltipOption & { trigger?: 'item' } show?: boolean axisType?: 'category' | 'time' | 'value' currentIndex?: number autoPlay?: boolean rewind?: boolean loop?: boolean playInterval?: number realtime?: boolean controlPosition?: 'left' | 'right' | 'top' | 'bottom' padding?: number | number[] orient?: LayoutOrient inverse?: boolean // If not specified, options will be changed by "normalMerge". // If specified, options will be changed by "replaceMerge". replaceMerge?: GlobalModelSetOptionOpts['replaceMerge'] lineStyle?: TimelineLineStyleOption itemStyle?: ItemStyleOption checkpointStyle?: TimelineCheckpointStyle controlStyle?: TimelineControlStyle label?: TimelineLabelOption emphasis?: { lineStyle?: TimelineLineStyleOption itemStyle?: ItemStyleOption checkpointStyle?: TimelineCheckpointStyle controlStyle?: TimelineControlStyle label?: TimelineLabelOption } // Style in progress progress?: { lineStyle?: TimelineLineStyleOption itemStyle?: ItemStyleOption label?: TimelineLabelOption } data?: (OptionDataValue | TimelineDataItemOption)[] } class TimelineModel extends ComponentModel { static type = 'timeline'; type = TimelineModel.type; layoutMode = 'box'; private _data: SeriesData; private _names: string[]; /** * @override */ init(option: TimelineOption, parentModel: Model, ecModel: GlobalModel) { this.mergeDefaultAndTheme(option, ecModel); this._initData(); } /** * @override */ mergeOption(option: TimelineOption) { super.mergeOption.apply(this, arguments as any); this._initData(); } setCurrentIndex(currentIndex: number) { if (currentIndex == null) { currentIndex = this.option.currentIndex; } const count = this._data.count(); if (this.option.loop) { currentIndex = (currentIndex % count + count) % count; } else { currentIndex >= count && (currentIndex = count - 1); currentIndex < 0 && (currentIndex = 0); } this.option.currentIndex = currentIndex; } /** * @return {number} currentIndex */ getCurrentIndex() { return this.option.currentIndex; } /** * @return {boolean} */ isIndexMax() { return this.getCurrentIndex() >= this._data.count() - 1; } /** * @param {boolean} state true: play, false: stop */ setPlayState(state: boolean) { this.option.autoPlay = !!state; } /** * @return {boolean} true: play, false: stop */ getPlayState() { return !!this.option.autoPlay; } /** * @private */ _initData() { const thisOption = this.option; const dataArr = thisOption.data || []; const axisType = thisOption.axisType; const names: string[] = this._names = []; let processedDataArr: TimelineOption['data']; if (axisType === 'category') { processedDataArr = []; each(dataArr, function (item, index) { const value = convertOptionIdName(getDataItemValue(item), ''); let newItem; if (isObject(item)) { newItem = clone(item); (newItem as TimelineDataItemOption).value = index; } else { newItem = index; } processedDataArr.push(newItem); names.push(value); }); } else { processedDataArr = dataArr; } const dimType = ({ category: 'ordinal', time: 'time', value: 'number' })[axisType] || 'number'; const data = this._data = new SeriesData([{ name: 'value', type: dimType }], this); data.initData(processedDataArr, names); } getData() { return this._data; } /** * @public * @return {Array.} categoreis */ getCategories() { if (this.get('axisType') === 'category') { return this._names.slice(); } } /** * @protected */ static defaultOption: TimelineOption = { // zlevel: 0, // 一级层叠 z: 4, // 二级层叠 show: true, axisType: 'time', // 模式是时间类型,支持 value, category realtime: true, left: '20%', top: null, right: '20%', bottom: 0, width: null, height: 40, padding: 5, controlPosition: 'left', // 'left' 'right' 'top' 'bottom' 'none' autoPlay: false, rewind: false, // 反向播放 loop: true, playInterval: 2000, // 播放时间间隔,单位ms currentIndex: 0, itemStyle: {}, label: { color: '#000' }, data: [] }; } export default TimelineModel;