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

naver / billboard.js / 27122665529

08 Jun 2026 07:32AM UTC coverage: 92.54% (-1.1%) from 93.621%
27122665529

push

github

web-flow
feat(canvas): add canvas rendering mode

Add canvas entry, renderer engine, axis renderer, theme probing, and hit detection.
Support canvas flow, subchart, zoom, selection, grid/regions, export, tooltip, and focus.
Add tests, benchmarks, types, and docs for canvas mode limitations.

10455 of 11840 branches covered (88.3%)

Branch coverage included in aggregate %.

5094 of 5363 new or added lines in 68 files covered. (94.98%)

19 existing lines in 3 files now uncovered.

13349 of 13883 relevant lines covered (96.15%)

26556.19 hits per line

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

96.08
/src/ChartInternal/ChartInternal.ts
1
/**
2
 * Copyright (c) 2017 ~ present NAVER Corp.
3
 * billboard.js project is licensed under the MIT license
4
 * @ignore
5
 */
6
import {select as d3Select} from "d3-selection";
7
import {
8
        timeFormat as d3TimeFormat,
9
        timeParse as d3TimeParse,
10
        utcFormat as d3UtcFormat,
11
        utcParse as d3UtcParse
12
} from "d3-time-format";
13
import type {d3Selection, d3Transition} from "../../types/types";
14
import {$CIRCLE, $COMMON, $TEXT} from "../config/classes";
15
import Options from "../config/Options/Options";
16
import Store from "../config/Store/Store";
17
import {document, window} from "../module/browser";
18
import Cache from "../module/Cache";
19
import {checkModuleImport} from "../module/error";
20
import {generateResize} from "../module/generator";
21
import {
22
        callFn,
23
        capitalize,
24
        convertInputType,
25
        extend,
26
        getOption,
27
        getRandom,
28
        hasStyle,
29
        isFunction,
30
        isObject,
31
        isString,
32
        notEmpty,
33
        sortValue
34
} from "../module/util";
35

36
// data
37
import dataConvert from "./data/convert";
38
import data from "./data/data";
39
import dataLoad from "./data/load";
40

41
// interactions
42
import interaction from "./interactions/interaction";
43

44
// internals
45
import category from "./internals/category"; // used to retrieve radar Axis name
46
import classModule from "./internals/class";
47
import color from "./internals/color";
48
import domain from "./internals/domain";
49
import format from "./internals/format";
50
import legend from "./internals/legend";
51
import redraw from "./internals/redraw";
52
import scale from "./internals/scale";
53
import size from "./internals/size";
54
import style from "./internals/style";
55
import text from "./internals/text";
56
import title from "./internals/title";
57
import tooltip from "./internals/tooltip";
58
import transform from "./internals/transform";
59
import typeInternals from "./internals/type";
60
import shape from "./shape/shape";
61

62
/**
63
 * Internal chart class.
64
 * - Note: Instantiated internally, not exposed for public.
65
 * @class ChartInternal
66
 * @ignore
67
 * @private
68
 */
69
export default class ChartInternal {
70
        public api; // API interface
71
        public config; // config object
72
        public cache; // cache instance
73
        public $el; // elements
74
        public state; // state variables
75
        public charts; // all Chart instances array within page (equivalent of 'bb.instances')
76

77
        // data object
78
        public data = {
6,984✔
79
                xs: {},
80
                targets: []
81
        };
82

83
        // Axis
84
        public axis; // Axis
85

86
        // scales
87
        public scale = {
6,984✔
88
                x: null,
89
                y: null,
90
                y2: null,
91
                subX: null,
92
                subY: null,
93
                subY2: null,
94
                zoom: null
95
        };
96

97
        // original values
98
        public org = {
6,984✔
99
                xScale: null,
100
                xDomain: null
101
        };
102

103
        // formatter function
104
        public color;
105
        public patterns;
106
        public levelColor;
107
        public point;
108
        public brush;
109

110
        // format function
111
        public format = {
6,984✔
112
                extraLineClasses: null,
113
                xAxisTick: null,
114
                dataTime: null, // dataTimeFormat
115
                defaultAxisTime: null, // defaultAxisTimeFormat
116
                axisTime: null // axisTimeFormat
117
        };
118

119
        constructor(api) {
120
                const $$ = this;
6,984✔
121

122
                $$.api = api; // Chart class instance alias
6,984✔
123
                $$.config = new Options();
6,984✔
124
                $$.cache = new Cache();
6,984✔
125

126
                const store = new Store();
6,984✔
127

128
                $$.$el = store.getStore("element");
6,984✔
129
                $$.state = store.getStore("state");
6,984✔
130

131
                $$.$T = $$.$T.bind($$);
6,984✔
132
        }
133

134
        /**
135
         * Get the selection based on transition config
136
         * @param {SVGElement|d3Selection} selection Target selection
137
         * @param {boolean} force Force transition
138
         * @param {string} name Transition name
139
         * @returns {d3Selection}
140
         * @private
141
         */
142
        $T(selection: SVGElement | d3Selection | d3Transition, force?: boolean,
143
                name?: string): d3Selection {
144
                const {config, state} = this;
219,222✔
145
                const duration = config.transition_duration;
219,222✔
146
                const subchart = config.subchart_show;
219,222✔
147
                let t = selection;
219,222✔
148

149
                if (t) {
219,222✔
150
                        // in case of non d3 selection, wrap with d3 selection
151
                        if ("tagName" in t) {
204,450✔
152
                                t = d3Select(t);
5,988✔
153
                        }
154

155
                        // do not transit on:
156
                        // - wheel zoom (state.zooming = true)
157
                        // - when has no subchart
158
                        // - initialization
159
                        // - resizing
160
                        const transit = ((force !== false && duration) || force) &&
204,450✔
161
                                (!state.zooming || state.dragging) &&
162
                                !state.resizing &&
163
                                state.rendered &&
164
                                !subchart;
165

166
                        // @ts-ignore
167
                        t = (transit ? t.transition(name).duration(duration) : t) as d3Selection;
204,450✔
168
                }
169

170
                return t;
219,222✔
171
        }
172

173
        beforeInit(): void {
174
                const $$ = this;
6,984✔
175

176
                $$.callPluginHook("$beforeInit");
6,984✔
177

178
                // can do something
179
                callFn($$.config.onbeforeinit, $$.api);
6,978✔
180
        }
181

182
        afterInit(): void {
183
                const $$ = this;
6,876✔
184

185
                $$.callPluginHook("$afterInit");
6,876✔
186

187
                // can do something
188
                callFn($$.config.onafterinit, $$.api);
6,876✔
189
        }
190

191
        init(): void {
192
                const $$ = <any>this;
6,978✔
193
                const {config, state, $el} = $$;
6,978✔
194
                const {boost_useCssRule, bindto} = config;
6,978✔
195

196
                checkModuleImport($$);
6,978✔
197

198
                const hasArcType = $$.hasArcType();
6,915✔
199
                state.hasRadar = !state.hasAxis && $$.hasType("radar");
6,915✔
200
                state.hasFunnel = !state.hasAxis && $$.hasType("funnel");
6,915✔
201
                state.hasTreemap = !state.hasAxis && $$.hasType("treemap");
6,915✔
202
                state.hasAxis = !hasArcType && !state.hasFunnel && !state.hasTreemap;
6,915✔
203

204
                // datetime to be used for uniqueness
205
                state.datetimeId = `bb-${+new Date() * (getRandom() as number)}`;
6,915✔
206

207
                if (boost_useCssRule) {
6,915✔
208
                        // append style element
209
                        const styleEl = document.createElement("style");
18✔
210

211
                        // styleEl.id = styleId;
212
                        styleEl.type = "text/css";
18✔
213
                        document.head.appendChild(styleEl);
18✔
214

215
                        state.style = {
18✔
216
                                rootSelector: `.${state.datetimeId}`,
217
                                sheet: styleEl.sheet
218
                        };
219

220
                        // used on .destroy()
221
                        $el.style = styleEl;
18✔
222
                }
223

224
                const bindConfig = {
6,915✔
225
                        element: bindto,
226
                        classname: "bb"
227
                };
228

229
                if (isObject(bindto)) {
6,915✔
230
                        bindConfig.element = bindto.element || "#chart";
18!
231
                        bindConfig.classname = bindto.classname || bindConfig.classname;
18!
232
                }
233

234
                // select bind element
235
                $el.chart = isFunction(bindConfig.element.node) ?
6,915!
236
                        bindto.element :
237
                        d3Select(bindConfig.element || []);
6,915!
238

239
                if ($el.chart.empty()) {
6,915✔
240
                        $el.chart = d3Select(document.body.appendChild(document.createElement("div")));
114✔
241
                }
242

243
                $el.chart.html("")
6,915✔
244
                        .classed(bindConfig.classname, true)
245
                        .classed(state.datetimeId, boost_useCssRule)
246
                        .style("position", "relative");
247

248
                $$.initParams();
6,915✔
249
                $$.initToRender();
6,915✔
250
        }
251

252
        /**
253
         * Initialize the rendering process
254
         * @param {boolean} forced Force to render process
255
         * @private
256
         */
257
        initToRender(forced?: boolean): void {
258
                const $$ = <any>this;
6,927✔
259
                const {config, state, $el: {chart}} = $$;
6,927✔
260
                const isHidden = () => hasStyle(chart, {display: "none", visibility: "hidden"});
6,927✔
261

262
                const isLazy = config.render.lazy === false ? false : config.render.lazy || isHidden();
6,927✔
263
                const MutationObserver = window.MutationObserver;
6,927✔
264

265
                if (isLazy && MutationObserver && config.render.observe !== false && !forced) {
6,927✔
266
                        new MutationObserver((mutation, observer) => {
9✔
267
                                if (!isHidden()) {
9!
268
                                        observer.disconnect();
9✔
269
                                        !state.rendered && $$.initToRender(true);
9✔
270
                                }
271
                        }).observe(chart.node(), {
272
                                attributes: true,
273
                                attributeFilter: ["class", "style"]
274
                        });
275
                }
276

277
                if (!isLazy || forced) {
6,927✔
278
                        $$.convertData(config, res => {
6,915✔
279
                                $$.initWithData(res);
6,876✔
280
                                $$.afterInit();
6,876✔
281
                        });
282
                }
283
        }
284

285
        initParams(): void {
286
                const $$ = <any>this;
6,915✔
287
                const {config, format, state} = $$;
6,915✔
288

289
                if (config.render_mode === "canvas") {
6,915✔
290
                        $$.prepareCanvasConfig?.();
627✔
291
                }
292

293
                // color settings
294
                $$.color = $$.generateColor();
6,915✔
295
                $$.levelColor = $$.generateLevelColor();
6,915✔
296

297
                // when 'padding=false' is set, disable axes and subchart. Because they are useless.
298
                if (config.padding === false) {
6,915✔
299
                        config.axis_x_show = false;
36✔
300
                        config.axis_y_show = false;
36✔
301
                        config.axis_y2_show = false;
36✔
302
                        config.subchart_show = false;
36✔
303
                }
304

305
                if (config.render_mode !== "canvas" && ($$.hasPointType() || $$.hasLegendDefsPoint?.())) {
6,915✔
306
                        $$.point = $$.generatePoint();
1,767✔
307
                }
308

309
                if (state.hasAxis) {
6,915✔
310
                        $$.initClip();
5,892✔
311

312
                        format.extraLineClasses = $$.generateExtraLineClass();
5,892✔
313
                        format.dataTime = config.data_xLocaltime ? d3TimeParse : d3UtcParse;
5,892!
314
                        format.axisTime = config.axis_x_localtime ? d3TimeFormat : d3UtcFormat;
5,892✔
315

316
                        const isDragZoom = config.zoom_enabled && config.zoom_type === "drag";
5,892✔
317

318
                        format.defaultAxisTime = d => {
5,892✔
319
                                const {x, zoom} = $$.scale;
1,731✔
320
                                const isZoomed = isDragZoom ?
1,731✔
321
                                        zoom :
322
                                        zoom && x.orgDomain().toString() !== zoom.domain().toString();
1,767✔
323

324
                                const specifier: string = (d.getMilliseconds() && ".%L") ||
1,731!
325
                                        (d.getSeconds() && ".:%S") ||
326
                                        (d.getMinutes() && "%I:%M") ||
327
                                        (d.getHours() && "%I %p") ||
328
                                        (d.getDate() !== 1 && "%b %d") ||
329
                                        (isZoomed && d.getDate() === 1 && "%b'%y") ||
330
                                        (d.getMonth() && "%-m/%-d") || "%Y";
331

332
                                return format.axisTime(specifier)(d);
1,731✔
333
                        };
334
                }
335

336
                const {legend_position, legend_inset_anchor, axis_rotated} = config;
6,915✔
337

338
                state.isLegendRight = legend_position === "right";
6,915✔
339
                state.isLegendInset = legend_position === "inset";
6,915✔
340
                state.isLegendTop = legend_inset_anchor === "top-left" ||
6,915✔
341
                        legend_inset_anchor === "top-right";
342
                state.isLegendLeft = legend_inset_anchor === "top-left" ||
6,915✔
343
                        legend_inset_anchor === "bottom-left";
344

345
                state.rotatedPadding.top = $$.getResettedPadding(state.rotatedPadding.top);
6,915✔
346
                state.rotatedPadding.right = axis_rotated && !config.axis_x_show ? 0 : 30;
6,915✔
347

348
                state.inputType = convertInputType(
6,915✔
349
                        config.interaction_inputType_mouse,
350
                        config.interaction_inputType_touch
351
                );
352
        }
353

354
        initWithData(data): void {
355
                const $$ = <any>this;
6,876✔
356
                const {config, scale, state, $el, org} = $$;
6,876✔
357
                const {hasAxis, hasFunnel, hasTreemap} = state;
6,876✔
358
                const hasInteraction = config.interaction_enabled;
6,876✔
359
                const hasPolar = $$.hasType("polar");
6,876✔
360
                const labelsBGColor = config.data_labels_backgroundColors;
6,876✔
361

362
                // for arc type, set axes to not be shown
363
                // $$.hasArcType() && ["x", "y", "y2"].forEach(id => (config[`axis_${id}_show`] = false));
364

365
                if (hasAxis) {
6,876✔
366
                        $$.axis = $$.getAxisInstance();
5,853✔
367
                        config.zoom_enabled && $$.initZoom();
5,853✔
368
                }
369

370
                // Init data as targets
371
                $$.data.xs = {};
6,876✔
372
                $$.data.targets = $$.convertDataToTargets(data);
6,876✔
373

374
                if (config.data_filter) {
6,876!
375
                        $$.data.targets = $$.data.targets.filter(config.data_filter.bind($$.api));
×
376
                }
377

378
                // Set targets to hide if needed
379
                if (config.data_hide) {
6,876✔
380
                        $$.addHiddenTargetIds(
45✔
381
                                config.data_hide === true ? $$.mapToIds($$.data.targets) : config.data_hide
45✔
382
                        );
383
                }
384

385
                if (config.legend_hide) {
6,876✔
386
                        $$.addHiddenLegendIds(
9✔
387
                                config.legend_hide === true ? $$.mapToIds($$.data.targets) : config.legend_hide
9✔
388
                        );
389
                }
390

391
                // Init sizes and scales
392
                $$.updateSizes();
6,876✔
393
                $$.updateScales(true);
6,876✔
394

395
                // retrieve scale after the 'updateScales()' is called
396
                if (hasAxis) {
6,876✔
397
                        const {x, y, y2, subX, subY, subY2} = scale;
5,853✔
398

399
                        // Set domains for each scale
400
                        if (x) {
5,853!
401
                                x.domain(sortValue($$.getXDomain($$.data.targets), !config.axis_x_inverted));
5,853✔
402
                                subX.domain(x.domain());
5,853✔
403

404
                                // Save original x domain for zoom update
405
                                org.xDomain = x.domain();
5,853✔
406
                        }
407

408
                        if (y) {
5,853!
409
                                y.domain($$.getYDomain($$.data.targets, "y"));
5,853✔
410
                                subY.domain(y.domain());
5,853✔
411
                        }
412

413
                        if (y2) {
5,853✔
414
                                y2.domain($$.getYDomain($$.data.targets, "y2"));
846✔
415
                                subY2 && subY2.domain(y2.domain());
846✔
416
                        }
417
                }
418

419
                if (config.render_mode === "canvas") {
6,876✔
420
                        if (!$$.initCanvas) {
627!
NEW
421
                                throw Error(
×
422
                                        "[billboard.js] Please import and call canvas() to use render.mode='canvas'."
423
                                );
424
                        }
425

426
                        // Bind resize event before tooltip init because tooltip position registers resize hooks.
427
                        $$.bindResize();
627✔
428
                        $$.initCanvas();
627✔
429

430
                        config.tooltip_show && $$.initTooltip();
627✔
431

432
                        $$.callPluginHook("$init");
627✔
433

434
                        // oninit callback
435
                        callFn(config.oninit, $$.api);
627✔
436

437
                        $$.redraw({
627✔
438
                                withTransition: false,
439
                                withTransform: true,
440
                                withUpdateXDomain: true,
441
                                withUpdateOrgXDomain: true,
442
                                withTransitionForAxis: false,
443
                                initializing: true
444
                        });
445

446
                        // data.onmin/max callback
447
                        if (config.data_onmin || config.data_onmax) {
627!
NEW
448
                                const minMax = $$.getMinMaxData();
×
449

NEW
450
                                callFn(config.data_onmin, $$.api, minMax.min);
×
NEW
451
                                callFn(config.data_onmax, $$.api, minMax.max);
×
452
                        }
453

454
                        state.rendered = true;
627✔
455
                        return;
627✔
456
                }
457

458
                // -- Basic Elements --
459
                $el.svg = $el.chart.append("svg")
6,249✔
460
                        .style("overflow", "hidden")
461
                        .style("display", "block");
462

463
                if (hasInteraction && state.inputType) {
6,249✔
464
                        const isTouch = state.inputType === "touch";
6,234✔
465
                        const {onclick, onover, onout} = config;
6,234✔
466

467
                        $el.svg
6,234✔
468
                                .on("click", onclick?.bind($$.api) || null)
12,465✔
469
                                .on(isTouch ? "touchstart" : "mouseenter", onover?.bind($$.api) || null,
18,699✔
470
                                        isTouch ? {passive: true} : undefined)
6,234✔
471
                                .on(isTouch ? "touchend" : "mouseleave", onout?.bind($$.api) || null);
18,699✔
472
                }
473

474
                config.svg_classname && $el.svg.attr("class", config.svg_classname);
6,249✔
475

476
                // Define defs
477
                const hasColorPatterns = isFunction(config.color_tiles) && $$.patterns;
6,249✔
478

479
                if (
6,249✔
480
                        hasAxis || hasColorPatterns || hasPolar || hasTreemap ||
10,818✔
481
                        labelsBGColor || $$.hasLegendDefsPoint?.()
482
                ) {
483
                        $el.defs = $el.svg.append("defs");
5,442✔
484

485
                        if (hasAxis) {
5,442✔
486
                                ["id", "idXAxis", "idYAxis", "idGrid"].forEach(v => {
5,247✔
487
                                        $$.appendClip($el.defs, state.clip[v]);
20,988✔
488
                                });
489
                        }
490

491
                        // Append data background color filter definition
492
                        $$.generateTextBGColorFilter(labelsBGColor);
5,442✔
493

494
                        // set color patterns
495
                        if (hasColorPatterns) {
5,442✔
496
                                $$.patterns.forEach(p => $el.defs.append(() => p.node));
153✔
497
                        }
498
                }
499

500
                $$.updateSvgSize();
6,249✔
501

502
                // Bind resize event
503
                $$.bindResize();
6,249✔
504

505
                // Define regions
506
                const main = $el.svg.append("g")
6,249✔
507
                        .classed($COMMON.main, true)
508
                        .attr("transform", hasFunnel || hasTreemap ? null : $$.getTranslate("main"));
18,642✔
509

510
                $el.main = main;
6,249✔
511

512
                // initialize subchart when subchart show option is set
513
                config.subchart_show && $$.initSubchart();
6,249✔
514

515
                config.tooltip_show && $$.initTooltip();
6,249✔
516

517
                config.title_text && $$.initTitle();
6,249✔
518
                !hasTreemap && config.legend_show && $$.initLegend();
6,249✔
519

520
                // -- Main Region --
521

522
                // text when empty
523
                if (config.data_empty_label_text) {
6,249✔
524
                        main.append("text")
30✔
525
                                .attr("class", `${$TEXT.text} ${$COMMON.empty}`)
526
                                .attr("text-anchor", "middle") // horizontal centering of text at x position in all browsers.
527
                                .attr("dominant-baseline", "middle"); // vertical centering of text at y position in all browsers, except IE.
528
                }
529

530
                if (hasAxis) {
6,249✔
531
                        // Regions (optional module — initRegion installed by regions resolver)
532
                        config.regions.length && $$.initRegion?.();
5,247✔
533

534
                        // Add Axis here, when clipPath is 'false'
535
                        !config.clipPath && $$.axis.init();
5,247✔
536
                }
537

538
                // Define g for chart area
539
                main.append("g")
6,249✔
540
                        .classed($COMMON.chart, true)
541
                        .attr("clip-path", hasAxis ? state.clip.path : null);
6,249✔
542

543
                $$.callPluginHook("$init");
6,249✔
544

545
                $$.initChartElements();
6,249✔
546

547
                if (hasAxis) {
6,249✔
548
                        // Cover whole with rects for events
549
                        hasInteraction && $$.initEventRect?.();
5,247✔
550

551
                        // Grids (optional module — initGrid installed by grid resolver)
552
                        $$.initGrid?.();
5,247✔
553

554
                        // Add Axis here, when clipPath is 'true'
555
                        config.clipPath && $$.axis?.init();
5,247✔
556
                }
557

558
                // Set targets
559
                $$.updateTargets($$.data.targets);
6,249✔
560

561
                // Draw with targets
562
                $$.updateDimension();
6,249✔
563

564
                // oninit callback
565
                callFn(config.oninit, $$.api);
6,249✔
566

567
                // Set background
568
                $$.setBackground();
6,249✔
569

570
                $$.redraw({
6,249✔
571
                        withTransition: false,
572
                        withTransform: true,
573
                        withUpdateXDomain: true,
574
                        withUpdateOrgXDomain: true,
575
                        withTransitionForAxis: false,
576
                        initializing: true
577
                });
578

579
                // data.onmin/max callback
580
                if (config.data_onmin || config.data_onmax) {
6,249✔
581
                        const minMax = $$.getMinMaxData();
6✔
582

583
                        callFn(config.data_onmin, $$.api, minMax.min);
6✔
584
                        callFn(config.data_onmax, $$.api, minMax.max);
6✔
585
                }
586

587
                config.tooltip_show && $$.initShowTooltip();
6,249✔
588
                state.rendered = true;
6,249✔
589
        }
590

591
        /**
592
         * Initialize chart elements
593
         * @private
594
         */
595
        initChartElements(): void {
596
                const $$ = <any>this;
6,249✔
597
                const {hasAxis, hasRadar, hasTreemap} = $$.state;
6,249✔
598
                const types: string[] = [];
6,249✔
599

600
                if (hasAxis) {
6,249✔
601
                        const shapes = ["bar", "bubble", "candlestick", "line"];
5,247✔
602

603
                        if ($$.config.bar_front) {
5,247✔
604
                                shapes.push(shapes.shift() as string);
6✔
605
                        }
606

607
                        for (const shape of shapes) {
5,247✔
608
                                const name = capitalize(shape);
20,988✔
609
                                if ((shape === "line" && $$.hasTypeOf(name)) || $$.hasType(shape)) {
20,988✔
610
                                        types.push(name);
5,280✔
611
                                }
612
                        }
613
                } else if (hasTreemap) {
1,002✔
614
                        types.push("Treemap");
105✔
615
                } else if ($$.hasType("funnel")) {
897✔
616
                        types.push("Funnel");
105✔
617
                } else {
618
                        const hasPolar = $$.hasType("polar");
792✔
619
                        const hasGauge = $$.hasType("gauge");
792✔
620

621
                        if (!hasRadar) {
792✔
622
                                types.push("Arc", "Pie");
690✔
623
                        }
624

625
                        if (hasGauge) {
792✔
626
                                types.push("Gauge");
258✔
627
                        } else if (hasRadar) {
534✔
628
                                types.push("Radar");
102✔
629
                        } else if (hasPolar) {
432✔
630
                                types.push("Polar");
75✔
631
                        }
632
                }
633

634
                for (const type of types) {
6,249✔
635
                        $$[`init${type}`]();
7,305✔
636
                }
637

638
                if (notEmpty($$.config.data_labels) && !$$.hasArcType(null, ["radar"])) {
6,249✔
639
                        $$.initText();
768✔
640
                }
641
        }
642

643
        /**
644
         * Set chart elements
645
         * @private
646
         */
647
        setChartElements(): void {
648
                const $$ = this;
7,215✔
649
                const {
650
                        $el: {
651
                                chart,
652
                                svg,
653
                                defs,
654
                                main,
655
                                tooltip,
656
                                legend,
657
                                title,
658
                                canvas,
659
                                eventOverlay,
660
                                grid,
661
                                needle,
662
                                arcs: arc,
663
                                circle: circles,
664
                                bar: bars,
665
                                candlestick,
666
                                line: lines,
667
                                area: areas,
668
                                text: texts
669
                        }
670
                } = $$;
7,215✔
671

672
                // public
673
                $$.api.$ = {
7,215✔
674
                        chart,
675
                        svg,
676
                        canvas,
677
                        eventOverlay,
678
                        defs,
679
                        main,
680
                        tooltip,
681
                        legend,
682
                        title,
683
                        grid,
684
                        arc,
685
                        circles,
686
                        bar: {bars},
687
                        candlestick,
688
                        line: {lines, areas},
689
                        needle,
690
                        text: {texts}
691
                };
692
        }
693

694
        /**
695
         * Set background element/image
696
         * @private
697
         */
698
        setBackground(): void {
699
                const $$ = this;
6,249✔
700
                const {config: {background: bg}, state, $el: {svg}} = $$;
6,249✔
701

702
                if (notEmpty(bg)) {
6,249✔
703
                        const element = svg.select("g")
15✔
704
                                .insert(bg.imgUrl ? "image" : "rect", ":first-child");
15✔
705

706
                        if (bg.imgUrl) {
15✔
707
                                element.attr("href", bg.imgUrl);
6✔
708
                        } else if (bg.color) {
9!
709
                                element
9✔
710
                                        .style("fill", bg.color)
711
                                        .attr("clip-path", state.clip.path);
712
                        }
713

714
                        element
15✔
715
                                .attr("class", bg.class || null)
18✔
716
                                .attr("width", "100%")
717
                                .attr("height", "100%");
718
                }
719
        }
720

721
        /**
722
         * Update targeted element with given data
723
         * @param {object} targets Data object formatted as 'target'
724
         * @private
725
         */
726
        updateTargets(targets): void {
727
                const $$ = <any>this;
6,534✔
728
                const {hasAxis, hasFunnel, hasRadar, hasTreemap} = $$.state;
6,534✔
729
                const helper = type =>
6,534✔
730
                        $$[`updateTargetsFor${type}`](
6,426✔
731
                                targets.filter($$[`is${type}Type`].bind($$))
732
                        );
733

734
                // Text
735
                $$.updateTargetsForText(targets);
6,534✔
736

737
                if (hasAxis) {
6,534✔
738
                        const shapes = ["bar", "candlestick", "line"];
5,499✔
739
                        for (const shape of shapes) {
5,499✔
740
                                const name = capitalize(shape);
16,497✔
741
                                if ((shape === "line" && $$.hasTypeOf(name)) || $$.hasType(shape)) {
16,497✔
742
                                        helper(name);
5,391✔
743
                                }
744
                        }
745

746
                        // Sub Chart
747
                        $$.updateTargetsForSubchart?.(targets);
5,499✔
748

749
                        // Arc, Polar, Radar
750
                } else if ($$.hasArcType(targets)) {
1,035✔
751
                        let type = "Arc";
807✔
752

753
                        if (hasRadar) {
807✔
754
                                type = "Radar";
105✔
755
                        } else if ($$.hasType("polar")) {
702✔
756
                                type = "Polar";
75✔
757
                        }
758

759
                        helper(type);
807✔
760
                } else if (hasFunnel) {
228✔
761
                        helper("Funnel");
114✔
762
                } else if (hasTreemap) {
114!
763
                        helper("Treemap");
114✔
764
                }
765

766
                // Point types
767
                const hasPointType = $$.hasType("bubble") || $$.hasType("scatter");
6,534✔
768

769
                if (hasPointType) {
6,534✔
770
                        $$.updateTargetForCircle?.();
270✔
771
                }
772

773
                // Fade-in each chart
774
                $$.filterTargetsToShowAtInit(hasPointType);
6,534✔
775
        }
776

777
        /**
778
         * Display targeted elements at initialization
779
         * @param {boolean} hasPointType whether has point type(bubble, scatter) or not
780
         * @private
781
         */
782
        filterTargetsToShowAtInit(hasPointType: boolean = false): void {
×
783
                const $$ = <any>this;
6,534✔
784
                const {$el: {svg}, $T} = $$;
6,534✔
785
                let selector = `.${$COMMON.target}`;
6,534✔
786

787
                if (hasPointType) {
6,534✔
788
                        selector += `, .${$CIRCLE.chartCircles} > .${$CIRCLE.circles}`;
270✔
789
                }
790

791
                $T(svg.selectAll(selector)
6,534✔
792
                        .filter(d => $$.isTargetToShow(d.id))).style("opacity", null);
15,381✔
793
        }
794

795
        getWithOption(options) {
796
                const withOptions = {
8,439✔
797
                        Dimension: true,
798
                        EventRect: true,
799
                        Legend: false,
800
                        Subchart: true,
801
                        Transform: false,
802
                        Transition: true,
803
                        TrimXDomain: true,
804
                        UpdateXAxis: "UpdateXDomain",
805
                        UpdateXDomain: false,
806
                        UpdateOrgXDomain: false,
807
                        TransitionForExit: "Transition",
808
                        TransitionForAxis: "Transition",
809
                        Y: true
810
                };
811

812
                for (const [key, defVal] of Object.entries(withOptions)) {
8,439✔
813
                        const value = isString(defVal) ? withOptions[defVal] : defVal;
109,707✔
814
                        withOptions[key] = getOption(options, `with${key}`, value);
109,707✔
815
                }
816

817
                return withOptions;
8,439✔
818
        }
819

820
        initialOpacity(d): null | "0" {
821
                const $$ = <any>this;
22,059✔
822
                const {withoutFadeIn} = $$.state;
22,059✔
823

824
                return $$.getBaseValue(d) !== null && withoutFadeIn[d.id] ? null : "0";
22,059✔
825
        }
826

827
        bindResize(): void {
828
                const $$ = <any>this;
6,876✔
829
                const {$el, config, state} = $$;
6,876✔
830
                const resizeFunction = generateResize(config.resize_timer);
6,876✔
831
                const {resize_auto} = config;
6,876✔
832
                const list: (() => void)[] = [];
6,876✔
833

834
                list.push(() => callFn(config.onresize, $$.api));
6,876✔
835

836
                if (/^(true|parent)$/.test(resize_auto)) {
6,876✔
837
                        list.push(() => {
6,810✔
838
                                // Skip resize if dimensions haven't changed
839
                                const prevWidth = state.current.width;
90✔
840
                                const prevHeight = state.current.height;
90✔
841

842
                                $$.setContainerSize();
90✔
843

844
                                if (
90✔
845
                                        prevWidth === state.current.width &&
168✔
846
                                        prevHeight === state.current.height
847
                                ) {
848
                                        return;
78✔
849
                                }
850

851
                                state.resizing = true;
12✔
852

853
                                // https://github.com/naver/billboard.js/issues/2650
854
                                if (config.legend_show) {
12!
855
                                        $$.updateSizes();
12✔
856
                                        state.isCanvasMode ? $$.updateHtmlLegend?.() : $$.updateLegend();
12!
857
                                }
858

859
                                $$.api.flush(false);
12✔
860
                        });
861
                }
862

863
                list.push(() => {
6,876✔
864
                        callFn(config.onresized, $$.api);
93✔
865
                        state.resizing = false;
93✔
866
                });
867

868
                // add resize functions
869
                list.forEach(v => resizeFunction.add(v));
20,562✔
870
                $$.resizeFunction = resizeFunction;
6,876✔
871

872
                // attach resize event
873
                if (resize_auto === "parent") {
6,876✔
874
                        ($$.resizeFunction.resizeObserver = new ResizeObserver($$.resizeFunction.bind($$)))
3✔
875
                                .observe($el.chart.node().parentNode);
876
                } else {
877
                        window.addEventListener("resize", $$.resizeFunction);
6,873✔
878
                }
879
        }
880

881
        /**
882
         * Call plugin hook
883
         * @param {string} phase The lifecycle phase
884
         * @param {Array} args Arguments
885
         * @private
886
         */
887
        callPluginHook(phase, ...args): void {
888
                this.config.plugins.forEach(v => {
30,201✔
889
                        if (phase === "$beforeInit") {
678✔
890
                                v.$$ = this;
171✔
891
                                this.api.plugins.push(v);
171✔
892
                        }
893

894
                        v[phase](...args);
678✔
895
                });
896
        }
897
}
898

899
extend(ChartInternal.prototype, [
246✔
900
        // common
901
        dataConvert,
902
        data,
903
        dataLoad,
904
        category,
905
        classModule,
906
        color,
907
        domain,
908
        interaction,
909
        format,
910
        legend,
911
        redraw,
912
        scale,
913
        shape,
914
        size,
915
        style,
916
        text,
917
        title,
918
        tooltip,
919
        transform,
920
        typeInternals
921
]);
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