| /* |
| * 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 {ECPolygon} from '../line/poly'; |
| import * as graphic from '../../util/graphic'; |
| import { setStatesStylesFromModel, enableHoverEmphasis } from '../../util/states'; |
| import {setLabelStyle, getLabelStatesModels} from '../../label/labelStyle'; |
| import {bind} from 'zrender/src/core/util'; |
| import DataDiffer from '../../data/DataDiffer'; |
| import ChartView from '../../view/Chart'; |
| import ThemeRiverSeriesModel from './ThemeRiverSeries'; |
| import GlobalModel from '../../model/Global'; |
| import ExtensionAPI from '../../core/ExtensionAPI'; |
| import { RectLike } from 'zrender/src/core/BoundingRect'; |
| import { ColorString } from '../../util/types'; |
| import { saveOldStyle } from '../../animation/basicTrasition'; |
| |
| type LayerSeries = ReturnType<ThemeRiverSeriesModel['getLayerSeries']>; |
| |
| class ThemeRiverView extends ChartView { |
| |
| static readonly type = 'themeRiver'; |
| readonly type = ThemeRiverView.type; |
| |
| private _layersSeries: LayerSeries; |
| private _layers: graphic.Group[] = []; |
| |
| render(seriesModel: ThemeRiverSeriesModel, ecModel: GlobalModel, api: ExtensionAPI) { |
| const data = seriesModel.getData(); |
| const self = this; |
| |
| const group = this.group; |
| |
| const layersSeries = seriesModel.getLayerSeries(); |
| |
| const layoutInfo = data.getLayout('layoutInfo'); |
| const rect = layoutInfo.rect; |
| const boundaryGap = layoutInfo.boundaryGap; |
| |
| group.x = 0; |
| group.y = rect.y + boundaryGap[0]; |
| |
| function keyGetter(item: LayerSeries[number]) { |
| return item.name; |
| } |
| const dataDiffer = new DataDiffer( |
| this._layersSeries || [], layersSeries, |
| keyGetter, keyGetter |
| ); |
| |
| const newLayersGroups: graphic.Group[] = []; |
| |
| dataDiffer |
| .add(bind(process, this, 'add')) |
| .update(bind(process, this, 'update')) |
| .remove(bind(process, this, 'remove')) |
| .execute(); |
| |
| function process(status: 'add' | 'update' | 'remove', idx: number, oldIdx?: number) { |
| const oldLayersGroups = self._layers; |
| if (status === 'remove') { |
| group.remove(oldLayersGroups[idx]); |
| return; |
| } |
| const points0: number[] = []; |
| const points1: number[] = []; |
| let style; |
| const indices = layersSeries[idx].indices; |
| let j = 0; |
| for (; j < indices.length; j++) { |
| const layout = data.getItemLayout(indices[j]); |
| const x = layout.x; |
| const y0 = layout.y0; |
| const y = layout.y; |
| |
| points0.push(x, y0); |
| points1.push(x, y0 + y); |
| |
| style = data.getItemVisual(indices[j], 'style'); |
| } |
| |
| let polygon: ECPolygon; |
| const textLayout = data.getItemLayout(indices[0]); |
| const labelModel = seriesModel.getModel('label'); |
| const margin = labelModel.get('margin'); |
| const emphasisModel = seriesModel.getModel('emphasis'); |
| |
| if (status === 'add') { |
| const layerGroup = newLayersGroups[idx] = new graphic.Group(); |
| polygon = new ECPolygon({ |
| shape: { |
| points: points0, |
| stackedOnPoints: points1, |
| smooth: 0.4, |
| stackedOnSmooth: 0.4, |
| smoothConstraint: false |
| }, |
| z2: 0 |
| }); |
| layerGroup.add(polygon); |
| group.add(layerGroup); |
| |
| if (seriesModel.isAnimationEnabled()) { |
| polygon.setClipPath(createGridClipShape(polygon.getBoundingRect(), seriesModel, function () { |
| polygon.removeClipPath(); |
| })); |
| } |
| } |
| else { |
| const layerGroup = oldLayersGroups[oldIdx]; |
| polygon = layerGroup.childAt(0) as ECPolygon; |
| group.add(layerGroup); |
| |
| newLayersGroups[idx] = layerGroup; |
| |
| graphic.updateProps(polygon, { |
| shape: { |
| points: points0, |
| stackedOnPoints: points1 |
| } |
| }, seriesModel); |
| |
| saveOldStyle(polygon); |
| } |
| |
| setLabelStyle(polygon, getLabelStatesModels(seriesModel), { |
| labelDataIndex: indices[j - 1], |
| defaultText: data.getName(indices[j - 1]), |
| inheritColor: style.fill as ColorString |
| }, { |
| normal: { |
| verticalAlign: 'middle' |
| // align: 'right' |
| } |
| }); |
| polygon.setTextConfig({ |
| position: null, |
| local: true |
| }); |
| |
| const labelEl = polygon.getTextContent(); |
| // TODO More label position options. |
| if (labelEl) { |
| labelEl.x = textLayout.x - margin; |
| labelEl.y = textLayout.y0 + textLayout.y / 2; |
| } |
| |
| polygon.useStyle(style); |
| |
| data.setItemGraphicEl(idx, polygon); |
| |
| setStatesStylesFromModel(polygon, seriesModel); |
| enableHoverEmphasis(polygon, emphasisModel.get('focus'), emphasisModel.get('blurScope')); |
| } |
| |
| this._layersSeries = layersSeries; |
| this._layers = newLayersGroups; |
| } |
| }; |
| |
| // add animation to the view |
| function createGridClipShape(rect: RectLike, seriesModel: ThemeRiverSeriesModel, cb: () => void) { |
| const rectEl = new graphic.Rect({ |
| shape: { |
| x: rect.x - 10, |
| y: rect.y - 10, |
| width: 0, |
| height: rect.height + 20 |
| } |
| }); |
| graphic.initProps(rectEl, { |
| shape: { |
| x: rect.x - 50, |
| width: rect.width + 100, |
| height: rect.height + 20 |
| } |
| }, seriesModel, cb); |
| |
| return rectEl; |
| } |
| |
| export default ThemeRiverView; |