• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

naver / billboard.js / 30443109728

29 Jul 2026 10:17AM UTC coverage: 93.712% (-0.1%) from 93.823%
30443109728

push

github

web-flow
feat(subchart): support configurable subchart rendering

Add subchart.type and subchart.types for independent overview series rendering.
Support subchart x/y/y2 axis ticks, formats, culling, outer ticks, and text visibility.
Add brush.enabled=false hover behavior and continuous focus grid across main/subchart.
Project candlestick values for subchart bars, line, and area using stock chart semantics.
Align SVG/canvas subchart axis, grid, and interaction behavior with demos/tests.

Ref #4175

11530 of 12842 branches covered (89.78%)

Branch coverage included in aggregate %.

553 of 584 new or added lines in 20 files covered. (94.69%)

108 existing lines in 12 files now uncovered.

14417 of 14846 relevant lines covered (97.11%)

29561.24 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.73
/src/ChartInternal/shape/shape.ts
1
/**
2
 * Copyright (c) 2017 ~ present NAVER Corp.
3
 * billboard.js project is licensed under the MIT license
4
 */
5
import {select as d3Select} from "d3-selection";
6
import {
7
        curveBasis as d3CurveBasis,
8
        curveBasisClosed as d3CurveBasisClosed,
9
        curveBasisOpen as d3CurveBasisOpen,
10
        curveBundle as d3CurveBundle,
11
        curveCardinal as d3CurveCardinal,
12
        curveCardinalClosed as d3CurveCardinalClosed,
13
        curveCardinalOpen as d3CurveCardinalOpen,
14
        curveCatmullRom as d3CurveCatmullRom,
15
        curveCatmullRomClosed as d3CurveCatmullRomClosed,
16
        curveCatmullRomOpen as d3CurveCatmullRomOpen,
17
        curveLinear as d3CurveLinear,
18
        curveLinearClosed as d3CurveLinearClosed,
19
        curveMonotoneX as d3CurveMonotoneX,
20
        curveMonotoneY as d3CurveMonotoneY,
21
        curveNatural as d3CurveNatural,
22
        curveStep as d3CurveStep,
23
        curveStepAfter as d3CurveStepAfter,
24
        curveStepBefore as d3CurveStepBefore
25
} from "d3-shape";
26
import type {d3Selection} from "../../../types/types";
27
import CLASS from "../../config/classes";
28
import {TYPE} from "../../config/const";
29
import {KEY} from "../../module/Cache";
30
import {
31
        capitalize,
32
        getPointer,
33
        getRectSegList,
34
        getUnique,
35
        isFunction,
36
        isNumber,
37
        isObjectType,
38
        isUndefined,
39
        isValue,
40
        notEmpty,
41
        parseDate
42
} from "../../module/util";
43
import type {IDataIndice, IDataRow, TIndices} from "../data/IData";
44
import type {IOffset, ShapeElementConfig, UpdateTargetsConfig} from "./IShape";
45

46
// Module-level constant: avoids re-creating the lookup object on every getInterpolate() call
47
const CURVE_MAP: Record<string, unknown> = {
258✔
48
        basis: d3CurveBasis,
49
        "basis-closed": d3CurveBasisClosed,
50
        "basis-open": d3CurveBasisOpen,
51
        bundle: d3CurveBundle,
52
        cardinal: d3CurveCardinal,
53
        "cardinal-closed": d3CurveCardinalClosed,
54
        "cardinal-open": d3CurveCardinalOpen,
55
        "catmull-rom": d3CurveCatmullRom,
56
        "catmull-rom-closed": d3CurveCatmullRomClosed,
57
        "catmull-rom-open": d3CurveCatmullRomOpen,
58
        "monotone-x": d3CurveMonotoneX,
59
        "monotone-y": d3CurveMonotoneY,
60
        natural: d3CurveNatural,
61
        "linear-closed": d3CurveLinearClosed,
62
        linear: d3CurveLinear,
63
        step: d3CurveStep,
64
        "step-after": d3CurveStepAfter,
65
        "step-before": d3CurveStepBefore
66
};
67

68
// Re-export types for backward compatibility
69
export type {
70
        IOffset,
71
        LinearGradientOption,
72
        ShapeElementConfig,
73
        UpdateTargetsConfig
74
} from "./IShape";
75

76
/**
77
 * Check if a target can use line-like grouped point offsets.
78
 * @param {object} $$ ChartInternal instance
79
 * @param {object|string} d Data value, target or id
80
 * @returns {boolean} Whether target uses point-like y coordinates
81
 * @private
82
 */
83
function isLinePointGroupType($$, d): boolean {
84
        return $$.isLineType(d) || $$.isScatterType?.(d) || $$.isBubbleType?.(d);
12,582✔
85
}
86

87
/**
88
 * Get type filter for grouped line-like point offsets.
89
 * @param {object} $$ ChartInternal instance
90
 * @returns {function} Type filter
91
 * @private
92
 */
93
function getLinePointGroupTypeFilter($$): Function {
94
        return d => isLinePointGroupType($$, d);
10,806✔
95
}
96

97
/**
98
 * Get numeric value used for stacked offset calculation.
99
 * @param {object} $$ ChartInternal instance
100
 * @param {object} d Data row
101
 * @param {boolean} isSub Whether coordinates are for the subchart
102
 * @returns {number|Array|object|null} Offset value
103
 * @private
104
 */
105
function getShapeOffsetValue($$, d, isSub?: boolean) {
106
        const subchartCandlestickValue = getSubchartCandlestickShapeValue($$, d, isSub);
135,951✔
107

108
        if (isNumber(subchartCandlestickValue)) {
135,951✔
109
                return subchartCandlestickValue;
90✔
110
        }
111

112
        if ($$.isCandlestickType?.(d)) {
135,861✔
113
                return $$.getCandlestickData?.(d)?.close;
699✔
114
        }
115

116
        return $$.getBaseValue(d);
135,162✔
117
}
118

119
/**
120
 * Get candlestick data projected for alternate subchart shapes.
121
 * @param {object} $$ ChartInternal instance
122
 * @param {object} d Data row
123
 * @param {boolean} isSub Whether coordinates are for the subchart
124
 * @returns {number|undefined} Projected value
125
 * @private
126
 */
