BarView.ts 26.3 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 38 39 40
import ChartView from '../../view/Chart';
import List from '../../data/List';
import GlobalModel from '../../model/Global';
import ExtensionAPI from '../../ExtensionAPI';
41
import { StageHandlerProgressParams, ZRElementEvent, ColorString } 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 50 51 52 53 54 55 56

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 已提交
57 58
type RectShape = Rect['shape'];
type SectorShape = Sector['shape'];
59 60 61 62

type SectorLayout = SectorShape;
type RectLayout = RectShape;

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

65
function getClipArea(coord: CoordSysOfBar, data: List) {
1
100pah 已提交
66
    let coordSysClipArea;
67
    if (isCoordinateSystemType<Cartesian2D>(coord, 'cartesian2d')) {
1
100pah 已提交
68
        coordSysClipArea = coord.getArea && coord.getArea();
69
        const baseAxis = coord.getBaseAxis();
70 71 72 73
        // 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) {
74
            const expandWidth = data.getLayout('bandWidth');
75 76 77 78 79 80 81 82 83 84 85 86 87 88
            if (baseAxis.isHorizontal()) {
                coordSysClipArea.x -= expandWidth;
                coordSysClipArea.width += expandWidth * 2;
            }
            else {
                coordSysClipArea.y -= expandWidth;
                coordSysClipArea.height += expandWidth * 2;
            }
        }
    }

    return coordSysClipArea;
}

L
lang 已提交
89

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

1
100pah 已提交
94
    private _data: List;
95

1
100pah 已提交
96
    private _isLargeDraw: boolean;
97

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

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

