BarView.ts 31.6 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
/*
* 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.
*/

S
sushuang 已提交
20
import {__DEV__} from '../../config';
S
sushuang 已提交
21
import * as zrUtil from 'zrender/src/core/util';
22 23 24 25 26 27 28 29 30 31
import {
    Rect,
    Sector,
    getECData,
    updateProps,
    initProps,
    enableHoverEmphasis,
    setLabelStyle,
    clearStates
} from '../../util/graphic';
32
import Path, { PathProps } from 'zrender/src/graphic/Path';
P
pissang 已提交
33
import Group from 'zrender/src/graphic/Group';
34
import {throttle} from '../../util/throttle';
35
import {createClipPath} from '../helper/createClipPathFromCoordSys';
36
import Sausage from '../../util/shape/sausage';
37
import ChartView from '../../view/Chart';
38
import List, {DefaultDataVisual} from '../../data/List';
39 40
import GlobalModel from '../../model/Global';
import ExtensionAPI from '../../ExtensionAPI';
O
Ovilia 已提交
41
import { StageHandlerProgressParams, ZRElementEvent, ColorString, OrdinalSortInfo, Payload, OrdinalNumber } from '../../util/types';
42 43 44 45 46
import BarSeriesModel, { BarSeriesOption, BarDataItemOption } from './BarSeries';
import type Axis2D from '../../coord/cartesian/Axis2D';
import type Cartesian2D from '../../coord/cartesian/Cartesian2D';
import type { RectLike } from 'zrender/src/core/BoundingRect';
import type Model from '../../model/Model';
47
import { isCoordinateSystemType } from '../../coord/CoordinateSystem';
P
pissang 已提交
48
import { getDefaultLabel } from '../helper/labelHelper';
49
import OrdinalScale from '../../scale/Ordinal';
50 51 52 53 54 55 56 57

const BAR_BORDER_WIDTH_QUERY = ['itemStyle', 'borderWidth'] as const;
const _eventPos = [0, 0];

const mathMax = Math.max;
const mathMin = Math.min;

type CoordSysOfBar = BarSeriesModel['coordinateSystem'];
1
100pah 已提交
58 59
type RectShape = Rect['shape'];
type SectorShape = Sector['shape'];
60 61 62 63

type SectorLayout = SectorShape;
type RectLayout = RectShape;

1
100pah 已提交
64
type BarPossiblePath = Sector | Rect | Sausage;
P
pissang 已提交
65

66
function getClipArea(coord: CoordSysOfBar, data: List) {
1
100pah 已提交
67
    let coordSysClipArea;
68
    if (isCoordinateSystemType<Cartesian2D>(coord, 'cartesian2d')) {
1
100pah 已提交
69
        coordSysClipArea = coord.getArea && coord.getArea();
70
        const baseAxis = coord.getBaseAxis();
71 72 73 74
        // When boundaryGap is false or using time axis. bar may exceed the grid.
        // We should not clip this part.
        // See test/bar2.html
        if (baseAxis.type !== 'category' || !baseAxis.onBand) {
75
            const expandWidth = data.getLayout('bandWidth');
76 77 78 79 80 81 82 83 84 85 86 87 88 89
            if (baseAxis.isHorizontal()) {
                coordSysClipArea.x -= expandWidth;
                coordSysClipArea.width += expandWidth * 2;
            }
            else {
                coordSysClipArea.y -= expandWidth;
                coordSysClipArea.height += expandWidth * 2;
            }
        }
    }

    return coordSysClipArea;
}

L
lang 已提交
90

91
class BarView extends ChartView {
1
100pah 已提交
92 93
    static type = 'bar' as const;
    type = BarView.type;
94

1
100pah 已提交
95
    _data: List;
96

1
100pah 已提交
97
    _isLargeDraw: boolean;
98

1
100pah 已提交
99
    _backgroundGroup: Group;
100

1
100pah 已提交
101
    _backgroundEls: (Rect | Sector)[];
L
lang 已提交
102

103
    render(seriesModel: BarSeriesModel, ecModel: GlobalModel, api: ExtensionAPI) {
104 105
        this._updateDrawMode(seriesModel);

106
        const coordinateSystemType = seriesModel.get('coordinateSystem');
L
lang 已提交
107

S
sushuang 已提交
108 109 110
        if (coordinateSystemType === 'cartesian2d'
            || coordinateSystemType === 'polar'
        ) {
111 112 113
            this._isLargeDraw
                ? this._renderLarge(seriesModel, ecModel, api)
                : this._renderNormal(seriesModel, ecModel, api);
S
sushuang 已提交
114 115 116 117
        }
        else if (__DEV__) {
            console.warn('Only cartesian2d and polar supported for bar.');
        }
L
lang 已提交
118

S
sushuang 已提交
119
        return this.group;
120
    }
L
lang 已提交
121

122
    incrementalPrepareRender(seriesModel: BarSeriesModel) {
123 124
        this._clear();
        this._updateDrawMode(seriesModel);
125
    }
126

127 128
    incrementalRender(
        params: StageHandlerProgressParams, seriesModel: BarSeriesModel) {
129 130
        // Do not support progressive in normal mode.
        this._incrementalRenderLarge(params, seriesModel);
131
    }
132

133
    _updateDrawMode(seriesModel: BarSeriesModel) {
134
        const isLargeDraw = seriesModel.pipelineContext.large;
135
        if (this._isLargeDraw == null || isLargeDraw !== this._isLargeDraw) {
136 137 138
            this._isLargeDraw = isLargeDraw;
            this._clear();
        }
139
    }
L
lang 已提交
140

141
    _renderNormal(seriesModel: BarSeriesModel, ecModel: GlobalModel, api: ExtensionAPI) {
142
        const that = this;
143 144 145
        const group = this.group;
        const data = seriesModel.getData();
        const oldData = this._data;
1
100pah 已提交
146

147 148
        const coord = seriesModel.coordinateSystem;
        const baseAxis = coord.getBaseAxis();
149
        let valueAxis: Axis2D;
150
        let isHorizontalOrRadial: boolean;
L
lang 已提交
151

S
sushuang 已提交
152
        if (coord.type === 'cartesian2d') {
153
            isHorizontalOrRadial = (baseAxis as Axis2D).isHorizontal();
154
            valueAxis = coord.getOtherAxis(baseAxis as Axis2D);
S
sushuang 已提交
155 156 157 158
        }
        else if (coord.type === 'polar') {
            isHorizontalOrRadial = baseAxis.dim === 'angle';
        }
O
Ovilia 已提交
159

160
        const animationModel = seriesModel.isAnimationEnabled() ? seriesModel : null;
161 162 163
        const axisAnimationModel = baseAxis.model;

        const axis2DModel = (baseAxis as Axis2D).model;
O
Ovilia 已提交
164 165 166
        const axisSort = coord.type === 'cartesian2d' && axis2DModel.get('sort')
            && axis2DModel.get('sortSeriesIndex') === seriesModel.seriesIndex;
        const realtimeSort = axisSort && axis2DModel.get('realtimeSort');
O
Ovilia 已提交
167

168 169
        const needsClip = seriesModel.get('clip', true);
        const coordSysClipArea = getClipArea(coord, data);
170 171 172 173 174
        // If there is clipPath created in large mode. Remove it.
        group.removeClipPath();
        // We don't use clipPath in normal mode because we needs a perfect animation
        // And don't want the label are clipped.

175
        const roundCap = seriesModel.get('roundCap', true);
O
Ovilia 已提交
176

177 178
        const drawBackground = seriesModel.get('showBackground', true);
        const backgroundModel = seriesModel.getModel('backgroundStyle');
179

180 181
        const bgEls: BarView['_backgroundEls'] = [];
        const oldBgEls = this._backgroundEls;
182

183
        let during: () => void = null;
O
Ovilia 已提交
184 185
        if (coord.type === 'cartesian2d') {
            const oldOrder = (baseAxis.scale as OrdinalScale).getCategorySortInfo();
186 187 188
            const orderMap = (idx: number) => {
                return data.get(valueAxis.dim, idx) as number;
            };
O
Ovilia 已提交
189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207

            if (realtimeSort) {
                // Sort in animation during
                const isOrderChanged = this._isDataOrderChanged(data, orderMap, oldOrder);
                during = isOrderChanged
                    ? () => {
                        const orderMap = (idx: number) => {
                            const shape = (data.getItemGraphicEl(idx) as Rect).shape;
                            return isHorizontalOrRadial ? shape.y + shape.height : shape.x + shape.width;
                        };
                        that._updateSort(data, orderMap, baseAxis as Axis2D, api);
                    }
                    : null;

            }
            else if (axisSort) {
                // Sort now in the first frame
                this._updateSort(data, orderMap, baseAxis as Axis2D, api);
            }
208 209 210
        }


S
sushuang 已提交
211 212
        data.diff(oldData)
            .add(function (dataIndex) {
213 214
                const itemModel = data.getItemModel(dataIndex);
                const layout = getLayout[coord.type](data, dataIndex, itemModel);
215 216

                if (drawBackground) {
217
                    const bgEl = createBackgroundEl(
218 219
                        coord, isHorizontalOrRadial, layout
                    );
220
                    bgEl.useStyle(backgroundModel.getItemStyle());
221 222 223
                    bgEls[dataIndex] = bgEl;
                }

224
                // If dataZoom in filteMode: 'empty', the baseValue can be set as NaN in "axisProxy".
S
sushuang 已提交
225 226 227
                if (!data.hasValue(dataIndex)) {
                    return;
                }
228

P
pissang 已提交
229 230 231
                if (needsClip) {
                    // Clip will modify the layout params.
                    // And return a boolean to determine if the shape are fully clipped.
232
                    const isClipped = clip[coord.type](coordSysClipArea, layout);
P
pissang 已提交
233
                    if (isClipped) {
1
100pah 已提交
234
                        // group.remove(el);
P
pissang 已提交
235 236 237 238
                        return;
                    }
                }

239
                const el = elementCreator[coord.type](
240
                    dataIndex, layout, isHorizontalOrRadial, animationModel, false, during, roundCap
S
sushuang 已提交
241 242 243 244 245 246 247 248 249 250
                );
                data.setItemGraphicEl(dataIndex, el);
                group.add(el);

                updateStyle(
                    el, data, dataIndex, itemModel, layout,
                    seriesModel, isHorizontalOrRadial, coord.type === 'polar'
                );
            })
            .update(function (newIndex, oldIndex) {
251 252
                const itemModel = data.getItemModel(newIndex);
                const layout = getLayout[coord.type](data, newIndex, itemModel);
S
sushuang 已提交
253

254
                if (drawBackground) {
255
                    const bgEl = oldBgEls[oldIndex];
256
                    bgEl.useStyle(backgroundModel.getItemStyle());
257 258
                    bgEls[newIndex] = bgEl;

259
                    const shape = createBackgroundShape(isHorizontalOrRadial, layout, coord);
260 261
                    updateProps(
                        bgEl as Path, { shape: shape }, animationModel, newIndex
262
                    );
263 264
                }

265
                let el = oldData.getItemGraphicEl(oldIndex) as BarPossiblePath;
S
sushuang 已提交
266 267 268 269
                if (!data.hasValue(newIndex)) {
                    group.remove(el);
                    return;
                }
L
lang 已提交
270

P
pissang 已提交
271
                if (needsClip) {
272
                    const isClipped = clip[coord.type](coordSysClipArea, layout);
P
pissang 已提交
273 274 275 276 277 278
                    if (isClipped) {
                        group.remove(el);
                        return;
                    }
                }

S
sushuang 已提交
279
                if (el) {
280
                    clearStates(el);
281 282

                    if (coord.type === 'cartesian2d'
O
Ovilia 已提交
283
                        && baseAxis.type === 'category' && (baseAxis as Axis2D).model.get('sort')
284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315
                    ) {
                        const rect = layout as RectShape;
                        let seriesShape, axisShape;
                        if (baseAxis.dim === 'x') {
                            axisShape = {
                                x: rect.x
                            };
                            seriesShape = {
                                y: rect.y,
                                width: rect.width,
                                height: rect.height
                            };
                        }
                        else {
                            axisShape = {
                                y: rect.y
                            };
                            seriesShape = {
                                x: rect.x,
                                width: rect.width,
                                height: rect.height
                            };
                        }

                        updateProps(el as Path, { shape: seriesShape }, animationModel, newIndex, null, during);
                        updateProps(el as Path, { shape: axisShape }, axisAnimationModel, newIndex, null);
                    }
                    else {
                        updateProps(el as Path, {
                            shape: layout
                        }, animationModel, newIndex, null);
                    }
S
sushuang 已提交
316 317 318
                }
                else {
                    el = elementCreator[coord.type](
319
                        newIndex, layout, isHorizontalOrRadial, animationModel, true, during, roundCap
P
pah100 已提交
320
                    );
S
sushuang 已提交
321
                }
1
100pah 已提交
322

S
sushuang 已提交
323 324 325 326 327 328 329 330 331 332
                data.setItemGraphicEl(newIndex, el);
                // Add back
                group.add(el);

                updateStyle(
                    el, data, newIndex, itemModel, layout,
                    seriesModel, isHorizontalOrRadial, coord.type === 'polar'
                );
            })
            .remove(function (dataIndex) {
333
                const el = oldData.getItemGraphicEl(dataIndex);
S
sushuang 已提交
334
                if (coord.type === 'cartesian2d') {
335
                    el && removeRect(dataIndex, animationModel, el as Rect);
S
sushuang 已提交
336 337
                }
                else {
338
                    el && removeSector(dataIndex, animationModel, el as Sector);
S
sushuang 已提交
339 340 341 342
                }
            })
            .execute();

343
        const bgGroup = this._backgroundGroup || (this._backgroundGroup = new Group());
344 345
        bgGroup.removeAll();

346
        for (let i = 0; i < bgEls.length; ++i) {
347 348 349 350 351
            bgGroup.add(bgEls[i]);
        }
        group.add(bgGroup);
        this._backgroundEls = bgEls;

S
sushuang 已提交
352
        this._data = data;
353
    }
S
sushuang 已提交
354

355
    _renderLarge(seriesModel: BarSeriesModel, ecModel: GlobalModel, api: ExtensionAPI) {
356 357
        this._clear();
        createLarge(seriesModel, this.group);
358 359

        // Use clipPath in large mode.
360
        const clipPath = seriesModel.get('clip', true)
361 362 363 364 365 366
            ? createClipPath(seriesModel.coordinateSystem, false, seriesModel)
            : null;
        if (clipPath) {
            this.group.setClipPath(clipPath);
        }
        else {
367
            this.group.removeClipPath();
368
        }
369
    }
370

371
    _incrementalRenderLarge(params: StageHandlerProgressParams, seriesModel: BarSeriesModel) {
372
        this._removeBackground();
373
        createLarge(seriesModel, this.group, true);
374
    }
375

376 377 378
    _dataSort(
        data: List<BarSeriesModel, DefaultDataVisual>,
        map: ((idx: number) => number)
O
Ovilia 已提交
379 380 381 382 383 384 385
    ): OrdinalSortInfo[] {
        type SortValueInfo = {
            mappedValue: number,
            ordinalNumber: OrdinalNumber,
            beforeSortIndex: number
        };
        const info: SortValueInfo[] = [];
386
        data.each(idx => {
O
Ovilia 已提交
387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406
            info.push({
                mappedValue: map(idx),
                ordinalNumber: idx,
                beforeSortIndex: null
            });
        });

        info.sort((a, b) => {
            return b.mappedValue - a.mappedValue;
        });

        // Update beforeSortIndex
        for (let i = 0; i < info.length; ++i) {
            info[info[i].ordinalNumber].beforeSortIndex = i;
        }
        return info.map(item => {
            return {
                ordinalNumber: item.ordinalNumber,
                beforeSortIndex: item.beforeSortIndex
            };
407
        });
O
Ovilia 已提交
408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428
    }

    _isDataOrderChanged(
        data: List<BarSeriesModel, DefaultDataVisual>,
        orderMap: ((idx: number) => number),
        oldOrder: OrdinalSortInfo[]
    ): boolean {
        if (!oldOrder || oldOrder.length === 0) {
            // If no old order, return if has new data
            return data.count() !== 0;
        }

        let lastValue = Number.MAX_VALUE;
        for (let i = 0; i < oldOrder.length; ++i) {
            const value = orderMap(oldOrder[i].ordinalNumber);
            if (value > lastValue) {
                return true;
            }
            lastValue = value;
        }
        return false;
429 430 431 432
    }

    _updateSort(
        data: List<BarSeriesModel, DefaultDataVisual>,
O
Ovilia 已提交
433
        orderMap: ((idx: number) => number),
434 435 436
        baseAxis: Axis2D,
        api: ExtensionAPI
    ) {
O
Ovilia 已提交
437 438 439 440 441 442
        const oldOrder = (baseAxis.scale as OrdinalScale).getCategorySortInfo();
        const isOrderChanged = this._isDataOrderChanged(data, orderMap, oldOrder);
        if (isOrderChanged) {
            // re-sort and update in axis
            const sortInfo = this._dataSort(data, orderMap);
            baseAxis.setCategorySortInfo(sortInfo);
443 444

            const action = {
O
Ovilia 已提交
445
                type: 'changeAxisOrder',
446 447 448 449 450 451 452
                componentType: baseAxis.dim + 'Axis',
                axisId: baseAxis.index
            } as Payload;
            api.dispatchAction(action);
        }
    }

453
    remove(ecModel?: GlobalModel) {
454
        this._clear(ecModel);
455
    }
456

457
    _clear(ecModel?: GlobalModel) {
458 459
        const group = this.group;
        const data = this._data;
S
sushuang 已提交
460
        if (ecModel && ecModel.get('animation') && data && !this._isLargeDraw) {
461 462 463
            this._removeBackground();
            this._backgroundEls = [];

464
            data.eachItemGraphicEl(function (el: Sector | Rect) {
S
sushuang 已提交
465
                if (el.type === 'sector') {
466
                    removeSector(getECData(el).dataIndex, ecModel, el as (Sector));
S
sushuang 已提交
467 468
                }
                else {
469
                    removeRect(getECData(el).dataIndex, ecModel, el as (Rect));
S
sushuang 已提交
470 471
                }
            });
L
lang 已提交
472
        }
S
sushuang 已提交
473 474 475
        else {
            group.removeAll();
        }
476
        this._data = null;
477
    }
478

479
    _removeBackground() {
480 481
        this.group.remove(this._backgroundGroup);
        this._backgroundGroup = null;
S
sushuang 已提交
482
    }
483
}
484