127
function getSubchartCandlestickShapeValue($$, d, isSub?: boolean) {
128
        if (
1,093,554✔
129
                !isSub ||
2,609,160✔
130
                $$.isCandlestickType?.(d) ||
131
                !$$.isSubchartSourceTypeOf?.(d, TYPE.CANDLESTICK)
132
        ) {
133
                return undefined;
1,093,233✔
134
        }
135

136
        const value = $$.getCandlestickData?.(d);
321✔
137

138
        if (!value) {
321✔
139
                return undefined;
18✔
140
        }
141

142
        if ($$.isBarType(d)) {
303✔
143
                return isNumber(value.open) && isNumber(value.close) ?
180!
144
                        value._isUp ? value.close : value.open :
180✔
145
                        undefined;
146
        }
147

148
        return isNumber(value.close) ? value.close : undefined;
123!
149
}
150

151
/**
152
 * Check whether candlestick data can be projected as a subchart bar value.
153
 * @param {object} $$ ChartInternal instance
154
 * @param {object} d Data row
155
 * @param {boolean} isSub Whether coordinates are for the subchart
156
 * @returns {boolean}
157
 * @private
158
 */
159
function isSubchartCandlestickBarValue($$, d, isSub?: boolean): boolean {
160
        const value = getSubchartCandlestickShapeValue($$, d, isSub);
14,940✔
161

162
        return isNumber(value) && $$.isBarType(d);
14,940✔
163
}
164

165
/**
166
 * Get subchart bar color projected from candlestick up/down state.
167
 * @param {object} $$ ChartInternal instance
168
 * @param {object} d Data row
169
 * @param {boolean} isSub Whether coordinates are for the subchart
170
 * @returns {string|null} Bar color
171
 * @private
172
 */
173
function getSubchartCandlestickBarColor($$, d, isSub?: boolean): string | null {
174
        if (!isSubchartCandlestickBarValue($$, d, isSub)) {
14,913✔
175
                return null;
14,886✔
176
        }
177

178
        const value = $$.getCandlestickData?.(d);
27✔
179

180
        if (value?._isUp) {
27✔
181
                return $$.color(d);
18✔
182
        }
183

184
        const downColor = $$.config.candlestick_color_down;
9✔
185
        const color = downColor && typeof downColor === "object" ? downColor[d.id] : downColor;
9!
186

187
        return color || $$.color(d);
9!
188
}
189

190
/**
191
 * Get grouped data point function for y coordinate
192
 * @param {object} d data vlaue
193
 * @returns {function|undefined}
194
 * @private
195
 */
196
function _getGroupedDataPointsFn(d) {
197
        const $$ = this;
1,776✔
198
        let fn;
199

200
        if (isLinePointGroupType($$, d)) {
1,776!
201
                const typeFilter = getLinePointGroupTypeFilter($$);
1,776✔
202

203
                fn = $$.generateGetLinePoints($$.getShapeIndices(typeFilter), false, typeFilter);
1,776✔
204
        } else if ($$.isBarType(d)) {
×
205
                fn = $$.generateGetBarPoints($$.getShapeIndices($$.isBarType));
×
206
        } else if ($$.isCandlestickType?.(d)) {
×
207
                fn = $$.generateGetCandlestickPoints?.($$.getShapeIndices($$.isCandlestickType));
×
208
        }
209

210
        return fn;
1,776✔
211
}
212

213
/**
214
 * Get shape color with gradient support
215
 * @param {object} d Data object
216
 * @param {string} configKey Configuration key for linearGradient (e.g., 'bar_linearGradient', 'area_linearGradient')
217
 * @param {(d: IDataRow) => string | null} colorFn Fallback color function when gradient is not enabled
218
 * @returns {string | null} Color string or gradient URL
219
 * @private
220
 */
221
export function getShapeColorWithGradient(
222
        this: any,
223
        d: IDataRow,
224
        configKey: string,
225
        colorFn: (d: IDataRow) => string | null
226
): string | null {
227
        return this.config[configKey] ? this.getGradienColortUrl(d.id) : colorFn(d);
30,510✔
228
}
229

230
/**
231
 * Initialize a shape element container
232
 * @param {ShapeElementConfig} config Configuration object
233
 * @private
234
 */
235
export function initShapeElement(this: any, config: ShapeElementConfig): void {
236
        const {$el} = this;
3,849✔
237
        const {elKey, className, cssRules, position} = config;
3,849✔
238
        const container = $el.main.select(`.${CLASS.chart}`);
3,849✔
239

240
        $el[elKey] = position === "first" ?
3,849!
241
                container.insert("g", ":first-child") :
242
                container.append("g");
243

244
        $el[elKey].attr("class", className);
3,849✔
245

246
        if (cssRules?.length) {
3,849✔
247
                $el[elKey].call(this.setCssRule(false, `.${className}`, cssRules));
3,780✔
248
        }
249
}
250

251
/**
252
 * Common update targets pattern for shapes
253
 * @param {Array} targets Target data
254
 * @param {UpdateTargetsConfig} config Configuration object
255
 * @returns {d3Selection} Enter selection for additional setup
256
 * @private
257
 */
258
export function updateTargetsForShape(
259
        this: any,
260
        targets: any[],
261
        config: UpdateTargetsConfig
262
): d3Selection {
263
        const $$ = this;
5,592✔
264
        const {$el} = $$;
5,592✔
265
        const {type, elKey, containerClass, itemClass, initFn, withFocus = true, withStyles = true} =
11,046✔
266
                config;
5,592✔
267

268
        if (!$el[elKey]) {
5,592✔
269
                initFn.call($$);
12✔
270
        }
271

272
        const classChart = $$.getChartClass(type);
5,592✔
273
        const classFocus = withFocus ? $$.classFocus.bind($$) : () => "";
5,592✔
274

275
        const mainUpdate = $el.main.select(`.${containerClass}`)
5,592✔
276
                .selectAll(`.${itemClass}`)
277
                .data($$.filterNullish(targets))
278
                .attr("class", d => classChart(d) + classFocus(d));
300✔
279

280
        const mainEnter = mainUpdate.enter().append("g")
5,592✔
281
                .attr("class", classChart);
282

283
        if (withStyles) {
5,592✔
284
                mainEnter
5,523✔
285
                        .style("opacity", "0")
286
                        .style("pointer-events", $$.getStylePropValue("none"));
287
        }
288

289
        return mainEnter;
5,592✔
290
}
291

