BarView.ts 25.9 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
import {Rect, Sector, getECData, updateProps, initProps, setHoverStyle} from '../../util/graphic';
S
sushuang 已提交
23
import {setLabel} from './helper';
24 25
import {getBarItemStyle} from './barItemStyle';
import Path, { PathProps } from 'zrender/src/graphic/Path';
26
import Group from 'zrender/src/container/Group';
27
import {throttle} from '../../util/throttle';
28
import {createClipPath} from '../helper/createClipPathFromCoordSys';
29
import Sausage from '../../util/shape/sausage';
30 31 32 33
import ChartView from '../../view/Chart';
import List from '../../data/List';
import GlobalModel from '../../model/Global';
import ExtensionAPI from '../../ExtensionAPI';
34
import { StageHandlerProgressParams, ZRElementEvent } from '../../util/types';
35 36 37 38 39
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';
40
import { isCoordinateSystemType } from '../../coord/CoordinateSystem';
41 42 43 44 45 46 47 48

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 已提交
49 50
type RectShape = Rect['shape'];
type SectorShape = Sector['shape'];
51 52 53 54

type SectorLayout = SectorShape;
type RectLayout = RectShape;

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

L
lang 已提交
57

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

    return coordSysClipArea;
}

L
lang 已提交
82

83
class BarView extends ChartView {
1
100pah 已提交
84 85
    static type = 'bar' as const;
    type = BarView.type;
86

1
100pah 已提交
87
    _data: List;
88

1
100pah 已提交
89
    _isLargeDraw: boolean;
90

1
100pah 已提交
91
    _backgroundGroup: Group;
92

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

95
    render(seriesModel: BarSeriesModel, ecModel: GlobalModel, api: ExtensionAPI) {
96 97
        this._updateDrawMode(seriesModel);

98
        let coordinateSystemType = seriesModel.get('coordinateSystem');
L
lang 已提交
99

S
sushuang 已提交
100 101 102
        if (coordinateSystemType === 'cartesian2d'
            || coordinateSystemType === 'polar'
        ) {
103 104 105
            this._isLargeDraw
                ? this._renderLarge(seriesModel, ecModel, api)
                : this._renderNormal(seriesModel, ecModel, api);
S
sushuang 已提交
106 107 108 109
        }
        else if (__DEV__) {
            console.warn('Only cartesian2d and polar supported for bar.');
        }
L
lang 已提交
110

S
sushuang 已提交
111
        return this.group;
112
    }
L
lang 已提交
113

114
    incrementalPrepareRender(seriesModel: BarSeriesModel) {
115 116
        this._clear();
        this._updateDrawMode(seriesModel);
117
    }
118

119 120
    incrementalRender(
        params: StageHandlerProgressParams, seriesModel: BarSeriesModel) {
121 122
        // Do not support progressive in normal mode.
        this._incrementalRenderLarge(params, seriesModel);
123
    }
124

125
    _updateDrawMode(seriesModel: BarSeriesModel) {
126
        let isLargeDraw = seriesModel.pipelineContext.large;
127
        if (this._isLargeDraw == null || isLargeDraw !== this._isLargeDraw) {
128 129 130
            this._isLargeDraw = isLargeDraw;
            this._clear();
        }
131
    }
L
lang 已提交
132

133
    _renderNormal(seriesModel: BarSeriesModel, ecModel: GlobalModel, api: ExtensionAPI) {
134 135 136
        let group = this.group;
        let data = seriesModel.getData();
        let oldData = this._data;
1
100pah 已提交
137

138 139 140
        let coord = seriesModel.coordinateSystem;
        let baseAxis = coord.getBaseAxis();
        let isHorizontalOrRadial: boolean;
L
lang 已提交
141

S
sushuang 已提交
142
        if (coord.type === 'cartesian2d') {
143
            isHorizontalOrRadial = (baseAxis as Axis2D).isHorizontal();
S
sushuang 已提交
144 145 146 147
        }
        else if (coord.type === 'polar') {
            isHorizontalOrRadial = baseAxis.dim === 'angle';
        }
O
Ovilia 已提交
148

149
        let animationModel = seriesModel.isAnimationEnabled() ? seriesModel : null;
O
Ovilia 已提交
150

151 152
        let needsClip = seriesModel.get('clip', true);
        let coordSysClipArea = getClipArea(coord, data);
153 154 155 156 157
        // 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.

158
        let roundCap = seriesModel.get('roundCap', true);
O
Ovilia 已提交
159

160 161
        let drawBackground = seriesModel.get('showBackground', true);
        let backgroundModel = seriesModel.getModel('backgroundStyle');
162

163 164
        let bgEls: BarView['_backgroundEls'] = [];
        let oldBgEls = this._backgroundEls;
165

S
sushuang 已提交
166 167
        data.diff(oldData)
            .add(function (dataIndex) {
168 169
                let itemModel = data.getItemModel(dataIndex);
                let layout = getLayout[coord.type](data, dataIndex, itemModel);
170 171

                if (drawBackground) {
172
                    let bgEl = createBackgroundEl(
173 174 175
                        coord, isHorizontalOrRadial, layout
                    );
                    bgEl.useStyle(getBarItemStyle(backgroundModel));
176 177 178
                    bgEls[dataIndex] = bgEl;
                }

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

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

194
                let el = elementCreator[coord.type](
195
                    dataIndex, layout, isHorizontalOrRadial, animationModel, false, roundCap
S
sushuang 已提交
196 197 198 199 200 201 202 203 204 205
                );
                data.setItemGraphicEl(dataIndex, el);
                group.add(el);

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

209
                if (drawBackground) {
210
                    let bgEl = oldBgEls[oldIndex];
211
                    bgEl.useStyle(getBarItemStyle(backgroundModel));
212 213
                    bgEls[newIndex] = bgEl;

214
                    let shape = createBackgroundShape(isHorizontalOrRadial, layout, coord);
215 216
                    updateProps(
                        bgEl as Path, { shape: shape }, animationModel, newIndex
217
                    );
218 219
                }

220
                let el = oldData.getItemGraphicEl(oldIndex) as BarPossiblePath;
S
sushuang 已提交
221 222 223 224
                if (!data.hasValue(newIndex)) {
                    group.remove(el);
                    return;
                }
L
lang 已提交
225

P
pissang 已提交
226
                if (needsClip) {
227
                    let isClipped = clip[coord.type](coordSysClipArea, layout);
P
pissang 已提交
228 229 230 231 232 233
                    if (isClipped) {
                        group.remove(el);
                        return;
                    }
                }

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

S
sushuang 已提交
245 246 247 248 249 250 251 252 253 254
                data.setItemGraphicEl(newIndex, el);
                // Add back
                group.add(el);

                updateStyle(
                    el, data, newIndex, itemModel, layout,
                    seriesModel, isHorizontalOrRadial, coord.type === 'polar'
                );
            })
            .remove(function (dataIndex) {
255
                let el = oldData.getItemGraphicEl(dataIndex);
S
sushuang 已提交
256
                if (coord.type === 'cartesian2d') {
257
                    el && removeRect(dataIndex, animationModel, el as Rect);
S
sushuang 已提交
258 259
                }
                else {
260
                    el && removeSector(dataIndex, animationModel, el as Sector);
S
sushuang 已提交
261 262 263 264
                }
            })
            .execute();

265
        let bgGroup = this._backgroundGroup || (this._backgroundGroup = new Group());
266 267
        bgGroup.removeAll();

268
        for (let i = 0; i < bgEls.length; ++i) {
269 270 271 272 273
            bgGroup.add(bgEls[i]);
        }
        group.add(bgGroup);
        this._backgroundEls = bgEls;

S
sushuang 已提交
274
        this._data = data;
275
    }
S
sushuang 已提交
276

277
    _renderLarge(seriesModel: BarSeriesModel, ecModel: GlobalModel, api: ExtensionAPI) {
278 279
        this._clear();
        createLarge(seriesModel, this.group);
280 281

        // Use clipPath in large mode.
282
        let clipPath = seriesModel.get('clip', true)
283 284 285 286 287 288
            ? createClipPath(seriesModel.coordinateSystem, false, seriesModel)
            : null;
        if (clipPath) {
            this.group.setClipPath(clipPath);
        }
        else {
289
            this.group.removeClipPath();
290
        }
291
    }
292

293
    _incrementalRenderLarge(params: StageHandlerProgressParams, seriesModel: BarSeriesModel) {
294
        this._removeBackground();
295
        createLarge(seriesModel, this.group, true);
296
    }
297

298
    remove(ecModel?: GlobalModel) {
299
        this._clear(ecModel);
300
    }
301

302
    _clear(ecModel?: GlobalModel) {
303 304
        let group = this.group;
        let data = this._data;
S
sushuang 已提交
305
        if (ecModel && ecModel.get('animation') && data && !this._isLargeDraw) {
306 307 308
            this._removeBackground();
            this._backgroundEls = [];

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

324
    _removeBackground() {
325 326
        this.group.remove(this._backgroundGroup);
        this._backgroundGroup = null;
S
sushuang 已提交
327
    }
328
}
329

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

349 350 351 352
        let x = mathMax(layout.x, coordSysBoundingRect.x);
        let x2 = mathMin(layout.x + layout.width, coordSysBoundingRect.x + coordSysBoundingRect.width);
        let y = mathMax(layout.y, coordSysBoundingRect.y);
        let y2 = mathMin(layout.y + layout.height, coordSysBoundingRect.y + coordSysBoundingRect.height);
P
pissang 已提交
353 354 355 356 357 358

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

359
        let clipped = layout.width < 0 || layout.height < 0;
P
pissang 已提交
360 361 362 363 364 365 366 367 368 369 370 371 372 373

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

374
    polar() {
P
pissang 已提交
375 376 377 378
        return false;
    }
};

379 380 381 382
interface ElementCreator {
    (
        dataIndex: number, layout: RectLayout | SectorLayout, isHorizontalOrRadial: boolean,
        animationModel: BarSeriesModel, isUpdate: boolean, roundCap?: boolean
P
pissang 已提交
383
    ): BarPossiblePath
384 385
}

1
100pah 已提交
386
const elementCreator: {
387 388
    [key in 'polar' | 'cartesian2d']: ElementCreator
} = {
P
pah100 已提交
389

390 391
    cartesian2d(
        dataIndex, layout: RectLayout, isHorizontal,
S
sushuang 已提交
392 393
        animationModel, isUpdate
    ) {
394
        let rect = new Rect({
395 396 397 398 399
            shape: zrUtil.extend({}, layout),
            z2: 1
        });

        rect.name = 'item';
S
sushuang 已提交
400 401 402

        // Animation
        if (animationModel) {
403 404 405
            let rectShape = rect.shape;
            let animateProperty = isHorizontal ? 'height' : 'width' as 'width' | 'height';
            let animateTarget = {} as RectShape;
S
sushuang 已提交
406 407
            rectShape[animateProperty] = 0;
            animateTarget[animateProperty] = layout[animateProperty];
408
            (isUpdate ? updateProps : initProps)(rect, {
S
sushuang 已提交
409 410 411
                shape: animateTarget
            }, animationModel, dataIndex);
        }
1
100pah 已提交
412

S
sushuang 已提交
413 414
        return rect;
    },
1
100pah 已提交
415

416 417
    polar(
        dataIndex: number, layout: SectorLayout, isRadial: boolean,
418
        animationModel, isUpdate, roundCap
S
sushuang 已提交
419
    ) {
S
sushuang 已提交
420 421 422 423
        // 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.
424
        let clockwise = layout.startAngle < layout.endAngle;
425

426
        let ShapeClass = (!isRadial && roundCap) ? Sausage : Sector;
427

428
        let sector = new ShapeClass({
429 430
            shape: zrUtil.defaults({clockwise: clockwise}, layout),
            z2: 1
S
sushuang 已提交
431
        });
S
sushuang 已提交
432

433 434
        sector.name = 'item';

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

S
sushuang 已提交
447 448 449 450
        return sector;
    }
};

451 452 453
function removeRect(
    dataIndex: number,
    animationModel: BarSeriesModel | GlobalModel,
454
    el: Rect
455
) {
S
sushuang 已提交
456 457
    // Not show text when animating
    el.style.text = null;
458
    updateProps(el, {
S
sushuang 已提交
459 460
        shape: {
            width: 0
O
Ovilia 已提交
461
        }
S
sushuang 已提交
462 463 464 465 466
    }, animationModel, dataIndex, function () {
        el.parent && el.parent.remove(el);
    });
}

467 468 469
function removeSector(
    dataIndex: number,
    animationModel: BarSeriesModel | GlobalModel,
470
    el: Sector
471
) {
S
sushuang 已提交
472 473
    // Not show text when animating
    el.style.text = null;
474
    updateProps(el, {
S
sushuang 已提交
475 476 477 478 479 480 481 482
        shape: {
            r: el.shape.r0
        }
    }, animationModel, dataIndex, function () {
        el.parent && el.parent.remove(el);
    });
}

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

        // fix layout with lineWidth
494 495
        let signX = layout.width > 0 ? 1 : -1;
        let signY = layout.height > 0 ? 1 : -1;
S
sushuang 已提交
496 497 498 499 500 501 502 503
        return {
            x: layout.x + signX * fixedLineWidth / 2,
            y: layout.y + signY * fixedLineWidth / 2,
            width: layout.width - signX * fixedLineWidth,
            height: layout.height - signY * fixedLineWidth
        };
    },

504
    polar(data, dataIndex, itemModel): SectorLayout {
505
        let layout = data.getItemLayout(dataIndex);
S
sushuang 已提交
506 507 508 509 510 511 512
        return {
            cx: layout.cx,
            cy: layout.cy,
            r0: layout.r0,
            r: layout.r,
            startAngle: layout.startAngle,
            endAngle: layout.endAngle
513
        } as SectorLayout;
1
100pah 已提交
514
    }
S
sushuang 已提交
515 516
};

517
function isZeroOnPolar(layout: SectorLayout) {
518 519 520 521 522
    return layout.startAngle != null
        && layout.endAngle != null
        && layout.startAngle === layout.endAngle;
}

S
sushuang 已提交
523
function updateStyle(
P
pissang 已提交
524
    el: BarPossiblePath,
525 526 527 528 529 530
    data: List, dataIndex: number,
    itemModel: Model<BarDataItemOption>,
    layout: RectLayout | SectorLayout,
    seriesModel: BarSeriesModel,
    isHorizontal: boolean,
    isPolar: boolean
S
sushuang 已提交
531
) {
532 533 534 535 536
    let color = data.getItemVisual(dataIndex, 'color');
    let opacity = data.getItemVisual(dataIndex, 'opacity');
    let stroke = data.getVisual('borderColor');
    let itemStyleModel = itemModel.getModel('itemStyle');
    let hoverStyle = getBarItemStyle(itemModel.getModel(['emphasis', 'itemStyle']));
S
sushuang 已提交
537 538 539

    if (!isPolar) {
        el.setShape('r', itemStyleModel.get('barBorderRadius') || 0);
O
Ovilia 已提交
540 541
    }

S
sushuang 已提交
542 543
    el.useStyle(zrUtil.defaults(
        {
544 545
            stroke: isZeroOnPolar(layout as SectorLayout) ? 'none' : stroke,
            fill: isZeroOnPolar(layout as SectorLayout) ? 'none' : color,
Z
zhangyi 已提交
546
            opacity: opacity
P
pah100 已提交
547
        },
548
        getBarItemStyle(itemStyleModel)
S
sushuang 已提交
549
    ));
1
100pah 已提交
550

551
    let cursorStyle = itemModel.getShallow('cursor');
S
sushuang 已提交
552
    cursorStyle && el.attr('cursor', cursorStyle);
1
100pah 已提交
553

S
sushuang 已提交
554
    if (!isPolar) {
555
        let labelPositionOutside = isHorizontal
556 557 558
            ? ((layout as RectLayout).height > 0 ? 'bottom' : 'top')
            : ((layout as RectLayout).width > 0 ? 'left' : 'right');

S
sushuang 已提交
559 560 561 562
        setLabel(
            el.style, hoverStyle, itemModel, color,
            seriesModel, dataIndex, labelPositionOutside
        );
1
100pah 已提交
563
    }
564
    if (isZeroOnPolar(layout as SectorLayout)) {
Z
zhangyi 已提交
565 566
        hoverStyle.fill = hoverStyle.stroke = 'none';
    }
567
    setHoverStyle(el, hoverStyle);
S
sushuang 已提交
568
}
1
tweak  
100pah 已提交
569

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

582
class LagePathShape {
1
100pah 已提交
583
    points: ArrayLike<number>;
584 585 586 587 588
}
interface LargePathProps extends PathProps {
    shape?: LagePathShape
}
class LargePath extends Path {
1
100pah 已提交
589
    type = 'largeBar';
590

1
100pah 已提交
591 592 593 594 595 596
    shape: LagePathShape;
;
    __startPoint: number[];
    __baseDimIdx: number;
    __largeDataIndices: ArrayLike<number>;
    __barWidth: number;
597

598 599 600
    constructor(opts?: LargePathProps) {
        super(opts, null, new LagePathShape());
    }
601

602
    buildPath(ctx: CanvasRenderingContext2D, shape: LagePathShape) {
603 604
        // Drawing lines is more efficient than drawing
        // a whole line or drawing rects.
605 606 607
        const points = shape.points;
        const startPoint = this.__startPoint;
        const baseDimIdx = this.__baseDimIdx;
608

609
        for (let i = 0; i < points.length; i += 2) {
610
            startPoint[baseDimIdx] = points[i + baseDimIdx];
611 612 613 614
            ctx.moveTo(startPoint[0], startPoint[1]);
            ctx.lineTo(points[i], points[i + 1]);
        }
    }
615
}
616

617 618 619 620 621
function createLarge(
    seriesModel: BarSeriesModel,
    group: Group,
    incremental?: boolean
) {
622
    // TODO support polar
623 624 625
    let data = seriesModel.getData();
    let startPoint = [];
    let baseDimIdx = data.getLayout('valueAxisHorizontal') ? 1 : 0;
626
    startPoint[1 - baseDimIdx] = data.getLayout('valueAxisStart');
627

628 629
    let largeDataIndices = data.getLayout('largeDataIndices');
    let barWidth = data.getLayout('barWidth');
630

631 632
    let backgroundModel = seriesModel.getModel('backgroundStyle');
    let drawBackground = seriesModel.get('showBackground', true);
633 634

    if (drawBackground) {
635 636
        const points = data.getLayout('largeBackgroundPoints');
        const backgroundStartPoint: number[] = [];
637 638
        backgroundStartPoint[1 - baseDimIdx] = data.getLayout('backgroundStart');

639
        const bgEl = new LargePath({
640 641 642 643 644
            shape: {points: points},
            incremental: !!incremental,
            silent: true,
            z2: 0
        });
645 646 647 648
        bgEl.__startPoint = backgroundStartPoint;
        bgEl.__baseDimIdx = baseDimIdx;
        bgEl.__largeDataIndices = largeDataIndices;
        bgEl.__barWidth = barWidth;
649 650 651 652
        setLargeBackgroundStyle(bgEl, backgroundModel, data);
        group.add(bgEl);
    }

653
    let el = new LargePath({
654
        shape: {points: data.getLayout('largePoints')},
655
        incremental: !!incremental
656
    });
657 658 659 660
    el.__startPoint = startPoint;
    el.__baseDimIdx = baseDimIdx;
    el.__largeDataIndices = largeDataIndices;
    el.__barWidth = barWidth;
661 662
    group.add(el);
    setLargeStyle(el, seriesModel, data);
663 664

    // Enable tooltip and user mouse/touch event handlers.
665
    getECData(el).seriesIndex = seriesModel.seriesIndex;
666 667 668 669 670 671 672 673

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

// Use throttle to avoid frequently traverse to find dataIndex.
1
100pah 已提交
674
const largePathUpdateDataIndex = throttle(function (this: LargePath, event: ZRElementEvent) {
675 676
    let largePath = this;
    let dataIndex = largePathFindDataIndex(largePath, event.offsetX, event.offsetY);
677
    getECData(largePath).dataIndex = dataIndex >= 0 ? dataIndex : null;
678 679
}, 30, false);

680
function largePathFindDataIndex(largePath: LargePath, x: number, y: number) {
681 682 683 684 685 686
    let baseDimIdx = largePath.__baseDimIdx;
    let valueDimIdx = 1 - baseDimIdx;
    let points = largePath.shape.points;
    let largeDataIndices = largePath.__largeDataIndices;
    let barWidthHalf = Math.abs(largePath.__barWidth / 2);
    let startValueVal = largePath.__startPoint[valueDimIdx];
687 688 689

    _eventPos[0] = x;
    _eventPos[1] = y;
690 691 692 693 694 695 696 697 698
    let pointerBaseVal = _eventPos[baseDimIdx];
    let pointerValueVal = _eventPos[1 - baseDimIdx];
    let baseLowerBound = pointerBaseVal - barWidthHalf;
    let baseUpperBound = pointerBaseVal + barWidthHalf;

    for (let i = 0, len = points.length / 2; i < len; i++) {
        let ii = i * 2;
        let barBaseVal = points[ii + baseDimIdx];
        let barValueVal = points[ii + valueDimIdx];
699 700 701 702 703 704 705 706 707 708 709 710 711
        if (
            barBaseVal >= baseLowerBound && barBaseVal <= baseUpperBound
            && (
                startValueVal <= barValueVal
                    ? (pointerValueVal >= startValueVal && pointerValueVal <= barValueVal)
                    : (pointerValueVal >= barValueVal && pointerValueVal <= startValueVal)
            )
        ) {
            return largeDataIndices[i];
        }
    }

    return -1;
712 713
}

714 715 716 717 718
function setLargeStyle(
    el: LargePath,
    seriesModel: BarSeriesModel,
    data: List
) {
719 720
    let borderColor = data.getVisual('borderColor') || data.getVisual('color');
    let itemStyle = seriesModel.getModel('itemStyle').getItemStyle(['color', 'borderColor']);
721 722 723 724 725 726 727

    el.useStyle(itemStyle);
    el.style.fill = null;
    el.style.stroke = borderColor;
    el.style.lineWidth = data.getLayout('barWidth');
}

728 729 730 731 732
function setLargeBackgroundStyle(
    el: LargePath,
    backgroundModel: Model<BarSeriesOption['backgroundStyle']>,
    data: List
) {
733 734
    let borderColor = backgroundModel.get('borderColor') || backgroundModel.get('color');
    let itemStyle = backgroundModel.getItemStyle(['color', 'borderColor']);
735 736 737 738

    el.useStyle(itemStyle);
    el.style.fill = null;
    el.style.stroke = borderColor;
739
    el.style.lineWidth = data.getLayout('barWidth') as number;
740 741
}

742 743 744 745 746
function createBackgroundShape(
    isHorizontalOrRadial: boolean,
    layout: SectorLayout | RectLayout,
    coord: CoordSysOfBar
): SectorShape | RectShape {
747
    if (isCoordinateSystemType<Cartesian2D>(coord, 'cartesian2d')) {
748 749 750 751 752 753 754 755
        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;
756 757
    }
    else {
758 759
        const coordLayout = coord.getArea();
        const sectorShape = layout as SectorShape;
760 761 762
        return {
            cx: coordLayout.cx,
            cy: coordLayout.cy,
763 764 765 766 767
            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;
768 769 770
    }
}

771 772 773 774
function createBackgroundEl(
    coord: CoordSysOfBar,
    isHorizontalOrRadial: boolean,
    layout: SectorLayout | RectLayout
775
): Rect | Sector {
776
    let ElementClz = coord.type === 'polar' ? Sector : Rect;
777
    return new ElementClz({
778
        shape: createBackgroundShape(isHorizontalOrRadial, layout, coord) as any,
779 780 781 782
        silent: true,
        z2: 0
    });
}
783 784 785 786

ChartView.registerClass(BarView);

export default BarView;