485 486 487
interface Clipper {
    (coordSysBoundingRect: RectLike, layout: RectLayout | SectorLayout): boolean
}
1
100pah 已提交
488
const clip: {
489 490
    [key in 'cartesian2d' | 'polar']: Clipper
} = {
491
    cartesian2d(coordSysBoundingRect: RectLike, layout: Rect['shape']) {
492 493
        const signWidth = layout.width < 0 ? -1 : 1;
        const signHeight = layout.height < 0 ? -1 : 1;
P
pissang 已提交
494 495 496 497 498 499 500 501 502 503
        // Needs positive width and height
        if (signWidth < 0) {
            layout.x += layout.width;
            layout.width = -layout.width;
        }
        if (signHeight < 0) {
            layout.y += layout.height;
            layout.height = -layout.height;
        }

504 505 506 507
        const x = mathMax(layout.x, coordSysBoundingRect.x);
        const x2 = mathMin(layout.x + layout.width, coordSysBoundingRect.x + coordSysBoundingRect.width);
        const y = mathMax(layout.y, coordSysBoundingRect.y);
        const y2 = mathMin(layout.y + layout.height, coordSysBoundingRect.y + coordSysBoundingRect.height);
P
pissang 已提交
508 509 510 511 512 513

        layout.x = x;
        layout.y = y;
        layout.width = x2 - x;
        layout.height = y2 - y;

514
        const clipped = layout.width < 0 || layout.height < 0;
P
pissang 已提交
515 516 517 518 519 520 521 522 523 524 525 526 527 528

        // Reverse back
        if (signWidth < 0) {
            layout.x += layout.width;
            layout.width = -layout.width;
        }
        if (signHeight < 0) {
            layout.y += layout.height;
            layout.height = -layout.height;
        }

        return clipped;
    },

529
    polar() {
P
pissang 已提交
530 531 532 533
        return false;
    }
};