1
100pah 已提交
102
    render(seriesModel: BarSeriesModel, ecModel: GlobalModel, api: ExtensionAPI): void {
103 104
        this._updateDrawMode(seriesModel);

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

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

1
100pah 已提交
119
    incrementalPrepareRender(seriesModel: BarSeriesModel): void {
120 121
        this._clear();
        this._updateDrawMode(seriesModel);
122
    }
123

1
100pah 已提交
124
    incrementalRender(params: StageHandlerProgressParams, seriesModel: BarSeriesModel): void {
125 126
        // Do not support progressive in normal mode.
        this._incrementalRenderLarge(params, seriesModel);
127
    }
128

1
100pah 已提交
129
    private _updateDrawMode(seriesModel: BarSeriesModel): void {
130
        const isLargeDraw = seriesModel.pipelineContext.large;
131
        if (this._isLargeDraw == null || isLargeDraw !== this._isLargeDraw) {
132 133 134
            this._isLargeDraw = isLargeDraw;
            this._clear();
        }
135
    }
L
lang 已提交
136

1
100pah 已提交
137
    private _renderNormal(seriesModel: BarSeriesModel, ecModel: GlobalModel, api: ExtensionAPI): void {
138 139 140
        const group = this.group;
        const data = seriesModel.getData();
        const oldData = this._data;
1
100pah 已提交
141

142 143
        const coord = seriesModel.coordinateSystem;
        const baseAxis = coord.getBaseAxis();
144
        let isHorizontalOrRadial: boolean;
L
lang 已提交
145

S
sushuang 已提交
146
        if (coord.type === 'cartesian2d') {
147
            isHorizontalOrRadial = (baseAxis as Axis2D).isHorizontal();
S
sushuang 已提交
148 149 150 151
        }
        else if (coord.type === 'polar') {
            isHorizontalOrRadial = baseAxis.dim === 'angle';
        }
O
Ovilia 已提交
152

153
        const animationModel = seriesModel.isAnimationEnabled() ? seriesModel : null;
O
Ovilia 已提交
154

155 156
        const needsClip = seriesModel.get('clip', true);
        const coordSysClipArea = getClipArea(coord, data);
157 158 159 160 161
        // 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.

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

164 165
        const drawBackground = seriesModel.get('showBackground', true);
        const backgroundModel = seriesModel.getModel('backgroundStyle');
166

167 168
        const bgEls: BarView['_backgroundEls'] = [];
        const oldBgEls = this._backgroundEls;
169

S
sushuang 已提交
170 171
        data.diff(oldData)
            .add(function (dataIndex) {
172 173
                const itemModel = data.getItemModel(dataIndex);
                const layout = getLayout[coord.type](data, dataIndex, itemModel);
174 175

                if (drawBackground) {
176
                    const bgEl = createBackgroundEl(
177 178
                        coord, isHorizontalOrRadial, layout
                    );
179
                    bgEl.useStyle(backgroundModel.getItemStyle());
180 181 182
                    bgEls[dataIndex] = bgEl;
                }

183
                // If dataZoom in filteMode: 'empty', the baseValue can be set as NaN in "axisProxy".
S
sushuang 已提交
184 185 186
                if (!data.hasValue(dataIndex)) {
                    return;
                }
187

P
pissang 已提交
188 189 190
                if (needsClip) {
                    // Clip will modify the layout params.
                    // And return a boolean to determine if the shape are fully clipped.
191
                    const isClipped = clip[coord.type](coordSysClipArea, layout);
P
pissang 已提交
192
                    if (isClipped) {
1
100pah 已提交
193
                        // group.remove(el);
P
pissang 已提交
194 195 196 197
                        return;
                    }
                }

198
                const el = elementCreator[coord.type](
199
                    dataIndex, layout, isHorizontalOrRadial, animationModel, false, roundCap
S
sushuang 已提交
200 201 202 203 204 205 206 207 208 209
                );
                data.setItemGraphicEl(dataIndex, el);
                group.add(el);

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

213
                if (drawBackground) {
214
                    const bgEl = oldBgEls[oldIndex];
215
                    bgEl.useStyle(backgroundModel.getItemStyle());
216 217
                    bgEls[newIndex] = bgEl;

218
                    const shape = createBackgroundShape(isHorizontalOrRadial, layout, coord);
219 220
                    updateProps(
                        bgEl as Path, { shape: shape }, animationModel, newIndex
221
                    );
222 223
                }

224
                let el = oldData.getItemGraphicEl(oldIndex) as BarPossiblePath;
S
sushuang 已提交
225 226 227 228
                if (!data.hasValue(newIndex)) {
                    group.remove(el);
                    return;
                }
L
lang 已提交
229

P
pissang 已提交
230
                if (needsClip) {
231
                    const isClipped = clip[coord.type](coordSysClipArea, layout);
P
pissang 已提交
232 233 234 235 236 237
                    if (isClipped) {
                        group.remove(el);
                        return;
                    }
                }

S
sushuang 已提交
238
                if (el) {
239
                    clearStates(el);
240
                    updateProps(el as Path, {
241 242
                        shape: layout
                    }, animationModel, newIndex);
S
sushuang 已提交
243 244 245
                }
                else {
                    el = elementCreator[coord.type](
246
                        newIndex, layout, isHorizontalOrRadial, animationModel, true, roundCap
P
pah100 已提交
247
                    );
S
sushuang 已提交
248
                }
1
100pah 已提交
249

S
sushuang 已提交
250 251 252 253 254 255 256 257 258 259
                data.setItemGraphicEl(newIndex, el);
                // Add back
                group.add(el);

                updateStyle(
                    el, data, newIndex, itemModel, layout,
                    seriesModel, isHorizontalOrRadial, coord.type === 'polar'
                );
            })
            .remove(function (dataIndex) {
260
                const el = oldData.getItemGraphicEl(dataIndex);
S
sushuang 已提交
261
                if (coord.type === 'cartesian2d') {
262
                    el && removeRect(dataIndex, animationModel, el as Rect);
S
sushuang 已提交
263 264
                }
                else {
265
                    el && removeSector(dataIndex, animationModel, el as Sector);
S
sushuang 已提交
266 267 268 269
                }
            })
            .execute();

270
        const bgGroup = this._backgroundGroup || (this._backgroundGroup = new Group());
271 272
        bgGroup.removeAll();

273
        for (let i = 0; i < bgEls.length; ++i) {
274 275 276 277 278
            bgGroup.add(bgEls[i]);
        }
        group.add(bgGroup);
        this._backgroundEls = bgEls;

S
sushuang 已提交
279
        this._data = data;
280
    }
S
sushuang 已提交
281

1
100pah 已提交
282
    private _renderLarge(seriesModel: BarSeriesModel, ecModel: GlobalModel, api: ExtensionAPI): void {
283 284
        this._clear();
        createLarge(seriesModel, this.group);
285
        // Use clipPath in large mode.
286
        const clipPath = seriesModel.get('clip', true)
287 288 289 290 291 292
            ? createClipPath(seriesModel.coordinateSystem, false, seriesModel)
            : null;
        if (clipPath) {
            this.group.setClipPath(clipPath);
        }
        else {
293
            this.group.removeClipPath();
294
        }
295
    }
296

1
100pah 已提交
297
    private _incrementalRenderLarge(params: StageHandlerProgressParams, seriesModel: BarSeriesModel): void {
298
        this._removeBackground();
299
        createLarge(seriesModel, this.group, true);
300
    }
301

1
100pah 已提交
302
    remove(ecModel?: GlobalModel): void {
303
        this._clear(ecModel);
304
    }
305

1
100pah 已提交
306
    private _clear(ecModel?: GlobalModel): void {
307 308
        const group = this.group;
        const data = this._data;
S
sushuang 已提交
309
        if (ecModel && ecModel.get('animation') && data && !this._isLargeDraw) {
310 311 312
            this._removeBackground();
            this._backgroundEls = [];

313
            data.eachItemGraphicEl(function (el: Sector | Rect) {
S
sushuang 已提交
314
                if (el.type === 'sector') {
315
                    removeSector(getECData(el).dataIndex, ecModel, el as (Sector));
S
sushuang 已提交
316 317
                }
                else {
318
                    removeRect(getECData(el).dataIndex, ecModel, el as (Rect));
S
sushuang 已提交
319 320
                }
            });
L
lang 已提交
321
        }
S
sushuang 已提交
322 323 324
        else {
            group.removeAll();
        }
325
        this._data = null;
326
    }
327

1
100pah 已提交
328
    private _removeBackground(): void {
329 330
        this.group.remove(this._backgroundGroup);
        this._backgroundGroup = null;
S
sushuang 已提交
331
    }
332
}
333

334 335 336
interface Clipper {
    (coordSysBoundingRect: RectLike, layout: RectLayout | SectorLayout): boolean
}
1
100pah 已提交
337
const clip: {
338 339
    [key in 'cartesian2d' | 'polar']: Clipper
} = {
340
    cartesian2d(coordSysBoundingRect: RectLike, layout: Rect['shape']) {
341 342
        const signWidth = layout.width < 0 ? -1 : 1;
        const signHeight = layout.height < 0 ? -1 : 1;
P
pissang 已提交
343 344 345 346 347 348 349 350 351 352
        // 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;
        }

353 354 355 356
        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 已提交
357 358 359 360 361 362

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

363
        const clipped = layout.width < 0 || layout.height < 0;
P
pissang 已提交
364 365 366 367 368 369 370 371 372 373 374 375 376 377

        // 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;
    },

378
    polar() {
P
pissang 已提交
379 380 381 382
        return false;
    }
};

383 384 385 386
interface ElementCreator {
    (
        dataIndex: number, layout: RectLayout | SectorLayout, isHorizontalOrRadial: boolean,
        animationModel: BarSeriesModel, isUpdate: boolean, roundCap?: boolean
P
pissang 已提交
387
    ): BarPossiblePath
388 389
}

1
100pah 已提交
390
const elementCreator: {
391 392
    [key in 'polar' | 'cartesian2d']: ElementCreator
} = {
P
pah100 已提交
393

394 395
    cartesian2d(
        dataIndex, layout: RectLayout, isHorizontal,
S
sushuang 已提交
396 397
        animationModel, isUpdate
    ) {
398
        const rect = new Rect({
399 400 401
            shape: zrUtil.extend({}, layout),
            z2: 1
        });
402
        // rect.autoBatch = true;
403 404

        rect.name = 'item';
S
sushuang 已提交
405 406 407

        // Animation
        if (animationModel) {
408 409 410
            const rectShape = rect.shape;
            const animateProperty = isHorizontal ? 'height' : 'width' as 'width' | 'height';
            const animateTarget = {} as RectShape;
S
sushuang 已提交
411 412
            rectShape[animateProperty] = 0;
            animateTarget[animateProperty] = layout[animateProperty];
413
            (isUpdate ? updateProps : initProps)(rect, {
S
sushuang 已提交
414 415 416
                shape: animateTarget
            }, animationModel, dataIndex);
        }
1
100pah 已提交
417

S
sushuang 已提交
418 419
        return rect;
    },
1
100pah 已提交
420

421 422
    polar(
        dataIndex: number, layout: SectorLayout, isRadial: boolean,
423
        animationModel, isUpdate, roundCap
S
sushuang 已提交
424
    ) {
S
sushuang 已提交
425 426 427 428
        // 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.
429
        const clockwise = layout.startAngle < layout.endAngle;
430

431
        const ShapeClass = (!isRadial && roundCap) ? Sausage : Sector;
432

433
        const sector = new ShapeClass({
434 435
            shape: zrUtil.defaults({clockwise: clockwise}, layout),
            z2: 1
S
sushuang 已提交
436
        });
S
sushuang 已提交
437

438 439
        sector.name = 'item';

S
sushuang 已提交
440 441
        // Animation
        if (animationModel) {
442 443 444
            const sectorShape = sector.shape;
            const animateProperty = isRadial ? 'r' : 'endAngle' as 'r' | 'endAngle';
            const animateTarget = {} as SectorShape;
S
sushuang 已提交
445 446
            sectorShape[animateProperty] = isRadial ? 0 : layout.startAngle;
            animateTarget[animateProperty] = layout[animateProperty];
447
            (isUpdate ? updateProps : initProps)(sector, {
S
sushuang 已提交
448 449 450
                shape: animateTarget
            }, animationModel, dataIndex);
        }
O
Ovilia 已提交
451

S
sushuang 已提交
452 453 454 455
        return sector;
    }
};

