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

naver / billboard.js / 15845306589

24 Jun 2025 08:26AM UTC coverage: 94.113% (-0.02%) from 94.134%
15845306589

push

github

web-flow
refactor(data): optimize data key extraction and improve category checking logic (#4009)

* refactor(data,arc): optimize data processing and improve number handling

* refactor(data): optimize data key extraction and improve category checking logic

6354 of 7019 branches covered (90.53%)

Branch coverage included in aggregate %.

13 of 13 new or added lines in 2 files covered. (100.0%)

1 existing line in 1 file now uncovered.

7890 of 8116 relevant lines covered (97.22%)

24489.27 hits per line

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

93.36
/src/ChartInternal/shape/arc.ts
1
/**
2
 * Copyright (c) 2017 ~ present NAVER Corp.
3
 * billboard.js project is licensed under the MIT license
4
 */
5
import {interpolate as d3Interpolate} from "d3-interpolate";
6
import {select as d3Select} from "d3-selection";
7
import {arc as d3Arc, pie as d3Pie} from "d3-shape";
8
import type {d3Selection} from "../../../types/types";
9
import {$ARC, $COMMON, $FOCUS, $GAUGE} from "../../config/classes";
10
import {document} from "../../module/browser";
11
import {
12
        callFn,
13
        endall,
14
        isDefined,
15
        isFunction,
16
        isNumber,
17
        isObject,
18
        isUndefined,
19
        setTextValue,
20
        tplProcess
21
} from "../../module/util";
22
import type {IArcData, IArcDataRow, IData} from "../data/IData";
23

24
/**
25
 * Get radius functions
26
 * @param {number} expandRate Expand rate number.
27
 *   - If 0, means for "normal" radius.
28
 *   - If > 0, means for "expanded" radius.
29
 * @returns {object} radius functions
30
 * @private
31
 */
32
function getRadiusFn(expandRate = 0) {
3,228✔
33
        const $$ = this;
6,510✔
34
        const {config, state} = $$;
6,510✔
35
        const hasMultiArcGauge = $$.hasMultiArcGauge();
6,510✔
36
        const singleArcWidth = state.gaugeArcWidth / $$.filterTargetsToShow($$.data.targets).length;
6,510✔
37
        const expandWidth = expandRate ?
6,510✔
38
                (
39
                        Math.min(
40
                                state.radiusExpanded * expandRate - state.radius,
41
                                singleArcWidth * 0.8 - (1 - expandRate) * 100
42
                        )
43
                ) :
44
                0;
45

46
        return {
6,510✔
47
                /**
48
                 * Getter of arc innerRadius value
49
                 * @param {IArcData} d Data object
50
                 * @returns {number} innerRadius value
51
                 * @private
52
                 */
53
                inner(d: IArcData) {
54
                        const {innerRadius} = $$.getRadius(d);
5,059✔
55

56
                        return hasMultiArcGauge ?
5,059✔
57
                                state.radius - singleArcWidth * (d.index + 1) :
58
                                (isNumber(innerRadius) ? innerRadius : 0);
4,345!
59
                },
60

61
                /**
62
                 * Getter of arc outerRadius value
63
                 * @param {IArcData} d Data object
64
                 * @returns {number} outerRadius value
65
                 * @private
66
                 */
67
                outer(d: IArcData) {
68
                        const {outerRadius} = $$.getRadius(d);
8,669✔
69
                        let radius: number;
70

71
                        if (hasMultiArcGauge) {
8,669✔
72
                                radius = state.radius - singleArcWidth * d.index + expandWidth;
1,428✔
73
                        } else if ($$.hasType("polar") && !expandRate) {
7,241✔
74
                                radius = $$.getPolarOuterRadius(d, outerRadius);
585✔
75
                        } else {
76
                                radius = outerRadius;
6,656✔
77

78
                                if (expandRate) {
6,656✔
79
                                        let {radiusExpanded} = state;
324✔
80

81
                                        if (state.radius !== outerRadius) {
324!
82
                                                radiusExpanded -= Math.abs(state.radius - outerRadius);
×
83
                                        }
84

85
                                        radius = radiusExpanded * expandRate;
324✔
86
                                }
87
                        }
88

89
                        return radius;
8,669✔
90
                },
91

92
                /**
93
                 * Getter of arc cornerRadius value
94
                 * @param {IArcData} d Data object
95
                 * @param {number} outerRadius outer radius value
96
                 * @returns {number} cornerRadius value
97
                 * @private
98
                 */
99
                corner(d: IArcData, outerRadius): number {
100
                        const {
101
                                arc_cornerRadius_ratio: ratio = 0,
×
102
                                arc_cornerRadius: cornerRadius = 0
×
103
                        } = config;
3,610✔
104
                        const {data: {id}, value} = d;
3,610✔
105
                        let corner = 0;
3,610✔
106

107
                        if (ratio) {
3,610✔
108
                                corner = ratio * outerRadius;
131✔
109
                        } else {
110
                                corner = isNumber(cornerRadius) ?
3,479✔
111
                                        cornerRadius :
112
                                        cornerRadius.call($$.api, id, value, outerRadius);
113
                        }
114

115
                        return corner;
3,610✔
116
                }
117
        };
118
}
119

120
/**
121
 * Get attrTween function to get interpolated value on transition
122
 * @param {Function} fn Arc function to execute
123
 * @returns {Function} attrTween function
124
 * @private
125
 */
126
function getAttrTweenFn(fn: (d: IArcData) => string) {
127
        return function(d: IArcData): (t: number) => string {
198✔
128
                const getAngleKeyValue = ({startAngle = 0, endAngle = 0, padAngle = 0}) => ({
564!
129
                        startAngle,
130
                        endAngle,
131
                        padAngle
132
                });
133

134
                // d3.interpolate interpolates id value, if id is given as color string(ex. gold, silver, etc)
135
                // to avoid unexpected behavior, interpolate only angle values
136
                // https://github.com/naver/billboard.js/issues/3321
137
                const interpolate = d3Interpolate(
282✔
138
                        getAngleKeyValue(this._current),
139
                        getAngleKeyValue(d)
140
                );
141

142
                this._current = d;
282✔
143

144
                return function(t: number): string {
282✔
145
                        const interpolated = interpolate(t) as IArcData;
477✔
146
                        const {data, index, value} = d;
477✔
147

148
                        return fn({...interpolated, data, index, value});
477✔
149
                };
150
        };
151
}
152

153
export default {
154
        initPie(): void {
155
                const $$ = this;
627✔
156
                const {config} = $$;
627✔
157
                const dataType = config.data_type;
627✔
158
                const padding = config[`${dataType}_padding`];
627✔
159
                const startingAngle = config[`${dataType}_startingAngle`] || 0;
627✔
160
                const padAngle = (
627✔
161
                        padding ? padding * 0.01 : config[`${dataType}_padAngle`]
627✔
162
                ) || 0;
163

164
                $$.pie = d3Pie()
627✔
165
                        .startAngle(startingAngle)
166
                        .endAngle(startingAngle + (2 * Math.PI))
167
                        .padAngle(padAngle)
168
                        .value((d: IData | any) => d.values?.reduce((a, b) => a + b.value, 0) ?? d)
32,387✔
169
                        .sort($$.getSortCompareFn.bind($$)(true));
170
        },
171

172
        updateRadius(): void {
173
                const $$ = this;
4,803✔
174
                const {config, state} = $$;
4,803✔
175
                const dataType = config.data_type;
4,803✔
176
                const padding = config[`${dataType}_padding`];
4,803✔
177
                const w = config.gauge_width || config.donut_width;
4,803✔
178
                const gaugeArcWidth = $$.filterTargetsToShow($$.data.targets).length *
4,803✔
179
                        config.gauge_arcs_minWidth;
180

181
                // determine radius
182
                state.radiusExpanded = Math.min(state.arcWidth, state.arcHeight) / 2 * (
4,803✔
183
                        $$.hasMultiArcGauge() && config.gauge_label_show ? 0.85 : 1
10,107✔
184
                );
185

186
                state.radius = state.radiusExpanded * 0.95;
4,803✔
187
                state.innerRadiusRatio = w ? (state.radius - w) / state.radius : 0.6;
4,803✔
188

189
                state.gaugeArcWidth = w || (
4,803✔
190
                        gaugeArcWidth <= state.radius - state.innerRadius ?
4,641✔
191
                                state.radius - state.innerRadius :
192
                                (gaugeArcWidth <= state.radius ? gaugeArcWidth : state.radius)
327✔
193
                );
194

195
                const innerRadius = config.pie_innerRadius || (
4,803✔
196
                        padding ? padding * (state.innerRadiusRatio + 0.1) : 0
4,767✔
197
                );
198

199
                // NOTE: inner/outerRadius can be an object by user setting, only for 'pie' type
200
                state.outerRadius = config.pie_outerRadius;
4,803✔
201
                state.innerRadius = $$.hasType("donut") || $$.hasType("gauge") ?
4,803✔
202
                        state.radius * state.innerRadiusRatio :
203
                        innerRadius;
204
        },
205

206
        /**
207
         * Get pie's inner & outer radius value
208
         * @param {object|undefined} d Data object
209
         * @returns {object}
210
         * @private
211
         */
212
        getRadius(d: IArcData): {innerRadius: number, outerRadius: number} {
213
                const $$ = this;
15,177✔
214
                const data = d?.data;
15,177✔
215
                let {innerRadius, outerRadius} = $$.state;
15,177✔
216

217
                if (!isNumber(innerRadius) && data) {
15,177✔
218
                        innerRadius = innerRadius[data.id] || 0;
54✔
219
                }
220

221
                if (isObject(outerRadius) && data && data.id in outerRadius) {
15,177✔
222
                        outerRadius = outerRadius[data.id];
36✔
223
                } else if (!isNumber(outerRadius)) {
15,141✔
224
                        outerRadius = $$.state.radius;
15,087✔
225
                }
226

227
                return {innerRadius, outerRadius};
15,177✔
228
        },
229

230
        updateArc(): void {
231
                const $$ = this;
3,228✔
232

233
                $$.updateRadius();
3,228✔
234
                $$.svgArc = $$.getSvgArc();
3,228✔
235
                $$.svgArcExpanded = $$.getSvgArcExpanded();
3,228✔
236
        },
237

238
        getArcLength(): number {
239
                const $$ = this;
612✔
240
                const {config} = $$;
612✔
241
                const arcLengthInPercent = config.gauge_arcLength * 3.6;
612✔
242
                let len = 2 * (arcLengthInPercent / 360);
612✔
243

244
                if (arcLengthInPercent < -360) {
612!
245
                        len = -2;
×
246
                } else if (arcLengthInPercent > 360) {
612✔
247
                        len = 2;
126✔
248
                }
249

250
                return len * Math.PI;
612✔
251
        },
252

253
        getStartingAngle(): number {
254
                const $$ = this;
9,194✔
255
                const {config} = $$;
9,194✔
256
                const dataType = config.data_type;
9,194✔
257
                const isFullCircle = $$.hasType("gauge") ? config.gauge_fullCircle : false;
9,194✔
258
                const defaultStartAngle = -1 * Math.PI / 2;
9,194✔
259
                const defaultEndAngle = Math.PI / 2;
9,194✔
260
                let startAngle = config[`${dataType}_startingAngle`] || 0;
9,194✔
261

262
                if (!isFullCircle && startAngle <= defaultStartAngle) {
9,194✔
263
                        startAngle = defaultStartAngle;
3,090✔
264
                } else if (!isFullCircle && startAngle >= defaultEndAngle) {
6,104✔
265
                        startAngle = defaultEndAngle;
24✔
266
                } else if (startAngle > Math.PI || startAngle < -1 * Math.PI) {
6,080!
267
                        startAngle = Math.PI;
×
268
                }
269

270
                return startAngle;
9,194✔
271
        },
272

273
        /**
274
         * Update angle data
275
         * @param {object} dValue Data object
276
         * @param {boolean} forRange Weather is for ranged text option(arc.rangeText.values)
277
         * @returns {object|null} Updated angle data
278
         * @private
279
         */
280
        updateAngle(dValue: IArcData, forRange = false) {
4,791✔
281
                const $$ = this;
7,023✔
282
                const {config, state} = $$;
7,023✔
283
                const hasGauge = forRange && $$.hasType("gauge");
7,023✔
284

285
                // to prevent excluding total data sum during the init(when data.hide option is used), use $$.rendered state value
286
                // const totalSum = $$.getTotalDataSum(state.rendered);
287
                let {pie} = $$;
7,023✔
288
                let d = dValue;
7,023✔
289
                let found = false;
7,023✔
290

291
                if (!config) {
7,023✔
292
                        return null;
34✔
293
                }
294

295
                const gStart = $$.getStartingAngle();
6,989✔
296
                const radius = config.gauge_fullCircle || (forRange && !hasGauge) ?
6,989✔
297
                        $$.getArcLength() :
298
                        gStart * -2;
299

300
                if (d.data && $$.isGaugeType(d.data) && !$$.hasMultiArcGauge()) {
6,989✔
301
                        const {gauge_min: gMin, gauge_max: gMax} = config;
1,200✔
302

303
                        // to prevent excluding total data sum during the init(when data.hide option is used), use $$.rendered state value
304
                        const totalSum = $$.getTotalDataSum(state.rendered);
1,200✔
305

306
                        // https://github.com/naver/billboard.js/issues/2123
307
                        const gEnd = radius * ((totalSum - gMin) / (gMax - gMin));
1,200✔
308

309
                        pie = pie
1,200✔
310
                                .startAngle(gStart)
311
                                .endAngle(gEnd + gStart);
312
                }
313

314
                if (forRange === false) {
6,989✔
315
                        pie($$.filterTargetsToShow())
6,704✔
316
                                .forEach((t, i) => {
317
                                        if (!found && t.data.id === d.data?.id) {
22,526✔
318
                                                found = true;
6,545✔
319
                                                d = t;
6,545✔
320
                                                d.index = i;
6,545✔
321
                                        }
322
                                });
323
                }
324

325
                if (isNaN(d.startAngle)) {
6,989!
326
                        d.startAngle = 0;
×
327
                }
328

329
                if (isNaN(d.endAngle)) {
6,989!
UNCOV
330
                        d.endAngle = d.startAngle;
×
331
                }
332

333
                if (forRange || (d.data && (config.gauge_enforceMinMax || $$.hasMultiArcGauge()))) {
6,989✔
334
                        const {gauge_min: gMin, gauge_max: gMax} = config;
1,719✔
335
                        const max = forRange && !hasGauge ? $$.getTotalDataSum(state.rendered) : gMax;
1,719✔
336
                        const gTic = radius / (max - gMin);
1,719✔
337
                        const value = d.value ?? 0;
1,719!
338
                        const gValue = value < gMin ? 0 : value < max ? value - gMin : (max - gMin);
1,719✔
339

340
                        d.startAngle = gStart;
1,719✔
341
                        d.endAngle = gStart + gTic * gValue;
1,719✔
342
                }
343

344
                return found || forRange ? d : null;
6,989✔
345
        },
346

347
        getSvgArc(): Function {
348
                const $$ = this;
3,228✔
349
                const {inner, outer, corner} = getRadiusFn.call($$);
3,228✔
350

351
                const arc = d3Arc()
3,228✔
352
                        .innerRadius(inner)
353
                        .outerRadius(outer);
354

355
                const newArc = function(d: IArcData, withoutUpdate) {
3,228✔
356
                        let path: string | null = "M 0 0";
3,448✔
357

358
                        if (d.value || d.data) {
3,448!
359
                                const data = withoutUpdate ? d : $$.updateAngle(d) ?? null;
3,448!
360

361
                                if (data) {
3,448!
362
                                        path = arc.cornerRadius(
3,448✔
363
                                                corner(data, outer(data))
364
                                        )(data);
365
                                }
366
                        }
367

368
                        return path;
3,448✔
369
                };
370

371
                // TODO: extends all function
372
                newArc.centroid = arc.centroid;
3,228✔
373

374
                return newArc;
3,228✔
375
        },
376

377
        /**
378
         * Get expanded arc path function
379
         * @param {number} rate Expand rate
380
         * @returns {Function} Expanded arc path getter function
381
         * @private
382
         */
383
        getSvgArcExpanded(rate = 1): (d: IArcData) => string {
3,228✔
384
                const $$ = this;
3,282✔
385
                const {inner, outer, corner} = getRadiusFn.call($$, rate);
3,282✔
386

387
                const arc = d3Arc()
3,282✔
388
                        .innerRadius(inner)
389
                        .outerRadius(outer);
390

391
                return (d: IArcData): string => {
3,282✔
392
                        const updated = $$.updateAngle(d);
162✔
393
                        const outerR = outer(updated);
162✔
394
                        let cornerR = 0;
162✔
395

396
                        if (updated) {
162!
397
                                cornerR = corner(updated, outerR);
162✔
398
                        }
399

400
                        return updated ? <string>arc.cornerRadius(cornerR)(updated) : "M 0 0";
162!
401
                };
402
        },
403

404
        getArc(d, withoutUpdate: boolean, force?: boolean): string {
405
                return force || this.isArcType(d.data) ? this.svgArc(d, withoutUpdate) : "M 0 0";
3,133!
406
        },
407

408
        /**
409
         * Render range value text
410
         * @private
411
         */
412
        redrawArcRangeText(): void {
413
                const $$ = this;
723✔
414
                const {config, $el: {arcs}, state, $T} = $$;
723✔
415
                const format = config.arc_rangeText_format;
723✔
416
                const fixed = $$.hasType("gauge") && config.arc_rangeText_fixed;
723✔
417
                let values = config.arc_rangeText_values;
723✔
418

419
                if (values?.length) {
723✔
420
                        const isPercent = config.arc_rangeText_unit === "%";
60✔
421
                        const totalSum = $$.getTotalDataSum(state.rendered);
60✔
422

423
                        if (isPercent) {
60✔
424
                                values = values.map(v => totalSum / 100 * v);
225✔
425
                        }
426

427
                        const pieData = $$.pie(values).map((d, i) => ((d.index = i), d));
315✔
428
                        let rangeText = arcs.selectAll(`.${$ARC.arcRange}`)
60✔
429
                                .data(values);
430

431
                        rangeText.exit();
60✔
432

433
                        rangeText = $T(rangeText.enter()
60✔
434
                                .append("text")
435
                                .attr("class", $ARC.arcRange)
436
                                .style("text-anchor", "middle")
437
                                .style("pointer-events", "none")
438
                                .style("opacity", "0")
439
                                .text(v => {
440
                                        const range = isPercent ? (v / totalSum * 100) : v;
189✔
441

442
                                        return isFunction(format) ? format(range) : (
189✔
443
                                                `${range}${isPercent ? "%" : ""}`
33✔
444
                                        );
445
                                })
446
                                .merge(rangeText));
447

448
                        if ((!state.rendered || (state.rendered && !fixed)) && totalSum > 0) {
60✔
449
                                rangeText.attr("transform", (d, i) => $$.transformForArcLabel(pieData[i], true));
285✔
450
                        }
451

452
                        rangeText.style("opacity",
60✔
453
                                d => (!fixed && (d > totalSum || totalSum === 0) ? "0" : null));
315✔
454
                }
455
        },
456

457
        /**
458
         * Set transform attributes to arc label text
459
         * @param {object} d Data object
460
         * @param {boolean} forRange Weather is for ranged text option(arc.rangeText.values)
461
         * @returns {string} Translate attribute string
462
         * @private
463
         */
464
        transformForArcLabel(d: IArcData, forRange = false): string {
1,947✔
465
                const $$ = this;
2,232✔
466
                const {config, state: {radiusExpanded}} = $$;
2,232✔
467
                const updated = $$.updateAngle(d, forRange);
2,232✔
468
                let translate = "";
2,232✔
469

470
                if (updated) {
2,232✔
471
                        if (forRange || $$.hasMultiArcGauge()) {
2,184✔
472
                                const y1 = Math.sin(updated.endAngle - Math.PI / 2);
642✔
473
                                const rangeTextPosition = config.arc_rangeText_position;
642✔
474
                                let x = Math.cos(updated.endAngle - Math.PI / 2) *
642✔
475
                                        (radiusExpanded + (forRange ? 5 : 25));
642✔
476
                                let y = y1 * (radiusExpanded + 15 - Math.abs(y1 * 10)) + 3;
642✔
477

478
                                if (forRange && rangeTextPosition) {
642✔
479
                                        const rangeValues = config.arc_rangeText_values;
45✔
480
                                        const pos = isFunction(rangeTextPosition) ?
45✔
481
                                                rangeTextPosition(rangeValues[d.index]) :
482
                                                rangeTextPosition;
483

484
                                        x += pos?.x ?? 0;
45✔
485
                                        y += pos?.y ?? 0;
45✔
486
                                }
487

488
                                translate = `translate(${x},${y})`;
642✔
489
                        } else if (!$$.hasType("gauge") || $$.data.targets.length > 1) {
1,542✔
490
                                let {outerRadius} = $$.getRadius(d);
1,449✔
491

492
                                if ($$.hasType("polar")) {
1,449✔
493
                                        outerRadius = $$.getPolarOuterRadius(d, outerRadius);
195✔
494
                                }
495

496
                                const c = this.svgArc.centroid(updated);
1,449✔
497
                                const [x, y] = c.map(v => (isNaN(v) ? 0 : v));
2,898!
498
                                const h = Math.sqrt(x * x + y * y);
1,449✔
499

500
                                let ratio = ["donut", "gauge", "pie", "polar"]
1,449✔
501
                                        .filter($$.hasType.bind($$))
502
                                        .map(v => config[`${v}_label_ratio`])?.[0];
1,449✔
503

504
                                if (ratio) {
1,449✔
505
                                        ratio = isFunction(ratio) ? ratio.bind($$.api)(d, outerRadius, h) : ratio;
36✔
506
                                } else {
507
                                        ratio = outerRadius && (
1,413✔
508
                                                h ?
1,413!
509
                                                        (36 / outerRadius > 0.375 ? 1.175 - 36 / outerRadius : 0.8) *
1,413✔
510
                                                        outerRadius / h :
511
                                                        0
512
                                        );
513
                                }
514

515
                                translate = `translate(${x * ratio},${y * ratio})`;
1,449✔
516
                        }
517
                }
518

519
                return translate;
2,232✔
520
        },
521

522
        convertToArcData(d: IArcData | IArcDataRow): object {
523
                return this.addName({
87✔
524
                        id: "data" in d ? d.data.id : d.id,
87!
525
                        value: d.value,
526
                        ratio: this.getRatio("arc", d),
527
                        index: d.index
528
                });
529
        },
530

531
        textForArcLabel(selection: d3Selection): void {
532
                const $$ = this;
714✔
533
                const hasGauge = $$.hasType("gauge");
714✔
534

535
                if ($$.shouldShowArcLabel()) {
714✔
536
                        selection
708✔
537
                                .style("fill", $$.updateTextColor.bind($$))
538
                                .attr("filter", d =>
539
                                        $$.updateTextBGColor.bind($$)(d, $$.config.data_labels_backgroundColors))
1,932✔
540
                                .each(function(d) {
541
                                        const node = d3Select(this);
1,932✔
542
                                        const updated = $$.updateAngle(d);
1,932✔
543
                                        const ratio = $$.getRatio("arc", updated);
1,932✔
544
                                        const isUnderThreshold = $$.meetsLabelThreshold(ratio,
1,932✔
545
                                                ["donut", "gauge", "pie", "polar"].filter($$.hasType.bind($$))?.[0]);
546

547
                                        if (isUnderThreshold) {
1,932✔
548
                                                const {value} = updated || d;
1,830✔
549
                                                const text = (
1,830✔
550
                                                        $$.getArcLabelFormat() || $$.defaultArcValueFormat
3,270✔
551
                                                )(value, ratio, d.data.id).toString();
552

553
                                                setTextValue(node, text, [-1, 1], hasGauge);
1,830✔
554
                                        } else {
555
                                                node.text("");
102✔
556
                                        }
557
                                });
558
                }
559
        },
560

561
        expandArc(targetIds: string[]): void {
562
                const $$ = this;
60✔
563
                const {state: {transiting}, $el} = $$;
60✔
564

565
                // MEMO: avoid to cancel transition
566
                if (transiting) {
60✔
567
                        const interval = setInterval(() => {
6✔
568
                                if (!transiting) {
322!
569
                                        clearInterval(interval);
×
570

571
                                        $el.legend.selectAll(`.${$FOCUS.legendItemFocused}`).size() > 0 &&
×
572
                                                $$.expandArc(targetIds);
573
                                }
574
                        }, 10);
575

576
                        return;
6✔
577
                }
578

579
                const newTargetIds = $$.mapToTargetIds(targetIds);
54✔
580

581
                $el.svg.selectAll($$.selectorTargets(newTargetIds, `.${$ARC.chartArc}`))
54✔
582
                        .each(function(d) {
583
                                if (!$$.shouldExpand(d.data.id)) {
54!
584
                                        return;
×
585
                                }
586

587
                                const expandDuration = $$.getExpandConfig(d.data.id, "duration");
54✔
588
                                const svgArcExpandedSub = $$.getSvgArcExpanded(
54✔
589
                                        $$.getExpandConfig(d.data.id, "rate")
590
                                );
591

592
                                d3Select(this).selectAll("path")
54✔
593
                                        // @ts-ignore
594
                                        .transition()
595
                                        .duration(expandDuration)
596
                                        .attrTween("d", getAttrTweenFn($$.svgArcExpanded.bind($$)))
597
                                        .transition()
598
                                        .duration(expandDuration * 2)
599
                                        .attrTween("d", getAttrTweenFn(svgArcExpandedSub.bind($$)));
600
                        });
601
        },
602

603
        unexpandArc(targetIds: string[]): void {
604
                const $$ = this;
105✔
605
                const {state: {transiting}, $el: {svg}} = $$;
105✔
606

607
                if (transiting) {
105✔
608
                        return;
15✔
609
                }
610

611
                const newTargetIds = $$.mapToTargetIds(targetIds);
90✔
612

613
                svg.selectAll($$.selectorTargets(newTargetIds, `.${$ARC.chartArc}`))
90✔
614
                        .selectAll("path")
615
                        .transition()
616
                        .duration(d => $$.getExpandConfig(d.data.id, "duration"))
216✔
617
                        .attrTween("d", getAttrTweenFn($$.svgArc.bind($$)));
618

619
                svg.selectAll(`${$ARC.arc}`)
90✔
620
                        .style("opacity", null);
621
        },
622

623
        /**
624
         * Get expand config value
625
         * @param {string} id data ID
626
         * @param {string} key config key: 'duration | rate'
627
         * @returns {number}
628
         * @private
629
         */
630
        getExpandConfig(id: string, key: "duration" | "rate"): number {
631
                const $$ = this;
324✔
632
                const {config} = $$;
324✔
633
                const def = {
324✔
634
                        duration: 50,
635
                        rate: 0.98
636
                };
637
                let type;
638

639
                if ($$.isDonutType(id)) {
324✔
640
                        type = "donut";
90✔
641
                } else if ($$.isGaugeType(id)) {
234!
642
                        type = "gauge";
×
643
                } else if ($$.isPieType(id)) {
234!
644
                        type = "pie";
234✔
645
                }
646

647
                return type ? config[`${type}_expand_${key}`] : def[key];
324!
648
        },
649

650
        shouldExpand(id: string): boolean {
651
                const $$ = this;
54✔
652
                const {config} = $$;
54✔
653

654
                return ($$.isDonutType(id) && config.donut_expand) ||
54!
655
                        ($$.isGaugeType(id) && config.gauge_expand) ||
656
                        ($$.isPieType(id) && config.pie_expand);
657
        },
658

659
        shouldShowArcLabel(): boolean {
660
                const $$ = this;
714✔
661
                const {config} = $$;
714✔
662

663
                return ["donut", "gauge", "pie", "polar"]
714✔
664
                        .some(v => $$.hasType(v) && config[`${v}_label_show`]);
1,698✔
665
        },
666

667
        getArcLabelFormat(): number | string {
668
                const $$ = this;
1,830✔
669
                const {config} = $$;
1,830✔
670
                let format = v => v;
1,830✔
671

672
                ["donut", "gauge", "pie", "polar"]
1,830✔
673
                        .filter($$.hasType.bind($$))
674
                        .forEach(v => {
675
                                format = config[`${v}_label_format`];
1,830✔
676
                        });
677

678
                return isFunction(format) ? format.bind($$.api) : format;
1,830✔
679
        },
680

681
        updateTargetsForArc(targets: IData): void {
682
                const $$ = this;
639✔
683
                const {$el} = $$;
639✔
684
                const hasGauge = $$.hasType("gauge");
639✔
685
                const classChartArc = $$.getChartClass("Arc");
639✔
686
                const classArcs = $$.getClass("arcs", true);
639✔
687
                const classFocus = $$.classFocus.bind($$);
639✔
688
                const chartArcs = $el.main.select(`.${$ARC.chartArcs}`);
639✔
689

690
                const mainPieUpdate = chartArcs
639✔
691
                        .selectAll(`.${$ARC.chartArc}`)
692
                        .data($$.pie(targets))
693
                        .attr("class", d => classChartArc(d) + classFocus(d.data));
18✔
694

695
                const mainPieEnter = mainPieUpdate.enter().append("g")
639✔
696
                        .attr("class", classChartArc)
697
                        .call(
698
                                this.setCssRule(false, `.${$ARC.chartArcs} text`, [
699
                                        "pointer-events:none",
700
                                        "text-anchor:middle"
701
                                ])
702
                        );
703

704
                mainPieEnter.append("g")
639✔
705
                        .attr("class", classArcs)
706
                        .merge(mainPieUpdate);
707

708
                mainPieEnter.append("text")
639✔
709
                        .attr("dy", hasGauge && !$$.hasMultiTargets() ? "-.1em" : ".35em")
1,536✔
710
                        .style("opacity", "0")
711
                        .style("text-anchor", $$.getStylePropValue("middle"))
712
                        .style("pointer-events", $$.getStylePropValue("none"));
713

714
                $el.text = chartArcs.selectAll(`.${$COMMON.target} text`);
639✔
715
                // MEMO: can not keep same color..., but not bad to update color in redraw
716
                // mainPieUpdate.exit().remove();
717
        },
718

719
        initArc(): void {
720
                const $$ = this;
627✔
721
                const {$el} = $$;
627✔
722

723
                $el.arcs = $el.main.select(`.${$COMMON.chart}`)
627✔
724
                        .append("g")
725
                        .attr("class", $ARC.chartArcs)
726
                        .attr("transform", $$.getTranslate("arc"));
727

728
                $$.setArcTitle();
627✔
729
        },
730

731
        /**
732
         * Set arc title text
733
         * @param {string} str Title text
734
         * @private
735
         */
736
        setArcTitle(str?: string) {
737
                const $$ = this;
693✔
738
                const title = str || $$.getArcTitle();
693✔
739
                const hasGauge = $$.hasType("gauge");
693✔
740

741
                if (title) {
693✔
742
                        const className = hasGauge ? $GAUGE.chartArcsGaugeTitle : $ARC.chartArcsTitle;
120✔
743
                        let text = $$.$el.arcs.select(`.${className}`);
120✔
744

745
                        if (text.empty()) {
120✔
746
                                text = $$.$el.arcs.append("text")
54✔
747
                                        .attr("class", className)
748
                                        .style("text-anchor", "middle");
749
                        }
750

751
                        hasGauge && text.attr("dy", "-0.3em");
120✔
752

753
                        setTextValue(text, title, hasGauge ? undefined : [-0.6, 1.35], true);
120✔
754
                }
755
        },
756

757
        /**
758
         * Return arc title text
759
         * @returns {string} Arc title text
760
         * @private
761
         */
762
        getArcTitle(): string {
763
                const $$ = this;
741✔
764
                const type = ($$.hasType("donut") && "donut") || ($$.hasType("gauge") && "gauge");
741✔
765

766
                return type ? $$.config[`${type}_title`] : "";
741✔
767
        },
768

769
        /**
770
         * Get arc title text with needle value
771
         * @returns {string|boolean} When title contains needle template string will return processed string, otherwise false
772
         * @private
773
         */
774
        getArcTitleWithNeedleValue(): string | false {
775
                const $$ = this;
114✔
776
                const {config, state} = $$;
114✔
777
                const title = $$.getArcTitle();
114✔
778

779
                if (title && $$.config.arc_needle_show && /{=[A-Z_]+}/.test(title)) {
114✔
780
                        let value = state.current.needle;
66✔
781

782
                        if (!isNumber(value)) {
66!
783
                                value = config.arc_needle_value;
×
784
                        }
785

786
                        return tplProcess(title, {
66✔
787
                                NEEDLE_VALUE: ~~value
788
                        });
789
                }
790

791
                return false;
48✔
792
        },
793

794
        redrawArc(duration: number, durationForExit: number, withTransform?: boolean): void {
795
                const $$ = this;
723✔
796
                const {config, state, $el: {main}} = $$;
723✔
797
                const hasInteraction = config.interaction_enabled;
723✔
798
                const isSelectable = hasInteraction && config.data_selection_isselectable;
723✔
799

800
                let mainArc = main.selectAll(`.${$ARC.arcs}`)
723✔
801
                        .selectAll(`.${$ARC.arc}`)
802
                        .data($$.arcData.bind($$));
803

804
                mainArc.exit()
723✔
805
                        .transition()
806
                        .duration(durationForExit)
807
                        .style("opacity", "0")
808
                        .remove();
809

810
                mainArc = mainArc.enter()
723✔
811
                        .append("path")
812
                        .attr("class", $$.getClass("arc", true))
813
                        .style("fill", d => $$.color(d.data))
1,695✔
814
                        .style("cursor", d => (isSelectable?.bind?.($$.api)(d) ? "pointer" : null))
1,695✔
815
                        .style("opacity", "0")
816
                        .each(function(d) {
817
                                if ($$.isGaugeType(d.data)) {
1,695✔
818
                                        d.startAngle = config.gauge_startingAngle;
648✔
819
                                        d.endAngle = config.gauge_startingAngle;
648✔
820
                                }
821

822
                                this._current = d;
1,695✔
823
                        })
824
                        .merge(mainArc);
825

826
                if ($$.hasType("gauge")) {
723✔
827
                        $$.updateGaugeMax();
285✔
828
                        $$.hasMultiArcGauge() && $$.redrawArcGaugeLine();
285✔
829
                }
830

831
                mainArc
723✔
832
                        .attr("transform", d => (!$$.isGaugeType(d.data) && withTransform ? "scale(0)" : ""))
1,956✔
833
                        .style("opacity", function(d) {
834
                                return d === this._current ? "0" : null;
1,956✔
835
                        })
836
                        .each(() => {
837
                                state.transiting = true;
1,956✔
838
                        })
839
                        .transition()
840
                        .duration(duration)
841
                        .attrTween("d", function(d) {
842
                                const updated = $$.updateAngle(d);
1,938✔
843

844
                                if (!updated) {
1,938✔
845
                                        return () => "M 0 0";
259✔
846
                                }
847

848
                                if (isNaN(this._current.startAngle)) {
1,841!
849
                                        this._current.startAngle = 0;
×
850
                                }
851

852
                                if (isNaN(this._current.endAngle)) {
1,841!
853
                                        this._current.endAngle = this._current.startAngle;
×
854
                                }
855

856
                                const interpolate = d3Interpolate(this._current, updated);
1,841✔
857

858
                                this._current = interpolate(0);
1,841✔
859

860
                                return function(t) {
1,841✔
861
                                        const interpolated = interpolate(t);
2,584✔
862

863
                                        interpolated.data = d.data; // data.id will be updated by interporator
2,584✔
864

865
                                        return $$.getArc(interpolated, true);
2,584✔
866
                                };
867
                        })
868
                        .attr("transform", withTransform ? "scale(1)" : "")
723✔
869
                        .style("fill", d => {
870
                                let color;
871

872
                                if ($$.levelColor) {
1,956✔
873
                                        color = $$.levelColor(d.data.values[0].value);
360✔
874

875
                                        // update data's color
876
                                        config.data_colors[d.data.id] = color;
360✔
877
                                } else {
878
                                        color = $$.color(d.data);
1,596✔
879
                                }
880

881
                                return color;
1,956✔
882
                        })
883
                        // Where gauge reading color would receive customization.
884
                        .style("opacity", null)
885
                        .call(endall, function() {
886
                                if ($$.levelColor) {
696✔
887
                                        const path = d3Select(this);
93✔
888
                                        const d: any = path.datum(this._current);
93✔
889

890
                                        $$.updateLegendItemColor(d.data.id, path.style("fill"));
93✔
891
                                }
892

893
                                state.transiting = false;
696✔
894
                                callFn(config.onrendered, $$.api);
696✔
895
                        });
896

897
                // bind arc events
898
                hasInteraction && $$.bindArcEvent(mainArc);
723✔
899

900
                $$.hasType("polar") && $$.redrawPolar();
723✔
901
                $$.hasType("gauge") && $$.redrawBackgroundArcs();
723✔
902

903
                config.arc_needle_show && $$.redrawNeedle();
723✔
904
                $$.redrawArcText(duration);
723✔
905
                $$.redrawArcRangeText();
723✔
906
        },
907

908
        /**
909
         * Update needle element
910
         * @private
911
         */
912
        redrawNeedle(): void {
913
                const $$ = this;
87✔
914
                const {$el, config, state: {hiddenTargetIds, radius}} = $$;
87✔
915
                const length = (radius - 1) / 100 * config.arc_needle_length;
87✔
916
                const hasDataToShow = hiddenTargetIds.length !== $$.data.targets.length;
87✔
917
                let needle = $$.$el.arcs.select(`.${$ARC.needle}`);
87✔
918

919
                // needle options
920
                const pathFn = config.arc_needle_path;
87✔
921
                const baseWidth = config.arc_needle_bottom_width / 2;
87✔
922
                const topWidth = config.arc_needle_top_width / 2;
87✔
923
                const topRx = config.arc_needle_top_rx;
87✔
924
                const topRy = config.arc_needle_top_ry;
87✔
925
                const bottomLen = config.arc_needle_bottom_len;
87✔
926
                const bottomRx = config.arc_needle_bottom_rx;
87✔
927
                const bottomRy = config.arc_needle_bottom_ry;
87✔
928
                const needleAngle = $$.getNeedleAngle();
87✔
929

930
                const updateNeedleValue = () => {
87✔
931
                        const title = $$.getArcTitleWithNeedleValue();
114✔
932

933
                        title && $$.setArcTitle(title);
114✔
934
                };
935

936
                updateNeedleValue();
87✔
937

938
                if (needle.empty()) {
87✔
939
                        needle = $el.arcs
66✔
940
                                .append("path")
941
                                .classed($ARC.needle, true);
942

943
                        $el.needle = needle;
66✔
944

945
                        /**
946
                         * Function to be exposed as public to facilitate updating needle
947
                         * @param {number} v Value to be updated
948
                         * @param {boolean} updateConfig Weather update config's value
949
                         * @private
950
                         */
951
                        $el.needle.updateHelper = (v: number, updateConfig = false): void => {
66✔
952
                                if ($el.needle.style("display") !== "none") {
27!
953
                                        $$.$T($el.needle)
27✔
954
                                                .style("transform", `rotate(${$$.getNeedleAngle(v)}deg)`)
955
                                                .call(endall, () => {
956
                                                        updateConfig && (config.arc_needle_value = v);
27✔
957
                                                        updateNeedleValue();
27✔
958
                                                });
959
                                }
960
                        };
961
                }
962

963
                if (hasDataToShow) {
87!
964
                        const path = isFunction(pathFn) ?
87✔
965
                                pathFn.call($$, length) :
966
                                `M-${baseWidth} ${bottomLen} A${bottomRx} ${bottomRy} 0 0 0 ${baseWidth} ${bottomLen} L${topWidth} -${length} A${topRx} ${topRy} 0 0 0 -${topWidth} -${length} L-${baseWidth} ${bottomLen} Z`;
967

968
                        $$.$T(needle)
87✔
969
                                .attr("d", path)
970
                                .style("fill", config.arc_needle_color)
971
                                .style("display", null)
972
                                .style("transform", `rotate(${needleAngle}deg)`);
973
                } else {
974
                        needle.style("display", "none");
×
975
                }
976
        },
977

978
        /**
979
         * Get needle angle value relative given value
980
         * @param {number} v Value to be calculated angle
981
         * @returns {number} angle value
982
         * @private
983
         */
984
        getNeedleAngle(v?: number): number {
985
                const $$ = this;
114✔
986
                const {config, state} = $$;
114✔
987
                const arcLength = $$.getArcLength();
114✔
988
                const hasGauge = $$.hasType("gauge");
114✔
989
                const total = $$.getTotalDataSum(true);
114✔
990
                let value = isDefined(v) ? v : config.arc_needle_value;
114✔
991
                let startingAngle = config[`${config.data_type}_startingAngle`] || 0;
114✔
992
                let radian = 0;
114✔
993

994
                if (!isNumber(value)) {
114✔
995
                        value = hasGauge && $$.data.targets.length === 1 ? total : 0;
30✔
996
                }
997

998
                state.current.needle = value;
114✔
999

1000
                if (hasGauge) {
114✔
1001
                        startingAngle = $$.getStartingAngle();
60✔
1002

1003
                        const radius = config.gauge_fullCircle ? arcLength : startingAngle * -2;
60✔
1004
                        const {gauge_min: min, gauge_max: max} = config;
60✔
1005

1006
                        radian = radius * ((value - min) / (max - min));
60✔
1007
                } else {
1008
                        radian = arcLength * (value / total);
54✔
1009
                }
1010

1011
                return (startingAngle + radian) * (180 / Math.PI);
114✔
1012
        },
1013

1014
        redrawBackgroundArcs() {
1015
                const $$ = this;
285✔
1016
                const {config, state} = $$;
285✔
1017
                const hasMultiArcGauge = $$.hasMultiArcGauge();
285✔
1018
                const isFullCircle = config.gauge_fullCircle;
285✔
1019
                const showEmptyTextLabel = $$.filterTargetsToShow($$.data.targets).length === 0 &&
285✔
1020
                        !!config.data_empty_label_text;
1021

1022
                const startAngle = $$.getStartingAngle();
285✔
1023
                const endAngle = isFullCircle ? startAngle + $$.getArcLength() : startAngle * -1;
285✔
1024

1025
                let backgroundArc = $$.$el.arcs.select(
285✔
1026
                        `${hasMultiArcGauge ? "g" : ""}.${$ARC.chartArcsBackground}`
285✔
1027
                );
1028

1029
                if (hasMultiArcGauge) {
285✔
1030
                        let index = 0;
84✔
1031

1032
                        backgroundArc = backgroundArc
84✔
1033
                                .selectAll(`path.${$ARC.chartArcsBackground}`)
1034
                                .data($$.data.targets);
1035

1036
                        backgroundArc.enter()
84✔
1037
                                .append("path")
1038
                                .attr("class", (d, i) =>
1039
                                        `${$ARC.chartArcsBackground} ${$ARC.chartArcsBackground}-${i}`)
357✔
1040
                                .merge(backgroundArc)
1041
                                .style("fill", (config.gauge_background) || null)
141✔
1042
                                .attr("d", ({id}) => {
1043
                                        if (showEmptyTextLabel || state.hiddenTargetIds.indexOf(id) >= 0) {
357!
1044
                                                return "M 0 0";
×
1045
                                        }
1046

1047
                                        const d = {
357✔
1048
                                                data: [{value: config.gauge_max}],
1049
                                                startAngle,
1050
                                                endAngle,
1051
                                                index: index++
1052
                                        };
1053

1054
                                        return $$.getArc(d, true, true);
357✔
1055
                                });
1056

1057
                        backgroundArc.exit().remove();
84✔
1058
                } else {
1059
                        backgroundArc.attr("d", showEmptyTextLabel ? "M 0 0" : () => {
201✔
1060
                                const d = {
192✔
1061
                                        data: [{value: config.gauge_max}],
1062
                                        startAngle,
1063
                                        endAngle
1064
                                };
1065

1066
                                return $$.getArc(d, true, true);
192✔
1067
                        });
1068
                }
1069
        },
1070

1071
        bindArcEvent(arc): void {
1072
                const $$ = this;
720✔
1073
                const {config, state} = $$;
720✔
1074
                const isTouch = state.inputType === "touch";
720✔
1075
                const isMouse = state.inputType === "mouse";
720✔
1076

1077
                // eslint-disable-next-line
1078
                function selectArc(_this, arcData, id) {
1079
                        // transitions
1080
                        $$.expandArc(id);
21✔
1081
                        $$.api.focus(id);
21✔
1082
                        $$.toggleFocusLegend(id, true);
21✔
1083
                        $$.showTooltip([arcData], _this);
21✔
1084
                }
1085

1086
                // eslint-disable-next-line
1087
                function unselectArc(arcData?) {
1088
                        const id = arcData?.id || undefined;
12✔
1089

1090
                        $$.unexpandArc(id);
12✔
1091
                        $$.api.revert();
12✔
1092
                        $$.revertLegend();
12✔
1093
                        $$.hideTooltip();
12✔
1094
                }
1095

1096
                arc
720✔
1097
                        .on("click", function(event, d, i) {
1098
                                const updated = $$.updateAngle(d);
12✔
1099
                                let arcData;
1100

1101
                                if (updated) {
12!
1102
                                        arcData = $$.convertToArcData(updated);
12✔
1103

1104
                                        $$.toggleShape?.(this, arcData, i);
12✔
1105
                                        config.data_onclick.bind($$.api)(arcData, this);
12✔
1106
                                }
1107
                        });
1108

1109
                // mouse events
1110
                if (isMouse) {
720✔
1111
                        arc
711✔
1112
                                .on("mouseover", function(event, d) {
1113
                                        if (state.transiting) { // skip while transiting
36✔
1114
                                                return;
15✔
1115
                                        }
1116

1117
                                        state.event = event;
21✔
1118
                                        const updated = $$.updateAngle(d);
21✔
1119
                                        const arcData = updated ? $$.convertToArcData(updated) : null;
21!
1120
                                        const id = arcData?.id || undefined;
21!
1121

1122
                                        selectArc(this, arcData, id);
21✔
1123
                                        $$.setOverOut(true, arcData);
21✔
1124
                                })
1125
                                .on("mouseout", (event, d) => {
1126
                                        if (state.transiting || !config.interaction_onout) { // skip while transiting
9!
1127
                                                return;
×
1128
                                        }
1129

1130
                                        state.event = event;
9✔
1131
                                        const updated = $$.updateAngle(d);
9✔
1132
                                        const arcData = updated ? $$.convertToArcData(updated) : null;
9!
1133

1134
                                        unselectArc();
9✔
1135
                                        $$.setOverOut(false, arcData);
9✔
1136
                                })
1137
                                .on("mousemove", function(event, d) {
1138
                                        const updated = $$.updateAngle(d);
42✔
1139
                                        const arcData = updated ? $$.convertToArcData(updated) : null;
42!
1140

1141
                                        state.event = event;
42✔
1142
                                        $$.showTooltip([arcData], this);
42✔
1143
                                });
1144
                }
1145

1146
                // touch events
1147
                if (isTouch && $$.hasArcType() && !$$.radars) {
720✔
1148
                        const getEventArc = event => {
9✔
1149
                                const {clientX, clientY} = event.changedTouches?.[0] ?? {clientX: 0, clientY: 0};
3✔
1150
                                const eventArc = d3Select(document.elementFromPoint(clientX, clientY));
3✔
1151

1152
                                return eventArc;
3✔
1153
                        };
1154

1155
                        $$.$el.svg
9✔
1156
                                .on("touchstart touchmove", function(event) {
1157
                                        if (state.transiting) { // skip while transiting
3!
1158
                                                return;
×
1159
                                        }
1160

1161
                                        state.event = event;
3✔
1162

1163
                                        const eventArc = getEventArc(event);
3✔
1164
                                        const datum: any = eventArc.datum();
3✔
1165
                                        const updated = (datum?.data && datum.data.id) ? $$.updateAngle(datum) : null;
3!
1166
                                        const arcData = updated ? $$.convertToArcData(updated) : null;
3!
1167
                                        const id = arcData?.id || undefined;
3✔
1168

1169
                                        $$.callOverOutForTouch(arcData);
3✔
1170

1171
                                        isUndefined(id) ? unselectArc() : selectArc(this, arcData, id);
3!
1172
                                });
1173
                }
1174
        },
1175

1176
        redrawArcText(duration: number): void {
1177
                const $$ = this;
723✔
1178
                const {config, state, $el: {main, arcs}} = $$;
723✔
1179
                const hasGauge = $$.hasType("gauge");
723✔
1180
                const hasMultiArcGauge = $$.hasMultiArcGauge();
723✔
1181
                let text;
1182

1183
                // for gauge type, update text when has no title & multi data
1184
                if (!(hasGauge && $$.data.targets.length === 1 && config.gauge_title)) {
723✔
1185
                        text = main.selectAll(`.${$ARC.chartArc}`)
714✔
1186
                                .select("text")
1187
                                .style("opacity", "0")
1188
                                .attr("class", d => ($$.isGaugeType(d.data) ? $GAUGE.gaugeValue : null))
1,947✔
1189
                                .call($$.textForArcLabel.bind($$))
1190
                                .attr("transform", d => $$.transformForArcLabel.bind($$)(d))
1,947✔
1191
                                .style("font-size", d => (
1192
                                        $$.isGaugeType(d.data) && $$.data.targets.length === 1 && !hasMultiArcGauge ?
1,947✔
1193
                                                `${Math.round(state.radius / 5)}px` :
1194
                                                null
1195
                                ))
1196
                                .transition()
1197
                                .duration(duration)
1198
                                .style("opacity",
1199
                                        d => ($$.isTargetToShow(d.data.id) && $$.isArcType(d.data) ? null : "0"));
1,947✔
1200

1201
                        hasMultiArcGauge && text.attr("dy", "-.1em");
714✔
1202
                }
1203

1204
                main.select(`.${$ARC.chartArcsTitle}`)
723✔
1205
                        .style("opacity", $$.hasType("donut") || hasGauge ? null : "0");
2,046✔
1206

1207
                if (hasGauge) {
723✔
1208
                        const isFullCircle = config.gauge_fullCircle;
285✔
1209

1210
                        isFullCircle &&
285✔
1211
                                text?.attr("dy", `${hasMultiArcGauge ? 0 : Math.round(state.radius / 14)}`);
30✔
1212

1213
                        if (config.gauge_label_show) {
285✔
1214
                                arcs.select(`.${$GAUGE.chartArcsGaugeUnit}`)
279✔
1215
                                        .attr("dy", `${isFullCircle ? 1.5 : 0.75}em`)
279✔
1216
                                        .text(config.gauge_units);
1217

1218
                                arcs.select(`.${$GAUGE.chartArcsGaugeMin}`)
279✔
1219
                                        .attr("dx", `${
1220
                                                -1 *
1221
                                                (state.innerRadius +
1222
                                                        ((state.radius - state.innerRadius) / (isFullCircle ? 1 : 2)))
279✔
1223
                                        }px`)
1224
                                        .attr("dy", "1.2em")
1225
                                        .text($$.textForGaugeMinMax(config.gauge_min, false));
1226

1227
                                // show max text when isn't fullCircle
1228
                                !isFullCircle && arcs.select(`.${$GAUGE.chartArcsGaugeMax}`)
279✔
1229
                                        .attr("dx", `${state.innerRadius + ((state.radius - state.innerRadius) / 2)}px`)
1230
                                        .attr("dy", "1.2em")
1231
                                        .text($$.textForGaugeMinMax(config.gauge_max, true));
1232
                        }
1233
                }
1234
        },
1235

1236
        /**
1237
         * Get Arc element by id or index
1238
         * @param {string|number} value id or index of Arc
1239
         * @returns {d3Selection} Arc path element
1240
         * @private
1241
         */
1242
        getArcElementByIdOrIndex(value: string | number): d3Selection {
1243
                const $$ = this;
60✔
1244
                const {$el: {arcs}} = $$;
60✔
1245
                const filterFn = isNumber(value) ? d => d.index === value : d => d.data.id === value;
162✔
1246

1247
                return arcs?.selectAll(`.${$COMMON.target} path`)
60✔
1248
                        .filter(filterFn);
1249
        }
1250
};
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc