BarView.ts 26.5 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';
P
pissang 已提交
22
import {Rect, Sector, getECData, updateProps, initProps, enableHoverEmphasis, setLabelStyle} from '../../util/graphic';
23 24
import {getBarItemStyle} from './barItemStyle';
import Path, { PathProps } from 'zrender/src/graphic/Path';
P
pissang 已提交
25
import Group from 'zrender/src/graphic/Group';
26
import {throttle} from '../../util/throttle';
27
import {createClipPath} from '../helper/createClipPathFromCoordSys';
28
import Sausage from '../../util/shape/sausage';
29 30 31 32
import ChartView from '../../view/Chart';
import List from '../../data/List';
import GlobalModel from '../../model/Global';
import ExtensionAPI from '../../ExtensionAPI';
33
import { StageHandlerProgressParams, ZRElementEvent } from '../../util/types';
34 35 36 37 38
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';
39
import { isCoordinateSystemType } from '../../coord/CoordinateSystem';
P
pissang 已提交
40
import { getDefaultLabel } from '../helper/labelHelper';
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

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

    return coordSysClipArea;
}

L
lang 已提交
81

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

162 163
        const bgEls: BarView['_backgroundEls'] = [];
        const oldBgEls = this._backgroundEls;
164

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

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

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

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

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

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

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

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

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

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

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

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

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

264
        const bgGroup = this._backgroundGroup || (this._backgroundGroup = new Group());
265 266
        bgGroup.removeAll();

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

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

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

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

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

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

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

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

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

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

348 349 350 351
        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 已提交
352 353 354 355 356 357

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

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

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

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

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

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

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

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

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

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

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

425
        const ShapeClass = (!isRadial && roundCap) ? Sausage : Sector;
426

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

432 433
        sector.name = 'item';

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

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

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

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

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

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

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

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

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

    if (!isPolar) {
P
pissang 已提交
538
        (el as Rect).setShape('r', itemStyleModel.get('barBorderRadius') || 0);
O
Ovilia 已提交
539 540
    }

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

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

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

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

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

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

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

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

    getDefaultShape() {
        return new LagePathShape();
611
    }
612

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

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

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

639 640
    const largeDataIndices = data.getLayout('largeDataIndices');
    const barWidth = data.getLayout('barWidth');
641

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

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

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

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

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

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

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

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

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

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

    return -1;
723 724
}

725 726 727 728 729
function setLargeStyle(
    el: LargePath,
    seriesModel: BarSeriesModel,
    data: List
) {
730 731
    const borderColor = data.getVisual('borderColor') || data.getVisual('color');
    const itemStyle = seriesModel.getModel('itemStyle').getItemStyle(['color', 'borderColor']);
732 733 734 735 736 737 738

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

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

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

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

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

ChartView.registerClass(BarView);

export default BarView;