456 457 458
function removeRect(
    dataIndex: number,
    animationModel: BarSeriesModel | GlobalModel,
459
    el: Rect
460
) {
S
sushuang 已提交
461
    // Not show text when animating
P
pissang 已提交
462
    el.removeTextContent();
463
    updateProps(el, {
S
sushuang 已提交
464 465
        shape: {
            width: 0
O
Ovilia 已提交
466
        }
S
sushuang 已提交
467 468 469 470 471
    }, animationModel, dataIndex, function () {
        el.parent && el.parent.remove(el);
    });
}

472 473 474
function removeSector(
    dataIndex: number,
    animationModel: BarSeriesModel | GlobalModel,
475
    el: Sector
476
) {
S
sushuang 已提交
477
    // Not show text when animating
P
pissang 已提交
478
    el.removeTextContent();
479
    updateProps(el, {
S
sushuang 已提交
480 481 482 483 484 485 486 487
        shape: {
            r: el.shape.r0
        }
    }, animationModel, dataIndex, function () {
        el.parent && el.parent.remove(el);
    });
}

488 489 490
interface GetLayout {
    (data: List, dataIndex: number, itemModel: Model<BarDataItemOption>): RectLayout | SectorLayout
}
1
100pah 已提交
491
const getLayout: {
492 493 494
    [key in 'cartesian2d' | 'polar']: GetLayout
} = {
    cartesian2d(data, dataIndex, itemModel): RectLayout {
495 496
        const layout = data.getItemLayout(dataIndex) as RectLayout;
        const fixedLineWidth = getLineWidth(itemModel, layout);
S
sushuang 已提交
497 498

        // fix layout with lineWidth
499 500
        const signX = layout.width > 0 ? 1 : -1;
        const signY = layout.height > 0 ? 1 : -1;
S
sushuang 已提交
501 502 503 504 505 506 507 508
        return {
            x: layout.x + signX * fixedLineWidth / 2,
            y: layout.y + signY * fixedLineWidth / 2,
            width: layout.width - signX * fixedLineWidth,
            height: layout.height - signY * fixedLineWidth
        };
    },

509
    polar(data, dataIndex, itemModel): SectorLayout {
510
        const layout = data.getItemLayout(dataIndex);
S
sushuang 已提交
511 512 513 514 515 516 517
        return {
            cx: layout.cx,
            cy: layout.cy,
            r0: layout.r0,
            r: layout.r,
            startAngle: layout.startAngle,
            endAngle: layout.endAngle
518
        } as SectorLayout;
1
100pah 已提交
519
    }
S
sushuang 已提交
520 521
};