534 535 536
interface ElementCreator {
    (
        dataIndex: number, layout: RectLayout | SectorLayout, isHorizontalOrRadial: boolean,
537 538
        animationModel: BarSeriesModel, isUpdate: boolean, during: () => void,
        roundCap?: boolean
P
pissang 已提交
539
    ): BarPossiblePath
540 541
}

1
100pah 已提交
542
const elementCreator: {
543 544
    [key in 'polar' | 'cartesian2d']: ElementCreator
} = {
P
pah100 已提交
545

546 547
    cartesian2d(
        dataIndex, layout: RectLayout, isHorizontal,
548
        animationModel, isUpdate, during
S
sushuang 已提交
549
    ) {
550
        const rect = new Rect({
551 552 553
            shape: zrUtil.extend({}, layout),
            z2: 1
        });
554
        // rect.autoBatch = true;
555 556

        rect.name = 'item';
S
sushuang 已提交
557 558 559

        // Animation
        if (animationModel) {
560 561 562
            const rectShape = rect.shape;
            const animateProperty = isHorizontal ? 'height' : 'width' as 'width' | 'height';
            const animateTarget = {} as RectShape;
S
sushuang 已提交
563 564
            rectShape[animateProperty] = 0;
            animateTarget[animateProperty] = layout[animateProperty];
565
            (isUpdate ? updateProps : initProps)(rect, {
S
sushuang 已提交
566
                shape: animateTarget
567
            }, animationModel, dataIndex, null, during);
S
sushuang 已提交
568
        }
1
100pah 已提交
569

S
sushuang 已提交
570 571
        return rect;
    },
1
100pah 已提交
572

573 574
    polar(
        dataIndex: number, layout: SectorLayout, isRadial: boolean,
575
        animationModel, isUpdate, roundCap
S
sushuang 已提交
576
    ) {
S
sushuang 已提交
577 578 579 580
        // Keep the same logic with bar in catesion: use end value to control
        // direction. Notice that if clockwise is true (by default), the sector
        // will always draw clockwisely, no matter whether endAngle is greater
        // or less than startAngle.
581
        const clockwise = layout.startAngle < layout.endAngle;
582

583
        const ShapeClass = (!isRadial && roundCap) ? Sausage : Sector;
584

585
        const sector = new ShapeClass({
586 587
            shape: zrUtil.defaults({clockwise: clockwise}, layout),
            z2: 1
S
sushuang 已提交
588
        });
S
sushuang 已提交
589

590 591
        sector.name = 'item';

S
sushuang 已提交
592 593
        // Animation
        if (animationModel) {
594 595 596
            const sectorShape = sector.shape;
            const animateProperty = isRadial ? 'r' : 'endAngle' as 'r' | 'endAngle';
            const animateTarget = {} as SectorShape;
S
sushuang 已提交
597 598
            sectorShape[animateProperty] = isRadial ? 0 : layout.startAngle;
            animateTarget[animateProperty] = layout[animateProperty];
599
            (isUpdate ? updateProps : initProps)(sector, {
S
sushuang 已提交
600 601 602
                shape: animateTarget
            }, animationModel, dataIndex);
        }
O
Ovilia 已提交
603

S
sushuang 已提交
604 605 606 607
        return sector;
    }
};

608 609 610
function removeRect(
    dataIndex: number,
    animationModel: BarSeriesModel | GlobalModel,
611
    el: Rect
612
) {
S
sushuang 已提交
613
    // Not show text when animating
P
pissang 已提交
614
    el.removeTextContent();
615
    updateProps(el, {
S
sushuang 已提交
616 617
        shape: {
            width: 0
O
Ovilia 已提交
618
        }
S
sushuang 已提交
619 620 621 622 623
    }, animationModel, dataIndex, function () {
        el.parent && el.parent.remove(el);
    });
}

624 625 626
function removeSector(
    dataIndex: number,
    animationModel: BarSeriesModel | GlobalModel,
627
    el: Sector
628
) {
S
sushuang 已提交
629
    // Not show text when animating
P
pissang 已提交
630
    el.removeTextContent();
631
    updateProps(el, {
S
sushuang 已提交
632 633 634 635 636 637 638 639
        shape: {
            r: el.shape.r0
        }
    }, animationModel, dataIndex, function () {
        el.parent && el.parent.remove(el);
    });
}

640 641 642
interface GetLayout {
    (data: List, dataIndex: number, itemModel: Model<BarDataItemOption>): RectLayout | SectorLayout
}
1
100pah 已提交
643
const getLayout: {
644 645 646
    [key in 'cartesian2d' | 'polar']: GetLayout
} = {
    cartesian2d(data, dataIndex, itemModel): RectLayout {
647 648
        const layout = data.getItemLayout(dataIndex) as RectLayout;
        const fixedLineWidth = getLineWidth(itemModel, layout);
S
sushuang 已提交
649 650

        // fix layout with lineWidth
651 652
        const signX = layout.width > 0 ? 1 : -1;
        const signY = layout.height > 0 ? 1 : -1;
S
sushuang 已提交
653 654 655 656 657 658 659 660
        return {
            x: layout.x + signX * fixedLineWidth / 2,
            y: layout.y + signY * fixedLineWidth / 2,
            width: layout.width - signX * fixedLineWidth,
            height: layout.height - signY * fixedLineWidth
        };
    },

661
    polar(data, dataIndex, itemModel): SectorLayout {
662
        const layout = data.getItemLayout(dataIndex);
S
sushuang 已提交
663 664 665 666 667 668 669
        return {
            cx: layout.cx,
            cy: layout.cy,
            r0: layout.r0,
            r: layout.r,
            startAngle: layout.startAngle,
            endAngle: layout.endAngle
670
        } as SectorLayout;
1
100pah 已提交
671
    }
S
sushuang 已提交
672 673
};

674
function isZeroOnPolar(layout: SectorLayout) {
675 676 677 678 679
    return layout.startAngle != null
        && layout.endAngle != null
        && layout.startAngle === layout.endAngle;
}

S
sushuang 已提交
680
function updateStyle(
P
pissang 已提交
681
    el: BarPossiblePath,
682 683 684 685 686 687
    data: List, dataIndex: number,
    itemModel: Model<BarDataItemOption>,
    layout: RectLayout | SectorLayout,
    seriesModel: BarSeriesModel,
    isHorizontal: boolean,
    isPolar: boolean
S
sushuang 已提交
688
) {
689 690
    const style = data.getItemVisual(dataIndex, 'style');
    const hoverStyle = itemModel.getModel(['emphasis', 'itemStyle']).getItemStyle();
S
sushuang 已提交
691 692

    if (!isPolar) {
693
        (el as Rect).setShape('r', itemModel.get(['itemStyle', 'barBorderRadius']) || 0);
O
Ovilia 已提交
694 695
    }

696 697 698
    el.useStyle(style);

    el.ignore = isZeroOnPolar(layout as SectorLayout);
1
100pah 已提交
699

700
    const cursorStyle = itemModel.getShallow('cursor');
P
pissang 已提交
701
    cursorStyle && (el as Path).attr('cursor', cursorStyle);
1
100pah 已提交
702

S
sushuang 已提交
703
    if (!isPolar) {
704
        const labelPositionOutside = isHorizontal
P
pissang 已提交
705 706 707
            ? ((layout as RectLayout).height > 0 ? 'bottom' as const : 'top' as const)
            : ((layout as RectLayout).width > 0 ? 'left' as const : 'right' as const);

708 709
        const labelModel = itemModel.getModel('label');
        const hoverLabelModel = itemModel.getModel(['emphasis', 'label']);
P
pissang 已提交
710 711 712 713 714 715
        setLabelStyle(
            el, labelModel, hoverLabelModel,
            {
                labelFetcher: seriesModel,
                labelDataIndex: dataIndex,
                defaultText: getDefaultLabel(seriesModel.getData(), dataIndex),
716
                autoColor: style.fill as ColorString,
P
pissang 已提交
717 718
                defaultOutsidePosition: labelPositionOutside
            }
S
sushuang 已提交
719
        );
1
100pah 已提交
720
    }
721
    if (isZeroOnPolar(layout as SectorLayout)) {
Z
zhangyi 已提交
722 723
        hoverStyle.fill = hoverStyle.stroke = 'none';
    }
P
pissang 已提交
724
    enableHoverEmphasis(el, hoverStyle);
S
sushuang 已提交
725
}
1
tweak  
100pah 已提交
726

S
sushuang 已提交
727
// In case width or height are too small.
728 729 730 731
function getLineWidth(
    itemModel: Model<BarSeriesOption>,
    rawLayout: RectLayout
) {
732
    const lineWidth = itemModel.get(BAR_BORDER_WIDTH_QUERY) || 0;
733
    // width or height may be NaN for empty data
734 735
    const width = isNaN(rawLayout.width) ? Number.MAX_VALUE : Math.abs(rawLayout.width);
    const height = isNaN(rawLayout.height) ? Number.MAX_VALUE : Math.abs(rawLayout.height);
736
    return Math.min(lineWidth, width, height);
S
sushuang 已提交
737
}
738

739
class LagePathShape {
1
100pah 已提交
740
    points: ArrayLike<number>;
741 742 743 744
}
interface LargePathProps extends PathProps {
    shape?: LagePathShape
}
P
pissang 已提交
745
class LargePath extends Path<LargePathProps> {
1
100pah 已提交
746
    type = 'largeBar';
747

1
100pah 已提交
748 749 750 751 752 753
    shape: LagePathShape;
;
    __startPoint: number[];
    __baseDimIdx: number;
    __largeDataIndices: ArrayLike<number>;
    __barWidth: number;
754

755
    constructor(opts?: LargePathProps) {
P
pissang 已提交
756 757 758 759 760
        super(opts);
    }

    getDefaultShape() {
        return new LagePathShape();
761
    }
762

763
    buildPath(ctx: CanvasRenderingContext2D, shape: LagePathShape) {
764 765
        // Drawing lines is more efficient than drawing
        // a whole line or drawing rects.
766 767 768
        const points = shape.points;
        const startPoint = this.__startPoint;
        const baseDimIdx = this.__baseDimIdx;
769

770
        for (let i = 0; i < points.length; i += 2) {
771
            startPoint[baseDimIdx] = points[i + baseDimIdx];
772 773 774 775
            ctx.moveTo(startPoint[0], startPoint[1]);
            ctx.lineTo(points[i], points[i + 1]);
        }
    }
776
}
777

778 779 780 781 782
function createLarge(
    seriesModel: BarSeriesModel,
    group: Group,
    incremental?: boolean
) {
783
    // TODO support polar
784 785 786
    const data = seriesModel.getData();
    const startPoint = [];
    const baseDimIdx = data.getLayout('valueAxisHorizontal') ? 1 : 0;
787
    startPoint[1 - baseDimIdx] = data.getLayout('valueAxisStart');
788

789 790
    const largeDataIndices = data.getLayout('largeDataIndices');
    const barWidth = data.getLayout('barWidth');
791

792 793
    const backgroundModel = seriesModel.getModel('backgroundStyle');
    const drawBackground = seriesModel.get('showBackground', true);
794 795

    if (drawBackground) {
796 797
        const points = data.getLayout('largeBackgroundPoints');
        const backgroundStartPoint: number[] = [];
798 799
        backgroundStartPoint[1 - baseDimIdx] = data.getLayout('backgroundStart');

800
        const bgEl = new LargePath({
801 802 803 804 805
            shape: {points: points},
            incremental: !!incremental,
            silent: true,
            z2: 0
        });
806 807 808 809
        bgEl.__startPoint = backgroundStartPoint;
        bgEl.__baseDimIdx = baseDimIdx;
        bgEl.__largeDataIndices = largeDataIndices;
        bgEl.__barWidth = barWidth;
810 811 812 813
        setLargeBackgroundStyle(bgEl, backgroundModel, data);
        group.add(bgEl);
    }

814
    const el = new LargePath({
815
        shape: {points: data.getLayout('largePoints')},
816
        incremental: !!incremental
817
    });
818 819 820 821
    el.__startPoint = startPoint;
    el.__baseDimIdx = baseDimIdx;
    el.__largeDataIndices = largeDataIndices;
    el.__barWidth = barWidth;
822 823
    group.add(el);
    setLargeStyle(el, seriesModel, data);
824 825

    // Enable tooltip and user mouse/touch event handlers.
826
    getECData(el).seriesIndex = seriesModel.seriesIndex;
827 828 829 830 831 832 833 834

    if (!seriesModel.get('silent')) {
        el.on('mousedown', largePathUpdateDataIndex);
        el.on('mousemove', largePathUpdateDataIndex);
    }
}

// Use throttle to avoid frequently traverse to find dataIndex.
1
100pah 已提交
835
const largePathUpdateDataIndex = throttle(function (this: LargePath, event: ZRElementEvent) {
836 837
    const largePath = this;
    const dataIndex = largePathFindDataIndex(largePath, event.offsetX, event.offsetY);
838
    getECData(largePath).dataIndex = dataIndex >= 0 ? dataIndex : null;
839 840
}, 30, false);

841
function largePathFindDataIndex(largePath: LargePath, x: number, y: number) {
842 843 844 845 846 847
    const baseDimIdx = largePath.__baseDimIdx;
    const valueDimIdx = 1 - baseDimIdx;
    const points = largePath.shape.points;
    const largeDataIndices = largePath.__largeDataIndices;
    const barWidthHalf = Math.abs(largePath.__barWidth / 2);
    const startValueVal = largePath.__startPoint[valueDimIdx];
848 849 850

    _eventPos[0] = x;
    _eventPos[1] = y;
851 852 853 854
    const pointerBaseVal = _eventPos[baseDimIdx];
    const pointerValueVal = _eventPos[1 - baseDimIdx];
    const baseLowerBound = pointerBaseVal - barWidthHalf;
    const baseUpperBound = pointerBaseVal + barWidthHalf;
855 856

    for (let i = 0, len = points.length / 2; i < len; i++) {
857 858 859
        const ii = i * 2;
        const barBaseVal = points[ii + baseDimIdx];
        const barValueVal = points[ii + valueDimIdx];
860 861 862 863 864 865 866 867 868 869 870 871 872
        if (
            barBaseVal >= baseLowerBound && barBaseVal <= baseUpperBound
            && (
                startValueVal <= barValueVal
                    ? (pointerValueVal >= startValueVal && pointerValueVal <= barValueVal)
                    : (pointerValueVal >= barValueVal && pointerValueVal <= startValueVal)
            )
        ) {
            return largeDataIndices[i];
        }
    }

    return -1;
873 874
}

875 876 877 878 879
function setLargeStyle(
    el: LargePath,
    seriesModel: BarSeriesModel,
    data: List
) {
880
    const globalStyle = data.getVisual('style');
881

882 883
    el.useStyle(zrUtil.extend({}, globalStyle));
    // Use stroke instead of fill.
884
    el.style.fill = null;
885
    el.style.stroke = globalStyle.fill;
886 887 888
    el.style.lineWidth = data.getLayout('barWidth');
}

889 890 891 892 893
function setLargeBackgroundStyle(
    el: LargePath,
    backgroundModel: Model<BarSeriesOption['backgroundStyle']>,
    data: List
) {
894 895
    const borderColor = backgroundModel.get('borderColor') || backgroundModel.get('color');
    const itemStyle = backgroundModel.getItemStyle(['color', 'borderColor']);
896 897 898 899

    el.useStyle(itemStyle);
    el.style.fill = null;
    el.style.stroke = borderColor;
900
    el.style.lineWidth = data.getLayout('barWidth') as number;
901 902
}

903 904 905 906 907
function createBackgroundShape(
    isHorizontalOrRadial: boolean,
    layout: SectorLayout | RectLayout,
    coord: CoordSysOfBar
): SectorShape | RectShape {
908
    if (isCoordinateSystemType<Cartesian2D>(coord, 'cartesian2d')) {
909 910 911 912 913 914 915 916
        const rectShape = layout as RectShape;
        const coordLayout = coord.getArea();
        return {
            x: isHorizontalOrRadial ? rectShape.x : coordLayout.x,
            y: isHorizontalOrRadial ? coordLayout.y : rectShape.y,
            width: isHorizontalOrRadial ? rectShape.width : coordLayout.width,
            height: isHorizontalOrRadial ? coordLayout.height : rectShape.height
        } as RectShape;
917 918
    }
    else {
919 920
        const coordLayout = coord.getArea();
        const sectorShape = layout as SectorShape;
921 922 923
        return {
            cx: coordLayout.cx,
            cy: coordLayout.cy,
924 925 926 927 928
            r0: isHorizontalOrRadial ? coordLayout.r0 : sectorShape.r0,
            r: isHorizontalOrRadial ? coordLayout.r : sectorShape.r,
            startAngle: isHorizontalOrRadial ? sectorShape.startAngle : 0,
            endAngle: isHorizontalOrRadial ? sectorShape.endAngle : Math.PI * 2
        } as SectorShape;
929 930 931
    }
}

932 933 934 935
function createBackgroundEl(
    coord: CoordSysOfBar,
    isHorizontalOrRadial: boolean,
    layout: SectorLayout | RectLayout
936
): Rect | Sector {
937
    const ElementClz = coord.type === 'polar' ? Sector : Rect;
938
    return new ElementClz({
939
        shape: createBackgroundShape(isHorizontalOrRadial, layout, coord) as any,
940 941 942 943
        silent: true,
        z2: 0
    });
}
944 945 946 947

ChartView.registerClass(BarView);

export default BarView;