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

naver / billboard.js / 27269607796

10 Jun 2026 10:20AM UTC coverage: 93.875% (+0.08%) from 93.792%
27269607796

push

github

web-flow
refactor(all): fix potential bugs & improve perf (#4139)

* refactor(all): fix potential bugs & improve perf

* fix(canvas): support public API parity

- Handle focus, defocus and revert without SVG targets in canvas mode.
- Keep legend focus state and canvas frames in sync for those APIs.
- Route tooltip.show and tooltip.hide through canvas focus rendering.
- Clear canvas focus state when programmatic tooltip APIs hide the tooltip.
- Use canvas subchart domain helpers for zoom and unzoom instead of SVG brush access.
- Add API canvas tests under test/api for core public APIs and canvas-only behavior.

* skip: fix build type error

10968 of 12185 branches covered (90.01%)

Branch coverage included in aggregate %.

314 of 325 new or added lines in 44 files covered. (96.62%)

3 existing lines in 3 files now uncovered.

13815 of 14215 relevant lines covered (97.19%)

27085.9 hits per line

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

92.31
/src/Chart/api/load.ts
1
/**
2
 * Copyright (c) 2017 ~ present NAVER Corp.
3
 * billboard.js project is licensed under the MIT license
4
 */
5
import {callDone} from "../../ChartInternal/data/load";
6
import {requestIdleCallback} from "../../module/browser";
7
import {isArray, isEmpty, isString} from "../../module/util";
8

9
export default {
10
        /**
11
         * Load data to the chart.<br><br>
12
         * You can specify multiple targets by giving an array that includes id as String. If no argument is given, all of targets will be toggles.
13
         * - <b>Note:</b>
14
         *   - unload should be used if some data needs to be unloaded simultaneously.
15
         *     If you call unload API soon after/before load instead of unload param, chart will not be rendered properly because of cancel of animation.<br>
16
         *   - done will be called after data loaded, but it's not after rendering.
17
         *     It's because rendering will finish after some transition and there is some time lag between loading and rendering
18
         * @function load
19
         * @instance
20
         * @memberof Chart
21
         * @param {object} args The object can consist with following members:<br>
22
         *
23
         *    | Key | Type | Description |
24
         *    | --- | --- | --- |
25
         *    | columns | Array | The `columns` data will be loaded. If data that has the same target id is given, the chart will be updated. Otherwise, new target will be added |
26
         *    | json | Array | The `json` data will be loaded. If data that has the same target id is given, the chart will be updated. Otherwise, new target will be added |
27
         *    | rows | Array | The `rows` data will be loaded. If data that has the same target id is given, the chart will be updated. Otherwise, new target will be added |
28
         *    | url | string | The data from `url` will be loaded. If data that has the same target id is given, the chart will be updated. Otherwise, new target will be added |
29
         *    | &nbsp; | | |
30
         *    | append | boolean | Load data appending it to the current dataseries.<br>If the existing chart has`x` value, should provide with corresponding `x` value for newly loaded data.  |
31
         *    | axes | Object | The axes specified by data.axes will be updated. axes must be Object that has target id as keys. |
32
         *    | categories | Array | The categories specified by axis.x.categories or data.x will be updated. categories must be Array. |
33
         *    | classes | Object | The classes specified by data.classes will be updated. classes must be Object that has target id as keys. |
34
         *    | colors | Object | The colors specified by data.colors will be updated. colors must be Object that has target id as keys. |
35
         *    | data | Object | Data objects to be loaded. Checkout the example. |
36
         *    | done | Function | The specified function will be called after data loaded.|
37
         *    | headers | string |  Set request header if loading via `data.url`.<br>@see [data․headers](Options.html#.data%25E2%2580%25A4headers) |
38
         *    | keys | Object |  Choose which JSON objects keys correspond to desired data.<br>**NOTE:** Only for JSON object given as array.<br>@see [data․keys](Options.html#.data%25E2%2580%25A4keys) |
39
         *    | mimeType | string |  Set 'json' if loading JSON via url.<br>@see [data․mimeType](Options.html#.data%25E2%2580%25A4mimeType) |
40
         *    | names | Object | Same as data.names() |
41
         *    | regions | Object | The regions specified by data.regions will be updated. regions must be Object that has target id as keys. |
42
         *    | resizeAfter | boolean | Resize after the load. Default value is `false`.<br>- This option won't call `onresize` neither `onresized`.<br>- When set to 'true', will call `.flush(true)` at the end of load. |
43
         *    | type | string | The type of targets will be updated. |
44
         *    | types | Object | The types of targets will be updated. |
45
         *    | unload | Array | Specify the data will be unloaded before loading new data. If true given, all of data will be unloaded. If target ids given as String or Array, specified targets will be unloaded. If absent or false given, unload will not occur. |
46
         *    | xs | string | Same as data.xs option  |
47
         * @see [Demo](https://naver.github.io/billboard.js/demo/#Data.DataFromURL)
48
         * @example
49
         * // Load data1 and unload data2 and data3
50
         * chart.load({
51
         *     columns: [
52
         *        ["data1", 100, 200, 150, ...],
53
         *        ...
54
         *    ],
55
         *    unload: ["data2", "data3"],
56
         *    url: "...",
57
         *    done: function() { ... }
58
         *    resizeAfter: true  // will resize after load
59
         * });
60
         * @example
61
         * const chart = bb.generate({
62
         *   data: {
63
         *     columns: [
64
         *       ["data1", 20, 30, 40]
65
         *     ]
66
         *   }
67
         * });
68
         *
69
         * chart.load({
70
         *    columns: [
71
         *        // with 'append' option, the 'data1' will have `[20,30,40,50,60]`.
72
         *        ["data1", 50, 60]
73
         *    ],
74
         *    append: true
75
         * });
76
         * @example
77
         * const chart = bb.generate({
78
         *   data: {
79
         *     x: "x",
80
         *     xFormat: "%Y-%m-%dT%H:%M:%S",
81
         *     columns: [
82
         *       ["x", "2021-01-03T03:00:00", "2021-01-04T12:00:00", "2021-01-05T21:00:00"],
83
         *       ["data1", 36, 30, 24]
84
         *     ]
85
         *   },
86
         *   axis: {
87
         *     x: {
88
         *       type: "timeseries"
89
         *     }
90
         *   }
91
         * };
92
         *
93
         * chart.load({
94
         *   columns: [
95
         *     // when existing chart has `x` value, should provide corresponding 'x' value.
96
         *     // with 'append' option, the 'data1' will have `[36,30,24,37]`.
97
         *     ["x", "2021-02-01T08:00:00"],
98
         *     ["data1", 37]
99
         *   ],
100
         *   append: true
101
         * });
102
         * @example
103
         * // myAPI.json
104
         * // {
105
         * //   "data1": [220, 240, 270, 250, 280],
106
         * //   "data2": [180, 150, 300, 70, 120]
107
         * // }
108
         *
109
         * chart.load({
110
         *     url: './data/myAPI.json',
111
         *     mimeType: "json",
112
         *
113
         *     // set request header if is needed
114
         *     headers: {
115
         *       "Content-Type": "text/json"
116
         *     }
117
         * });
118
         * @example
119
         * chart.load({
120
         *     data: [
121
         *       // equivalent as: columns: [["data1", 30, 200, 100]]
122
         *       {"data1": 30}, {"data1": 200}, {"data1": 100}
123
         *
124
         *       // or
125
         *       // equivalent as: columns: [["data1", 10, 20], ["data2", 13, 30]]
126
         *       // {"data1": 10, "data2": 13}, {"data1": 20, "data2": 30}}
127
         *     ]
128
         * });
129
         * @example
130
         * chart.load({
131
         *     json: [
132
         *          {name: "www.site1.com", upload: 800, download: 500, total: 400},
133
         *     ],
134
         *     keys: {
135
         *         x: "name",
136
         *         value: ["upload", "download"]
137
         *     }
138
         * });
139
         * @example
140
         * chart.load({
141
         *   json: {
142
         *       data1:[30, 20, 50, 40, 60, 50],
143
         *       data2:[200, 130, 90, 240, 130, 220],
144
         *   }
145
         * });
146
         */
147
        load(args): void {
148
                const $$ = this.internal;
258✔
149
                const {config} = $$;
258✔
150

151
                $$.state.dirty.data = true;
258✔
152
                $$.state._eventRectFingerprint = null;
258✔
153

154
                // update xs if specified
155
                args.xs && $$.addXs(args.xs);
258✔
156

157
                // update names if exists
158
                "names" in args && this.data.names(args.names);
258!
159

160
                // update classes if exists
161
                "classes" in args && Object.keys(args.classes).forEach(id => {
258✔
162
                        config.data_classes[id] = args.classes[id];
3✔
163
                });
164

165
                // update categories if exists
166
                if ("categories" in args && $$.axis.isCategorized()) {
258✔
167
                        config.axis_x_categories = args.categories;
6✔
168
                }
169

170
                // update axes if exists
171
                "axes" in args && Object.keys(args.axes).forEach(id => {
258✔
172
                        config.data_axes[id] = args.axes[id];
3✔
173
                });
174

175
                // update colors if exists
176
                "colors" in args && Object.keys(args.colors).forEach(id => {
258✔
177
                        config.data_colors[id] = args.colors[id];
3✔
178
                });
179

180
                // update regions if exists
181
                "regions" in args && (config.data_regions = args.regions || {});
258!
182

183
                const hasDataToLoad = ["data", "columns", "rows", "json", "url"]
258✔
184
                        .some(key => key in args);
543✔
185
                const hasUnload = "unload" in args && args.unload !== false;
258✔
186

187
                if ("regions" in args && !hasDataToLoad && !hasUnload) {
258✔
188
                        $$.redraw({
3✔
189
                                withUpdateOrgXDomain: true,
190
                                withUpdateXDomain: true,
191
                                withLegend: true
192
                        });
193
                        callDone.call($$, args.done, args.resizeAfter);
3✔
194
                        return;
3✔
195
                }
196

197
                // unload if needed
198
                if (hasUnload) {
255✔
199
                        // TODO: do not unload if target will load (included in url/rows/columns)
200
                        const unloadIds = $$.mapToTargetIds(args.unload === true ? null : args.unload);
39✔
201

202
                        $$.unload(unloadIds, () => {
39✔
203
                                if (!$$.config || !$$.cache) {
39✔
204
                                        return;
3✔
205
                                }
206

207
                                $$.cache.remove(unloadIds);
36✔
208

209
                                // to mitigate improper rendering for multiple consecutive calls
210
                                // https://github.com/naver/billboard.js/issues/2121
211
                                requestIdleCallback(() => $$.loadFromArgs(args));
36✔
212
                        });
213
                } else {
214
                        $$.loadFromArgs(args);
216✔
215
                }
216
        },
217

218
        /**
219
         * Unload data to the chart.<br><br>
220
         * You can specify multiple targets by giving an array that includes id as String. If no argument is given, all of targets will be toggles.
221
         * - <b>Note:</b>
222
         * If you call load API soon after/before unload, unload param of load should be used. Otherwise chart will not be rendered properly because of cancel of animation.<br>
223
         * `done` will be called after data loaded, but it's not after rendering. It's because rendering will finish after some transition and there is some time lag between loading and rendering.
224
         * @function unload
225
         * @instance
226
         * @memberof Chart
227
         * @param {object} argsValue
228
         *  | key | Type | Description |
229
         *  | --- | --- | --- |
230
         *  | ids | String &vert; Array | Target id data to be unloaded. If not given, all data will be unloaded. |
231
         *  | done | Function | Callback after data is unloaded. |
232
         *  | resizeAfter | boolean | Resize after the unload. Default value is `false`.<br>- This option won't call `onresize` neither `onresized`.<br>- When set to 'true', will call `.flush(true)` at the end of unload. |
233
         * @example
234
         *  // Unload data2 and data3
235
         *  chart.unload({
236
         *    ids: ["data2", "data3"],
237
         *    done: function() {
238
         *       // called after the unloaded
239
         *    },
240
         *    resizeAfter: true  // will resize after unload
241
         *  });
242
         */
243
        unload(argsValue): void {
244
                const $$ = this.internal;
36✔
245
                let args = argsValue || {};
36!
246

247
                // hide possible tooltip display when data is completely unloaded
248
                isEmpty(args) && this.tooltip.hide();
36✔
249

250
                if (isArray(args)) {
36✔
251
                        args = {ids: args};
3✔
252
                } else if (isString(args)) {
33!
253
                        args = {ids: [args]};
×
254
                }
255

256
                const ids = $$.mapToTargetIds(args.ids);
36✔
257

258
                $$.state.dirty.data = true;
36✔
259
                $$.state._eventRectFingerprint = null;
36✔
260

261
                $$.unload(ids, () => {
36✔
262
                        if (!$$.config || !$$.cache) {
36!
NEW
263
                                return;
×
264
                        }
265

266
                        $$.redraw({
36✔
267
                                withUpdateOrgXDomain: true,
268
                                withUpdateXDomain: true,
269
                                withLegend: true
270
                        });
271

272
                        $$.cache.remove(ids);
36✔
273
                        callDone.call($$, args.done, args.resizeAfter);
36✔
274
                });
275
        }
276
};
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