522
function isZeroOnPolar(layout: SectorLayout) {
523 524 525 526 527
    return layout.startAngle != null
        && layout.endAngle != null
        && layout.startAngle === layout.endAngle;
}

S
sushuang 已提交
528
function updateStyle(
P
pissang 已提交
529
    el: BarPossiblePath,
530 531 532 533 534 535
    data: List, dataIndex: number,
    itemModel: Model<BarDataItemOption>,
    layout: RectLayout | SectorLayout,
    seriesModel: BarSeriesModel,
    isHorizontal: boolean,
    isPolar: boolean
S
sushuang 已提交
536
) {
537 538
    const style = data.getItemVisual(dataIndex, 'style');
    const hoverStyle = itemModel.getModel(['emphasis', 'itemStyle']).getItemStyle();
S
sushuang 已提交
539 540

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

544 545 546
    el.useStyle(style);

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

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

S
sushuang 已提交
551
    if (!isPolar) {
552
        const labelPositionOutside = isHorizontal
P
pissang 已提交
553 554 555
            ? ((layout as RectLayout).height > 0 ? 'bottom' as const : 'top' as const)
            : ((layout as RectLayout).width > 0 ? 'left' as const : 'right' as const);

556 557
        const labelModel = itemModel.getModel('label');
        const hoverLabelModel = itemModel.getModel(['emphasis', 'label']);
P
pissang 已提交
558 559 560 561 562 563
        setLabelStyle(
            el, labelModel, hoverLabelModel,
            {
                labelFetcher: seriesModel,
                labelDataIndex: dataIndex,
                defaultText: getDefaultLabel(seriesModel.getData(), dataIndex),
564
                autoColor: style.fill as ColorString,
P
pissang 已提交
565 566
                defaultOutsidePosition: labelPositionOutside
            }
S
sushuang 已提交
567
        );
1
100pah 已提交
568
    }
569
    if (isZeroOnPolar(layout as SectorLayout)) {
Z
zhangyi 已提交
570 571
        hoverStyle.fill = hoverStyle.stroke = 'none';
    }
P
pissang 已提交
572
    enableHoverEmphasis(el, hoverStyle);
S
sushuang 已提交
573
}
1
tweak  
100pah 已提交
574

S
sushuang 已提交
575
// In case width or height are too small.
576 577 578 579
function getLineWidth(
    itemModel: Model<BarSeriesOption>,
    rawLayout: RectLayout
) {
580
    const lineWidth = itemModel.get(BAR_BORDER_WIDTH_QUERY) || 0;
581
    // width or height may be NaN for empty data
582 583
    const width = isNaN(rawLayout.width) ? Number.MAX_VALUE : Math.abs(rawLayout.width);
    const height = isNaN(rawLayout.height) ? Number.MAX_VALUE : Math.abs(rawLayout.height);
584
    return Math.min(lineWidth, width, height);
S
sushuang 已提交
585
}
586

587
class LagePathShape {
1
100pah 已提交
588
    points: ArrayLike<number>;
589 590 591 592
}
interface LargePathProps extends PathProps {
    shape?: LagePathShape
}
P
pissang 已提交
593
class LargePath extends Path<LargePathProps> {
1
100pah 已提交
594
    type = 'largeBar';
595

1
100pah 已提交
596 597 598 599 600 601
    shape: LagePathShape;
;
    __startPoint: number[];
    __baseDimIdx: number;
    __largeDataIndices: ArrayLike<number>;
    __barWidth: number;
602

603
    constructor(opts?: LargePathProps) {
P
pissang 已提交
604 605 606 607 608
        super(opts);
    }

    getDefaultShape() {
        return new LagePathShape();
609
    }
610

611
    buildPath(ctx: CanvasRenderingContext2D, shape: LagePathShape) {
612 613
        // Drawing lines is more efficient than drawing
        // a whole line or drawing rects.
614 615 616
        const points = shape.points;
        const startPoint = this.__startPoint;
        const baseDimIdx = this.__baseDimIdx;
617

618
        for (let i = 0; i < points.length; i += 2) {
619
            startPoint[baseDimIdx] = points[i + baseDimIdx];
620 621 622 623
            ctx.moveTo(startPoint[0], startPoint[1]);
            ctx.lineTo(points[i], points[i + 1]);
        }
    }
624
}
625

626 627 628 629 630
function createLarge(
    seriesModel: BarSeriesModel,
    group: Group,
    incremental?: boolean
) {
631
    // TODO support polar
632 633 634
    const data = seriesModel.getData();
    const startPoint = [];
    const baseDimIdx = data.getLayout('valueAxisHorizontal') ? 1 : 0;
635
    startPoint[1 - baseDimIdx] = data.getLayout('valueAxisStart');
636

637 638
    const largeDataIndices = data.getLayout('largeDataIndices');
    const barWidth = data.getLayout('barWidth');
639

640 641
    const backgroundModel = seriesModel.getModel('backgroundStyle');
    const drawBackground = seriesModel.get('showBackground', true);
642 643

    if (drawBackground) {
644 645
        const points = data.getLayout('largeBackgroundPoints');
        const backgroundStartPoint: number[] = [];
646 647
        backgroundStartPoint[1 - baseDimIdx] = data.getLayout('backgroundStart');

648
        const bgEl = new LargePath({
649 650 651 652 653
            shape: {points: points},
            incremental: !!incremental,
            silent: true,
            z2: 0
        });
654 655 656 657
        bgEl.__startPoint = backgroundStartPoint;
        bgEl.__baseDimIdx = baseDimIdx;
        bgEl.__largeDataIndices = largeDataIndices;
        bgEl.__barWidth = barWidth;
658 659 660 661
        setLargeBackgroundStyle(bgEl, backgroundModel, data);
        group.add(bgEl);
    }

662
    const el = new LargePath({
663
        shape: {points: data.getLayout('largePoints')},
664
        incremental: !!incremental
665
    });
666 667 668 669
    el.__startPoint = startPoint;
    el.__baseDimIdx = baseDimIdx;
    el.__largeDataIndices = largeDataIndices;
    el.__barWidth = barWidth;
670 671
    group.add(el);
    setLargeStyle(el, seriesModel, data);
672 673

    // Enable tooltip and user mouse/touch event handlers.
674
    getECData(el).seriesIndex = seriesModel.seriesIndex;
675 676 677 678 679 680 681 682

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

// Use throttle to avoid frequently traverse to find dataIndex.
1
100pah 已提交
683
const largePathUpdateDataIndex = throttle(function (this: LargePath, event: ZRElementEvent) {
684 685
    const largePath = this;
    const dataIndex = largePathFindDataIndex(largePath, event.offsetX, event.offsetY);
686
    getECData(largePath).dataIndex = dataIndex >= 0 ? dataIndex : null;
687 688
}, 30, false);

689
function largePathFindDataIndex(largePath: LargePath, x: number, y: number) {
690 691 692 693 694 695
    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];
696 697 698

    _eventPos[0] = x;
    _eventPos[1] = y;
699 700 701 702
    const pointerBaseVal = _eventPos[baseDimIdx];
    const pointerValueVal = _eventPos[1 - baseDimIdx];
    const baseLowerBound = pointerBaseVal - barWidthHalf;
    const baseUpperBound = pointerBaseVal + barWidthHalf;
703 704

    for (let i = 0, len = points.length / 2; i < len; i++) {
705 706 707
        const ii = i * 2;
        const barBaseVal = points[ii + baseDimIdx];
        const barValueVal = points[ii + valueDimIdx];
708 709 710 711 712 713 714 715 716 717 718 719 720
        if (
            barBaseVal >= baseLowerBound && barBaseVal <= baseUpperBound
            && (
                startValueVal <= barValueVal
                    ? (pointerValueVal >= startValueVal && pointerValueVal <= barValueVal)
                    : (pointerValueVal >= barValueVal && pointerValueVal <= startValueVal)
            )
        ) {
            return largeDataIndices[i];
        }
    }

    return -1;
721 722
}

723 724 725 726 727
function setLargeStyle(
    el: LargePath,
    seriesModel: BarSeriesModel,
    data: List
) {
728
    const globalStyle = data.getVisual('style');
729

730 731
    el.useStyle(zrUtil.extend({}, globalStyle));
    // Use stroke instead of fill.
732
    el.style.fill = null;
733
    el.style.stroke = globalStyle.fill;
734 735 736
    el.style.lineWidth = data.getLayout('barWidth');
}

737 738 739 740 741
function setLargeBackgroundStyle(
    el: LargePath,
    backgroundModel: Model<BarSeriesOption['backgroundStyle']>,
    data: List
) {
742 743
    const borderColor = backgroundModel.get('borderColor') || backgroundModel.get('color');
    const itemStyle = backgroundModel.getItemStyle(['color', 'borderColor']);
744 745 746 747

    el.useStyle(itemStyle);
    el.style.fill = null;
    el.style.stroke = borderColor;
748
    el.style.lineWidth = data.getLayout('barWidth') as number;
749 750
}

751 752 753 754 755
function createBackgroundShape(
    isHorizontalOrRadial: boolean,
    layout: SectorLayout | RectLayout,
    coord: CoordSysOfBar
): SectorShape | RectShape {
756
    if (isCoordinateSystemType<Cartesian2D>(coord, 'cartesian2d')) {
757 758 759 760 761 762 763 764
        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;
765 766
    }
    else {
767 768
        const coordLayout = coord.getArea();
        const sectorShape = layout as SectorShape;
769 770 771
        return {
            cx: coordLayout.cx,
            cy: coordLayout.cy,
772 773 774 775 776
            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;
777 778 779
    }
}

780 781 782 783
function createBackgroundEl(
    coord: CoordSysOfBar,
    isHorizontalOrRadial: boolean,
    layout: SectorLayout | RectLayout
784
): Rect | Sector {
785
    const ElementClz = coord.type === 'polar' ? Sector : Rect;
786
    return new ElementClz({
787
        shape: createBackgroundShape(isHorizontalOrRadial, layout, coord) as any,
788 789 790 791
        silent: true,
        z2: 0
    });
}
792 793 794 795

ChartView.registerClass(BarView);

export default BarView;