292
export default {
293
        /**
294
         * Get the shape draw function
295
         * @returns {object}
296
         * @private
297
         */
298
        getDrawShape() {
299
                type TShape = {area?: any, bar?: any, line?: any};
300

301
                const $$ = this;
9,418✔
302
                const isRotated = $$.config.axis_rotated;
9,418✔
303
                const {hasRadar, hasTreemap} = $$.state;
9,418✔
304
                const shape = {type: <TShape>{}, indices: <TShape>{}, pos: {}};
9,418✔
305

306
                !hasTreemap && ["bar", "candlestick", "line", "area"].forEach(v => {
9,418✔
307
                        const name = capitalize(v);
37,072✔
308

309
                        if (
37,072✔
310
                                $$.hasType(v) || $$.hasTypeOf(name) || (
100,777✔
311
                                        v === "line" &&
312
                                        ($$.hasType("bubble") || $$.hasType("scatter"))
313
                                )
314
                        ) {
315
                                const indices = $$.getShapeIndices($$[`is${name}Type`]);
9,091✔
316
                                const drawFn = $$[`generateDraw${name}`];
9,091✔
317

318
                                shape.indices[v] = indices;
9,091✔
319
                                shape.type[v] = drawFn ? drawFn.bind($$)(indices, false) : undefined;
9,091✔
320
                        }
321
                });
322

323
                if (!$$.hasArcType() || hasRadar || hasTreemap) {
9,418✔
324
                        let cx;
325
                        let cy;
326
                        let xForText;
327
                        let yForText;
328

329
                        // generate circle x/y functions depending on updated params
330
                        if (!hasTreemap) {
8,635✔
331
                                cx = hasRadar ? $$.radarCircleX : (isRotated ? $$.circleY : $$.circleX);
8,485✔
332
                                cy = hasRadar ? $$.radarCircleY : (isRotated ? $$.circleX : $$.circleY);
8,485✔
333
                        }
334

335
                        if (hasTreemap && $$.state.isCanvasMode) {
8,635✔
336
                                xForText = yForText = function() {};
30✔
337
                        } else {
338
                                xForText = $$.generateXYForText(shape.indices, true);
8,605✔
339
                                yForText = $$.generateXYForText(shape.indices, false);
8,605✔
340
                        }
341

342
                        shape.pos = {
8,635✔
343
                                xForText,
344
                                yForText,
345
                                cx: (cx || function() {}).bind($$),
8,785✔
346
                                cy: (cy || function() {}).bind($$)
8,785✔
347
                        };
348
                }
349

350
                return shape;
9,418✔
351
        },
352

353
        /**
354
         * Get shape's indices according it's position within each axis tick.
355
         *
356
         * From the below example, indices will be:
357
         * ==> {data1: 0, data2: 0, data3: 1, data4: 1, __max__: 1}
358
         *
359
         *         data1 data3   data1 data3
360
         *         data2 data4   data2 data4
361
         *         -------------------------
362
         *                  0             1
363
         * @param {function} typeFilter Chart type filter function
364
         * @returns {object} Indices object with its position
365
         */
366
        getShapeIndices(typeFilter): TIndices {
367
                const $$ = this;
14,692✔
368
                const {config} = $$;
14,692✔
369
                const xs = config.data_xs;
14,692✔
370
                const hasXs = notEmpty(xs);
14,692✔
371
                const indices: TIndices = {};
14,692✔
372
                let i: any = hasXs ? {} : 0;
14,692✔
373

374
                if (hasXs) {
14,692✔
375
                        getUnique(Object.keys(xs).map(v => xs[v]))
312✔
376
                                .forEach(v => {
377
                                        i[v] = 0;
297✔
378
                                        indices[v] = {};
297✔
379
                                });
380
                }
381

382
                $$.filterTargetsToShow($$.data.targets.filter(typeFilter, $$))
14,692✔
383
                        .forEach(d => {
384
                                const xKey = d.id in xs ? xs[d.id] : "";
21,697✔
385
                                const ind = xKey ? indices[xKey] : indices;
21,697✔
386

387
                                for (let j = 0, groups; (groups = config.data_groups[j]); j++) {
21,697✔
388
                                        if (groups.indexOf(d.id) < 0) {
11,121✔
389
                                                continue;
2,733✔
390
                                        }
391

392
                                        for (let k = 0, key; (key = groups[k]); k++) {
8,388✔
393
                                                if (key in ind) {
13,656✔
394
                                                        ind[d.id] = ind[key];
5,235✔
395
                                                        break;
5,235✔
396
                                                }
397

398
                                                // for same grouped data, add other data to same indices
399
                                                if (d.id !== key && xKey) {
8,421✔
400
                                                        ind[key] = ind[d.id] ?? i[xKey];
18✔
401
                                                }
402
                                        }
403
                                }
404

405
                                if (isUndefined(ind[d.id])) {
21,697✔
406
                                        ind[d.id] = xKey ? i[xKey]++ : i++;
16,462✔
407
                                        ind.__max__ = (xKey ? i[xKey] : i) - 1;
16,462✔
408
                                }
409
                        });
410

411
                return indices;
14,692✔
412
        },
413

414
        /**
415
         * Get indices value based on data ID value
416
         * @param {object} indices Indices object
417
         * @param {object} d Data row
418
         * @param {string} caller Caller function name (Used only for 'sparkline' plugin)
419
         * @returns {object} Indices object
420
         * @private
421
         */
422
        getIndices(indices: TIndices, d: IDataRow, caller?: string): IDataIndice { // eslint-disable-line
423
                const $$ = this;
123,261✔
424
                const {data_xs: xs, bar_indices_removeNull: removeNull} = $$.config;
123,261✔
425
                const {id, index} = d;
123,261✔
426

427
                if ($$.isBarType(id) && removeNull) {
123,261✔
428
                        const ind = {} as IDataIndice;
54✔
429

430
                        // redefine bar indices order
431
                        $$.getAllValuesOnIndex(index, true)
54✔
432
                                .forEach((v, i) => {
433
                                        ind[v.id] = i;
108✔
434
                                        ind.__max__ = i;
108✔
435
                                });
436

437
                        return ind;
54✔
438
                }
439

440
                return notEmpty(xs) ? indices[xs[id]] : indices as IDataIndice;
123,207✔
441
        },
442

443
        /**
444
         * Get indices max number
445
         * @param {object} indices Indices object
446
         * @returns {number} Max number
447
         * @private
448
         */
449
        getIndicesMax(indices: TIndices | IDataIndice): number {
450
                if (!notEmpty(this.config.data_xs)) {
8,255✔
451
                        return (indices as IDataIndice).__max__;
8,129✔
452
                }
453

454
                // if is multiple xs, return total sum of xs' __max__ value
455
                let total = 0;
126✔
456

457
                for (const key in indices) {
126✔
458
                        total += indices[key].__max__ || 0;
234✔
459
                }
460

461
                return total;
126✔
462
        },
463

464
        getShapeX(offset: IOffset, indices, isSub?: boolean): (d) => number {
465
                const $$ = this;
32,690✔
466
                const {config, scale} = $$;
32,690✔
467
                const currScale = isSub ? scale.subX : (scale.zoom || scale.x);
32,690✔
468
                const barOverlap = config.bar_overlap;
32,690✔
469
                const barPadding = config.bar_padding;
32,690✔
470
                const sum = (p, c) => p + c;
32,690✔
471

472
                // total shapes half width
473
                const halfWidth = isObjectType(offset) && (
32,690✔
474
                        offset._$total.length ? offset._$total.reduce(sum) / 2 : 0
285✔
475
                );
476

477
                // Pre-compute prefix sums to avoid O(n) slice+reduce on every bar datum
478
                const prefixSums: number[] = [];
32,690✔
479

480
                if (halfWidth && isObjectType(offset) && offset._$total.length) {
32,690✔
481
                        let acc = 0;
45✔
482

483
                        for (const v of offset._$total) {
45✔
484
                                acc += v;
99✔
485
                                prefixSums.push(acc);
99✔
486
                        }
487
                }
488

489
                return d => {
32,690✔
490
                        const ind = $$.getIndices(indices, d, "getShapeX");
33,249✔
491
                        const index = d.id in ind ? ind[d.id] : 0;
33,249✔
492
                        const targetsNum = (ind.__max__ || 0) + 1;
33,249✔
493
                        let x = 0;
33,249✔
494

495
                        if (notEmpty(d.x)) {
33,249!
496
                                const xPos = currScale(d.x, true);
33,249✔
497

498
                                if (halfWidth) {
33,249✔
499
                                        const offsetWidth = offset[d.id] || offset._$width;
171!
500

501
                                        x = barOverlap ? xPos - offsetWidth / 2 : xPos - offsetWidth +
171✔
502
                                                (prefixSums[index] ?? offset._$total.slice(0, index + 1).reduce(sum)) -
144!
503
                                                halfWidth;
504
                                } else {
505
                                        x = xPos - (isNumber(offset) ? offset : offset._$width) *
33,078✔
506
                                                        (targetsNum / 2 - (
507
                                                                barOverlap ? 1 : index
33,078!
508
                                                        ));
509
                                }
510
                        }
511

512
                        // adjust x position for bar.padding option
513
                        if (offset && x && targetsNum > 1 && barPadding) {
33,249✔
514
                                if (index) {
432✔
515
                                        x += barPadding * index;
216✔
516
                                }
517

518
                                if (targetsNum > 2) {
432!
519
                                        x -= (targetsNum - 1) * barPadding / 2;
×
520
                                } else if (targetsNum === 2) {
432!
521
                                        x -= barPadding / 2;
432✔
522
                                }
523
                        }
524

525
                        return x;
33,249✔
526
                };
527
        },
528

529
        getShapeY(isSub?: boolean): Function {
530
                const $$ = this;
32,846✔
531
                const isStackNormalized = $$.isStackNormalized();
32,846✔
532

533
                return d => {
32,846✔
534
                        let {value} = d;
34,617✔
535
                        const subchartCandlestickValue = getSubchartCandlestickShapeValue($$, d, isSub);
34,617✔
536

537
                        if (isNumber(d)) {
34,617✔
538
                                value = d;
1,824✔
539
                        } else if (isNumber(subchartCandlestickValue)) {
32,793✔
540
                                value = subchartCandlestickValue;
36✔
541
                        } else if ($$.isAreaRangeType(d)) {
32,757✔
542
                                value = $$.getBaseValue(d, "mid");
108✔
543
                        } else if (isStackNormalized) {
32,649✔
544
                                value = $$.getRatio("index", d, true);
420✔
545
                        } else if ($$.isBubbleZType(d)) {
32,229!
546
                                value = $$.getBubbleZData(d.value, "y");
×
547
                        } else if ($$.isBarRangeType(d)) {
32,229✔
548
                                // TODO use range.getEnd() like method
549
                                value = value[1];
186✔
550
                        }
551

552
                        return $$.getYScaleById(d.id, isSub)(value);
34,617✔
553
                };
554
        },
555

556
        /**
557
         * Get shape based y Axis min value
558
         * @param {string} id Data id
559
         * @param {boolean} isSub Whether to use subchart scale
560
         * @returns {number}
561
         * @private
562
         */
563
        getShapeYMin(id: string, isSub = false): number {
×
564
                const $$ = this;
55,338✔
565
                const axisId = $$.axis.getId(id);
55,338✔
566
                const scale = $$.getYScaleById(id, isSub);
55,338✔
567
                const [yMin] = scale.domain();
55,338✔
568
                const inverted = $$.config[`axis_${axisId}_inverted`];
55,338✔
569

570
                return !$$.isGrouped(id) && !inverted && yMin > 0 ? yMin : 0;
55,338✔
571
        },
572

573
        /**
574
         * Get Shape's offset data
575
         * @param {function} typeFilter Type filter function
576
         * @param {boolean} isSub Whether coordinates are for the subchart
577
         * @returns {object}
578
         * @private
579
         */
580
        getShapeOffsetData(typeFilter, isSub?: boolean) {
581
                const $$ = this;
32,690✔
582
                const targets = $$.orderTargets(
32,690✔
583
                        $$.filterTargetsToShow($$.data.targets.filter(typeFilter, $$))
584
                );
585

586
                // Same IDs can receive new values through load()/flow(), so ID-only
587
                // caching can leave stacked offsets pointing at stale row maps.
588
                const dataGeneration = $$.state.dataGeneration;
32,690✔
589
                const targetIds = targets.map(t => t.id).join("_");
56,796✔
590
                const cacheKey = `${KEY.shapeOffset}_${isSub ? "sub" : "main"}_${targetIds}`;
32,690✔
591

592
                // Check if result is already cached
593
                const cachedData = $$.cache.get(cacheKey);
32,690✔
594

595
                if (cachedData?.generation === dataGeneration) {
32,690✔
596
                        return cachedData;
24,476✔
597
                }
598

599
                const isStackNormalized = $$.isStackNormalized();
8,214✔
600

601
                const shapeOffsetTargets = targets.map(target => {
8,214✔
602
                        let rowValues = target.values;
13,812✔
603
                        const values = {};
13,812✔
604

605
                        if ($$.isStepType(target)) {
13,812✔
606
                                rowValues = $$.convertValuesToStep(rowValues);
282✔
607
                        }
608

609
                        const rowValueMapByXValue = rowValues.reduce((out, d) => {
13,812✔
610
                                const key = Number(d.x);
76,986✔
611
                                const value = getShapeOffsetValue($$, d, isSub);
76,986✔
612

613
                                out[key] = d;
76,986✔
614
                                values[key] = isStackNormalized ? $$.getRatio("index", d, true) : value;
76,986✔
615

616
                                return out;
76,986✔
617
                        }, {});
618

619
                        return {
13,812✔
620
                                id: target.id,
621
                                rowValues,
622
                                rowValueMapByXValue,
623
                                values
624
                        };
625
                });
626
                const indexMapByTargetId = targets.reduce((out, {id}, index) => {
8,214✔
627
                        out[id] = index;
13,812✔
628
                        return out;
13,812✔
629
                }, {});
630

631
                const result = {generation: dataGeneration, indexMapByTargetId, shapeOffsetTargets};
8,214✔
632

633
                // Cache the result
634
                $$.cache.add(cacheKey, result);
8,214✔
635

636
                return result;
8,214✔
637
        },
638

639
        getShapeOffset(typeFilter, indices, isSub?: boolean): Function {
640
                const $$ = this;
32,690✔
641
                const {shapeOffsetTargets, indexMapByTargetId} = $$.getShapeOffsetData(
32,690✔
642
                        typeFilter,
643
                        isSub
644
                );
645
                const groupsZeroAs = $$.config.data_groupsZeroAs;
32,690✔
646

647
                // Pre-build per-series same-stacking-group lookup to avoid .filter() on every datum.
648
                // bar_indices_removeNull recomputes group membership per-datum index, so fall back there.
649
                let sameGroupByTargetId: Map<string, typeof shapeOffsetTargets> | null = null;
32,690✔
650

651
                if (!$$.config.bar_indices_removeNull) {
32,690✔
652
                        sameGroupByTargetId = new Map();
32,681✔
653

654
                        for (const target of shapeOffsetTargets) {
32,681✔
655
                                const ind = $$.getIndices(indices, {id: target.id, index: 0} as IDataRow);
56,769✔
656

657
                                sameGroupByTargetId.set(
56,769✔
658
                                        target.id,
659
                                        shapeOffsetTargets.filter(
660
                                                t => t.id !== target.id && ind[t.id] === ind[target.id]
431,955✔
661
                                        )
662
                                );
663
                        }
664
                }
665

666
                return (d, idx) => {
32,690✔
667
                        const {id, value, x} = d;
33,252✔
668
                        const baseValue = getShapeOffsetValue($$, d, isSub);
33,252✔
669
                        const ind = $$.getIndices(indices, d);
33,252✔
670
                        const scale = $$.getYScaleById(id, isSub);
33,252✔
671

672
                        if ($$.isBarRangeType(d)) {
33,252✔
673
                                // TODO use range.getStart()
674
                                return scale(value[0]);
186✔
675
                        }
676

677
                        const dataXAsNumber = Number(x);
33,066✔
678
                        const y0 = scale(groupsZeroAs === "zero" ? 0 : $$.getShapeYMin(id, isSub));
33,066✔
679
                        let offset = y0;
33,066✔
680

681
                        const sameGroupTargets = sameGroupByTargetId?.get(id) ??
33,066✔
682
                                shapeOffsetTargets.filter(t => t.id !== id && ind[t.id] === ind[id]);
369✔
683

684
                        for (const t of sameGroupTargets) {
33,066✔
685
                                const {
686
                                        id: tid,
687
                                        rowValueMapByXValue,
688
                                        rowValues,
689
                                        values: tvalues
690
                                } = t;
49,974✔
691

692
                                // for same stacked group (ind[tid] === ind[id])
693
                                if (indexMapByTargetId[tid] < indexMapByTargetId[id]) {
49,974✔
694
                                        const rValue = tvalues[dataXAsNumber];
25,719✔
695
                                        let row = rowValues[idx];
25,719✔
696

697
                                        // check if the x values line up
698
                                        if (!row || Number(row.x) !== dataXAsNumber) {
25,719✔
699
                                                row = rowValueMapByXValue[dataXAsNumber];
909✔
700
                                        }
701

702
                                        const rowValue = row && getShapeOffsetValue($$, row, isSub);
25,719✔
703

704
                                        if (
25,719✔
705
                                                isNumber(rowValue) &&
67,584✔
706
                                                isNumber(baseValue) &&
707
                                                rowValue * baseValue >= 0 &&
708
                                                isNumber(rValue)
709
                                        ) {
710
                                                const addOffset = baseValue === 0 ?
13,443✔
711
                                                        (
712
                                                                (groupsZeroAs === "positive" &&
840✔
713
                                                                        rValue > 0) ||
714
                                                                (groupsZeroAs === "negative" && rValue < 0)
715
                                                        ) :
716
                                                        true;
717

718
                                                if (addOffset) {
13,443✔
719
                                                        offset += scale(rValue) - y0;
13,281✔
720
                                                }
721
                                        }
722
                                }
723
                        }
724

725
                        return offset;
33,066✔
726
                };
727
        },
728

729
        /**
730
         * Generate line coordinate points from shared geometry.
731
         * @param {object} lineIndices Data order within x axis
732
         * @param {boolean} isSub Whether the coordinates are for subchart
733
         * @param {function} typeFilter Type filter for offset targets
734
         * @returns {function} Line point generator
735
         * @private
736
         */
737
        generateGetLinePoints(lineIndices, isSub?: boolean, typeFilter?: Function): Function {
738
                const $$ = this;
22,299✔
739
                const {config} = $$;
22,299✔
740
                const x = $$.getShapeX(0, lineIndices, isSub);
22,299✔
741
                const y = $$.getShapeY(isSub);
22,299✔
742
                const lineOffset = $$.getShapeOffset(typeFilter || $$.isLineType, lineIndices, isSub);
22,299✔
743
                const yScale = $$.getYScaleById.bind($$);
22,299✔
744

745
                return (d, i) => {
22,299✔
746
                        const y0 = yScale.call($$, d.id, isSub)($$.getShapeYMin(d.id, isSub));
9,156✔
747
                        const offset = lineOffset(d, i) || y0;
9,156✔
748
                        const posX = x(d);
9,156✔
749
                        let posY = y(d);
9,156✔
750

751
                        if (
9,156!
752
                                config.axis_rotated && (
11,676✔
753
                                        (d.value > 0 && posY < y0) || (d.value < 0 && y0 < posY)
754
                                )
755
                        ) {
756
                                posY = y0;
×
757
                        }
758

759
                        const point = [posX, posY - (y0 - offset)];
9,156✔
760

761
                        return [
9,156✔
762
                                point,
763
                                point,
764
                                point,
765
                                point
766
                        ];
767
                };
768
        },
769

770
        /**
771
         * Generate area coordinate points from shared geometry.
772
         * @param {object} areaIndices Data order within x axis
773
         * @param {boolean} isSub Whether the coordinates are for subchart
774
         * @returns {function} Area point generator
775
         * @private
776
         */
777
        generateGetAreaPoints(
778
                areaIndices: TIndices,
779
                isSub?: boolean
780
        ): (d: IDataRow, i: number) => [number, number][] {
781
                const $$ = this;
2,139✔
782
                const {config} = $$;
2,139✔
783
                const x = $$.getShapeX(0, areaIndices, isSub);
2,139✔
784
                const y = $$.getShapeY(!!isSub);
2,139✔
785
                const areaOffset = $$.getShapeOffset($$.isAreaType, areaIndices, isSub);
2,139✔
786
                const yScale = $$.getYScaleById.bind($$);
2,139✔
787

788
                // per-series cache: y0 depends only on the id and is stable within one draw pass
789
                const y0Cache = new Map<string, number>();
2,139✔
790

791
                return function(d, i) {
2,139✔
792
                        let y0 = y0Cache.get(d.id);
2,412✔
793

794
                        if (y0 === undefined) {
2,412✔
795
                                y0 = yScale.call($$, d.id, isSub)($$.getShapeYMin(d.id, isSub)) as number;
429✔
796
                                y0Cache.set(d.id, y0);
429✔
797
                        }
798

799
                        const offset = areaOffset(d, i) || y0;
2,412✔
800
                        const posX = x(d);
2,412✔
801
                        const value = d.value as number;
2,412✔
802
                        let posY = y(d);
2,412✔
803

804
                        if (
2,412!
805
                                config.axis_rotated && (
2,628!
806
                                        (value > 0 && posY < y0) || (value < 0 && y0 < posY)
807
                                )
808
                        ) {
UNCOV
809
                                posY = y0;
×
810
                        }
811

812
                        return [
2,412✔
813
                                [posX, offset],
814
                                [posX, posY - (y0 - offset)],
815
                                [posX, posY - (y0 - offset)],
816
                                [posX, offset]
817
                        ];
818
                };
819
        },
820

821
        /**
822
         * Generate bar coordinate points from shared geometry.
823
         * @param {object} barIndices Data order within x axis
824
         * @param {boolean} isSub Whether the coordinates are for subchart
825
         * @returns {function} Bar point generator
826
         * @private
827
         */
828
        generateGetBarPoints(
829
                barIndices,
830
                isSub?: boolean
831
        ): (d, i: number) => [number, number][] {
832
                const $$ = this;
6,922✔
833
                const {config} = $$;
6,922✔
834
                const axis = isSub ? $$.axis.subX : $$.axis.x;
6,922✔
835
                const barTargetsNum = $$.getIndicesMax(barIndices) + 1;
6,922✔
836
                const barW: IOffset = $$.getBarW("bar", axis, barTargetsNum);
6,922✔
837
                const barX = $$.getShapeX(barW, barIndices, !!isSub);
6,922✔
838
                const barY = $$.getShapeY(!!isSub);
6,922✔
839
                const barOffset = $$.getShapeOffset($$.isBarType, barIndices, !!isSub);
6,922✔
840
                const yScale = $$.getYScaleById.bind($$);
6,922✔
841

842
                // per-series cache: y0/isInverted depend only on the id and are stable within one draw pass
843
                const idCache = new Map<string, {y0: number, isInverted: boolean}>();
6,922✔
844

845
                return (d, i) => {
6,922✔
846
                        const {id} = d;
21,225✔
847
                        let idInfo = idCache.get(id);
21,225✔
848

849
                        if (!idInfo) {
21,225✔
850
                                idInfo = {
5,211✔
851
                                        y0: yScale.call($$, id, isSub)($$.getShapeYMin(id, isSub)),
852
                                        isInverted: config[`axis_${$$.axis.getId(id)}_inverted`]
853
                                };
854

855
                                idCache.set(id, idInfo);
5,211✔
856
                        }
857

858
                        const {y0, isInverted} = idInfo;
21,225✔
859
                        const offset = barOffset(d, i) || y0;
21,225✔
860
                        const width = isNumber(barW) ? barW : barW[d.id] || barW._$width;
21,225✔
861
                        const value = d.value as number;
21,225✔
862
                        const posX = barX(d);
21,225✔
863
                        let posY = barY(d);
21,225✔
864

865
                        if (
21,225!
866
                                config.axis_rotated && !isInverted && (
38,451✔
867
                                        (value > 0 && posY < y0) || (value < 0 && y0 < posY)
868
                                )
869
                        ) {
UNCOV
870
                                posY = y0;
×
871
                        }
872

873
                        if (!$$.isBarRangeType(d)) {
21,225✔
874
                                posY -= y0 - offset;
21,039✔
875
                        }
876

877
                        const startPosX = posX + width;
21,225✔
878

879
                        return [
21,225✔
880
                                [posX, offset],
881
                                [posX, posY],
882
                                [startPosX, posY],
883
                                [startPosX, offset]
884
                        ];
885
                };
886
        },
887

888
        /**
889
         * Get data's y coordinate
890
         * @param {object} d Target data
891
         * @param {number} i Index number
892
         * @returns {number} y coordinate
893
         * @private
894
         */
895
        circleY(d: IDataRow, i: number): number {
896
                const $$ = this;
688,548✔
897
                const id = d.id;
688,548✔
898
                let points;
899

900
                if ($$.isGrouped(id)) {
688,548✔
901
                        points = _getGroupedDataPointsFn.bind($$)(d);
1,776✔
902
                }
903

904
                return points ? points(d, i)[0][1] : $$.getYScaleById(id)($$.getBaseValue(d));
688,548✔
905
        },
906

907
        /**
908
         * Get data point x coordinate.
909
         * @param {object} d Data row
910
         * @returns {number|null} X coordinate
911
         * @private
912
         */
913
        circleX(d): number | null {
914
                return this.xx(d);
687,657✔
915
        },
916

917
        /**
918
         * Generate data point y coordinate accessor.
919
         * @param {boolean} isSub Whether the coordinates are for subchart
920
         * @returns {function} Y coordinate accessor
921
         * @private
922
         */
923
        updateCircleY(isSub = false): Function {
×
924
                const $$ = this;
156✔
925
                const typeFilter = getLinePointGroupTypeFilter($$);
156✔
926
                const getPoints = $$.generateGetLinePoints($$.getShapeIndices(typeFilter), isSub,
156✔
927
                        typeFilter);
928
                const y = $$.getShapeY(isSub);
156✔
929

930
                return (d, i) => {
156✔
UNCOV
931
                        const id = d.id;
×
932

NEW
933
                        return $$.isGrouped(id) && isLinePointGroupType($$, d) ? getPoints(d, i)[0][1] : y(d);
×
934
                };
935
        },
936

937
        /**
938
         * Get candlestick data projected for alternate subchart shapes.
939
         * @param {object} d Data row
940
         * @param {boolean} isSub Whether coordinates are for the subchart
941
         * @returns {number|undefined} Projected value
942
         * @private
943
         */
944
        getSubchartCandlestickShapeValue(d, isSub?: boolean) {
945
                return getSubchartCandlestickShapeValue(this, d, isSub);
908,046✔
946
        },
947

948
        /**
949
         * Check whether the row should be drawn as a candlestick-derived subchart bar.
950
         * @param {object} d Data row
951
         * @param {boolean} isSub Whether coordinates are for the subchart
952
         * @returns {boolean}
953
         * @private
954
         */
955
        isSubchartCandlestickBarValue(d, isSub?: boolean): boolean {
956
                return isSubchartCandlestickBarValue(this, d, isSub);
27✔
957
        },
958

959
        /**
960
         * Get subchart bar color projected from candlestick up/down state.
961
         * @param {object} d Data row
962
         * @param {boolean} isSub Whether coordinates are for the subchart
963
         * @returns {string|null} Bar color
964
         * @private
965
         */
966
        getSubchartCandlestickBarColor(d, isSub?: boolean): string | null {
967
                return getSubchartCandlestickBarColor(this, d, isSub);
14,913✔
968
        },
969

970
        /**
971
         * Get point radius.
972
         * @param {object} d Data row
973
         * @returns {number} Point radius
974
         * @private
975
         */
976
        pointR(d): number {
977
                const $$ = this;
46,503✔
978
                const {config} = $$;
46,503✔
979
                const pointR = config.point_r;
46,503✔
980
                let r = pointR;
46,503✔
981

982
                if ($$.isBubbleType(d)) {
46,503✔
983
                        r = $$.getBubbleR(d);
2,166✔
984
                } else if (isFunction(pointR)) {
44,337!
UNCOV
985
                        r = pointR.bind($$.api)(d);
×
986
                }
987

988
                d.r = r;
46,503✔
989

990
                return r;
46,503✔
991
        },
992

993
        /**
994
         * Get focused point radius.
995
         * @param {object} d Data row
996
         * @returns {number} Focused point radius
997
         * @private
998
         */
999
        pointExpandedR(d): number {
1000
                const $$ = this;
2,115✔
1001
                const {config} = $$;
2,115✔
1002
                const scale = $$.isBubbleType(d) ? 1.15 : 1.75;
2,115✔
1003

1004
                return config.point_focus_expand_enabled ?
2,115!
1005
                        (config.point_focus_expand_r || $$.pointR(d) * scale) :
4,212✔
1006
                        $$.pointR(d);
1007
        },
1008

1009
        /**
1010
         * Get selected point radius.
1011
         * @param {object} d Data row
1012
         * @returns {number} Selected point radius
1013
         * @private
1014
         */
1015
        pointSelectR(d): number {
1016
                const $$ = this;
372✔
1017
                const selectR = $$.config.point_select_r;
372✔
1018

1019
                return isFunction(selectR) ? selectR(d) : (selectR || $$.pointR(d) * 4);
372!
1020
        },
1021

1022
        /**
1023
         * Check if point.focus.only option can be applied.
1024
         * @returns {boolean} Whether focus-only point rendering is active
1025
         * @private
1026
         */
1027
        isPointFocusOnly(): boolean {
1028
                const $$ = this;
67,561✔
1029

1030
                return $$.config.point_focus_only &&
67,561✔
1031
                        !$$.hasType("bubble") && !$$.hasType("scatter") && !$$.hasArcType(null, ["radar"]);
1032
        },
1033

1034
        /**
1035
         * Get data point sensitivity radius.
1036
         * @param {object} d Data point
1037
         * @returns {number} Sensitivity radius
1038
         * @private
1039
         */
1040
        getPointSensitivity(d) {
1041
                const $$ = this;
1,557✔
1042
                let sensitivity = $$.config.point_sensitivity;
1,557✔
1043

1044
                if (!d) {
1,557!
UNCOV
1045
                        return sensitivity;
×
1046
                } else if (isFunction(sensitivity)) {
1,557✔
1047
                        sensitivity = sensitivity.call($$.api, d);
60✔
1048
                } else if (sensitivity === "radius") {
1,497✔
1049
                        sensitivity = d.r;
108✔
1050
                }
1051

1052
                return sensitivity;
1,557✔
1053
        },
1054

1055
        getBarW(type, axis, targetsNum: number): number | IOffset {
1056
                const $$ = this;
8,252✔
1057
                const {config, org, scale, state} = $$;
8,252✔
1058
                const maxDataCount = $$.getMaxDataCount();
8,252✔
1059
                const isGrouped = type === "bar" && config.data_groups?.length;
8,252✔
1060
                const configName = `${type}_width`;
8,252✔
1061
                const {k} = $$.getZoomTransform?.() ?? {k: 1};
8,252✔
1062
                const xMinMax = [
8,252✔
1063
                        config.axis_x_min ?? org.xDomain[0],
16,387✔
1064
                        config.axis_x_max ?? org.xDomain[1]
16,417✔
1065
                ].map(v => ($$.axis.isTimeSeries() ? parseDate.call($$, v) : Number(v))) as [
16,504✔
1066
                        number,
1067
                        number
1068
                ];
1069

1070
                let tickInterval = axis.tickInterval(maxDataCount);
8,252✔
1071

1072
                if (scale.zoom && !$$.axis.isCategorized() && k > 1) {
8,252✔
1073
                        const isSameMinMax = xMinMax.every((v, i) => v === org.xDomain[i]);
282✔
1074

1075
                        tickInterval = org.xDomain.map((v, i) => {
150✔
1076
                                const value = isSameMinMax ? v : v - Math.abs(xMinMax[i]);
300✔
1077

1078
                                return scale.zoom(value);
300✔
1079
                        }).reduce((a, c) => Math.abs(a) + c) / maxDataCount;
150✔
1080
                }
1081

1082
                const getWidth = (id?: string) => {
8,252✔
1083
                        const width = id ? config[configName][id] : config[configName];
8,351✔
1084
                        const ratio = id ? width.ratio : config[`${configName}_ratio`];
8,351✔
1085
                        const max = id ? width.max : config[`${configName}_max`];
8,351✔
1086
                        const w = isNumber(width) ? width : (
8,351✔
1087
                                isFunction(width) ?
8,117✔
1088
                                        width.call($$, state.width, targetsNum, maxDataCount) :
1089
                                        (targetsNum ? (tickInterval * ratio) / targetsNum : 0)
8,099✔
1090
                        );
1091

1092
                        return max && w > max ? max : w;
8,351✔
1093
                };
1094

1095
                let result = getWidth();
8,252✔
1096

1097
                if (!isGrouped && isObjectType(config[configName])) {
8,252✔
1098
                        result = {_$width: result, _$total: []};
285✔
1099

1100
                        $$.getTargetsToShow().forEach(v => {
285✔
1101
                                if (config[configName][v.id]) {
483✔
1102
                                        result[v.id] = getWidth(v.id);
99✔
1103
                                        result._$total.push(result[v.id] || result._$width);
99!
1104
                                }
1105
                        });
1106
                }
1107

1108
                return result;
8,252✔
1109
        },
1110

1111
        /**
1112
         * Get shape element
1113
         * @param {string} shapeName Shape string
1114
         * @param {number} i Index number
1115
         * @param {string} id Data series id
1116
         * @returns {d3Selection}
1117
         * @private
1118
         */
1119
        getShapeByIndex(shapeName: string, i: number, id?: string): d3Selection {
1120
                const $$ = this;
2,082✔
1121
                const {$el} = $$;
2,082✔
1122
                const suffix = isValue(i) ? `-${i}` : ``;
2,082✔
1123
                let shape = $el[shapeName];
2,082✔
1124

1125
                // filter from shape reference if has
1126
                if (shape && !shape.empty()) {
2,082✔
1127
                        shape = shape
2,007✔
1128
                                .filter(d => (id ? d.id === id : true))
18,990✔
1129
                                .filter(d => (isValue(i) ? d.index === i : true));
17,871✔
1130
                } else {
1131
                        shape = (id ?
75!
1132
                                $el.main
1133
                                        .selectAll(
1134
                                                `.${CLASS[`${shapeName}s`]}${$$.getTargetSelectorSuffix(id)}`
1135
                                        ) :
1136
                                $el.main)
1137
                                .selectAll(`.${CLASS[shapeName]}${suffix}`);
1138
                }
1139

1140
                return shape;
2,082✔
1141
        },
1142

1143
        isWithinShape(that, d): boolean {
1144
                const $$ = this;
669✔
1145
                const shape = d3Select(that);
669✔
1146
                let isWithin;
1147

1148
                if (!$$.isTargetToShow(d.id)) {
669!
UNCOV
1149
                        isWithin = false;
×
1150
                } else if ($$.hasValidPointType?.(that.nodeName)) {
669✔
1151
                        isWithin = $$.isStepType(d) ?
447!
1152
                                $$.isWithinStep(that, $$.getYScaleById(d.id)($$.getBaseValue(d))) :
1153
                                $$.isWithinCircle(
1154
                                        that,
1155
                                        $$.isBubbleType(d) ? $$.pointSelectR(d) * 1.5 : 0
447✔
1156
                                );
1157
                } else if (that.nodeName === "path") {
222!
1158
                        isWithin = shape.classed(CLASS.bar) ? $$.isWithinBar(that) : true;
222!
1159
                }
1160

1161
                return isWithin;
669✔
1162
        },
1163

1164
        getInterpolate(d) {
1165
                const $$ = this;
11,929✔
1166
                const interpolation = $$.getInterpolateType(d);
11,929✔
1167

1168
                return CURVE_MAP[interpolation];
11,929✔
1169
        },
1170

1171
        /**
1172
         * Get curve generator for line-like shapes.
1173
         * @param {object} d Data target
1174
         * @returns {function} Curve generator
1175
         * @private
1176
         */
1177
        getCurve(d): Function {
1178
                const $$ = this;
11,923✔
1179
                const isRotatedStepType = $$.config.axis_rotated && $$.isStepType(d);
11,923✔
1180

1181
                // when is step & rotated, should be computed in different way
1182
                // https://github.com/naver/billboard.js/issues/471
1183
                return isRotatedStepType ?
11,923✔
1184
                        context => {
1185
                                const step = $$.getInterpolate(d)(context);
30✔
1186

1187
                                // keep the original method
1188
                                step.orgPoint = step.point;
30✔
1189

1190
                                // to get rotated path data
1191
                                step.pointRotated = function(x, y) {
30✔
1192
                                        this._point === 1 && (this._point = 2);
342✔
1193

1194
                                        const y1 = this._y * (1 - this._t) + y * this._t;
342✔
1195

1196
                                        this._context.lineTo(this._x, y1);
342✔
1197
                                        this._context.lineTo(x, y1);
342✔
1198

1199
                                        this._x = x;
342✔
1200
                                        this._y = y;
342✔
1201
                                };
1202

1203
                                step.point = function(x, y) {
30✔
1204
                                        this._point === 0 ? this.orgPoint(x, y) : this.pointRotated(x, y);
387✔
1205
                                };
1206

1207
                                return step;
30✔
1208
                        } :
1209
                        $$.getInterpolate(d);
1210
        },
1211

1212
        getInterpolateType(d) {
1213
                const $$ = this;
11,941✔
1214
                const {config} = $$;
11,941✔
1215
                const type = config.spline_interpolation_type;
11,941✔
1216
                const interpolation = $$.isInterpolationType(type) ? type : "cardinal";
11,941✔
1217

1218
                return $$.isSplineType(d) ? interpolation : (
11,941✔
1219
                        $$.isStepType(d) ? config.line_step_type : "linear"
11,332✔
1220
                );
1221
        },
1222

1223
        isWithinBar(that): boolean {
1224
                const mouse = getPointer(this.state.event, that);
273✔
1225
                const list = getRectSegList(that);
273✔
1226
                const [seg0, seg1, seg2] = list;
273✔
1227
                const x = Math.min(seg0.x, seg1.x);
273✔
1228
                const y = Math.min(seg0.y, seg1.y);
273✔
1229
                const offset = this.config.bar_sensitivity;
273✔
1230
                const width = Math.abs(seg2.x - seg1.x);
273✔
1231
                const height = Math.abs(seg0.y - seg1.y);
273✔
1232
                const sx = x - offset;
273✔
1233
                const ex = x + width + offset;
273✔
1234
                const sy = y + height + offset;
273✔
1235
                const ey = y - offset;
273✔
1236

1237
                const isWithin = sx < mouse[0] &&
273✔
1238
                        mouse[0] < ex &&
1239
                        ey < mouse[1] &&
1240
                        mouse[1] < sy;
1241

1242
                return isWithin;
273✔
1243
        }
1244
};
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc