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

naver / billboard.js / 15919719400

27 Jun 2025 06:24AM UTC coverage: 86.941% (-0.1%) from 87.071%
15919719400

push

github

netil
Merge branch 'master' into latest

5668 of 7016 branches covered (80.79%)

Branch coverage included in aggregate %.

158 of 188 new or added lines in 24 files covered. (84.04%)

1 existing line in 1 file now uncovered.

7487 of 8115 relevant lines covered (92.26%)

11762.65 hits per line

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

95.26
/src/ChartInternal/data/convert.ts
1
/**
2
 * Copyright (c) 2017 ~ present NAVER Corp.
3
 * billboard.js project is licensed under the MIT license
4
 */
5
import {isArray, isDefined, isObject, isUndefined, isValue, notEmpty} from "../../module/util";
6
import {runWorker} from "../../module/worker";
7
import type {IData} from "../data/IData";
8
import {columns, json, rows, url} from "./convert.helper";
9

10
/**
11
 * Get data key for JSON
12
 * @param {string|object} keysParam Key params
13
 * @param {object} config Config object
14
 * @returns {string} Data key
15
 * @private
16
 */
17
function getDataKeyForJson(keysParam, config) {
18
        const keys = keysParam || config?.data_keys;
36✔
19

20
        if (keys?.x) {
36✔
21
                config.data_x = keys.x;
15✔
22
        }
23

24
        return keys;
36✔
25
}
26

27
/**
28
 * Set `xs` for each id
29
 * @param {string[]} ids Ids to set xs
30
 * @param {object[]} data Data to set xs from
31
 * @param {object} params Parameters for setting xs
32
 * @param {boolean} params.appendXs Whether to append xs
33
 * @param {string[]} params.xs X keys to set xs from
34
 * @param {boolean} params.categorized Whether the axis is categorized
35
 * @param {boolean} params.timeSeries Whether the axis is time series
36
 * @param {boolean} params.customX Whether the x is custom
37
 * @private
38
 */
39
function setXS(
40
        ids: string[],
41
        data: {[key: string]: number | null}[],
42
        params: {appendXs, xs, categorized: boolean, timeSeries: boolean, customX: boolean}
43
): void {
44
        const $$ = this;
3,036✔
45
        const {config} = $$;
3,036✔
46
        let xsData;
47

48
        ids.forEach(id => {
3,036✔
49
                const xKey = $$.getXKey(id);
6,714✔
50

51
                if (params.customX || params.timeSeries) {
6,714✔
52
                        // if included in input data
53
                        if (params.xs.indexOf(xKey) >= 0) {
1,353✔
54
                                xsData = ((params.appendXs && $$.data.xs[id]) || [])
1,347✔
55
                                        .concat(
56
                                                data.map((d, i) => {
57
                                                        const rawX = isValue(d[xKey]);
7,134✔
58
                                                        return rawX ? $$.generateTargetX(rawX, id, i) : false;
7,134✔
59
                                                }).filter(v => v !== false)
7,134✔
60
                                        );
61
                        } else if (config.data_x) {
6✔
62
                                // if not included in input data, find from preloaded data of other id's x
63
                                xsData = this.getOtherTargetXs();
3✔
64
                        } else if (notEmpty(config.data_xs)) {
3!
65
                                // if not included in input data, find from preloaded data
66
                                xsData = $$.getXValuesOfXKey(xKey, $$.data.targets);
3✔
67
                        }
68
                        // MEMO: if no x included, use same x of current will be used
69
                } else {
70
                        xsData = data.map((d, i) => i);
19,020✔
71
                }
72

73
                if (xsData) {
6,714!
74
                        $$.data.xs[id] = xsData;
6,714✔
75
                } else {
NEW
76
                        throw new Error(`x is not defined for id = "${id}".`);
×
77
                }
78
        });
79
}
80

81
/**
82
 * Data convert
83
 * @memberof ChartInternal
84
 * @private
85
 */
86
export default {
87
        /**
88
         * Convert data according its type
89
         * @param {object} args data object
90
         * @param {Function} [callback] callback for url(XHR) type loading
91
         * @private
92
         */
93
        convertData(args, callback: Function): void {
94
                const {config} = this;
3,048✔
95
                const useWorker = config.boost_useWorker;
3,048✔
96
                let data = args;
3,048✔
97

98
                if (args.bindto) {
3,048✔
99
                        data = {};
2,835✔
100

101
                        ["url", "mimeType", "headers", "keys", "json", "keys", "rows", "columns"]
2,835✔
102
                                .forEach(v => {
103
                                        const key = `data_${v}`;
22,680✔
104

105
                                        if (key in args) {
22,680!
106
                                                data[v] = args[key];
22,680✔
107
                                        }
108
                                });
109
                }
110

111
                if (data.url && callback) {
3,048✔
112
                        url(data.url, data.mimeType, data.headers, getDataKeyForJson(data.keys, config),
3✔
113
                                callback);
114
                } else if (data.json) {
3,045✔
115
                        runWorker(useWorker, json, callback, [columns, rows])(
33✔
116
                                data.json,
117
                                getDataKeyForJson(data.keys, config)
118
                        );
119
                } else if (data.rows) {
3,012✔
120
                        runWorker(useWorker, rows, callback)(data.rows);
15✔
121
                } else if (data.columns) {
2,997✔
122
                        runWorker(useWorker, columns, callback)(data.columns);
2,994✔
123
                } else if (args.bindto) {
3!
124
                        throw Error("url or json or rows or columns is required.");
3✔
125
                }
126
        },
127

128
        /**
129
         * Convert data to targets
130
         * @param {object[]} data Data to convert
131
         * @param {boolean} appendXs Whether to append xs
132
         * @returns {IData[]} Converted targets
133
         * @private
134
         */
135
        convertDataToTargets(data: {[key: string]: number | null}[], appendXs: boolean): IData[] {
136
                const $$ = this;
3,036✔
137
                const {axis, config, state} = $$;
3,036✔
138
                const chartType = config.data_type;
3,036✔
139
                const dataKeys = Object.keys(data[0] || {});
3,036✔
140

141
                // Extract ids and xs from data keys to handle x and non-x values
142
                const {ids, xs} = dataKeys.length ?
3,036✔
143
                        dataKeys.reduce((acc, key) => {
144
                                if ($$.isX.call($$, key)) {
7,476✔
145
                                        acc.xs.push(key);
762✔
146
                                } else if ($$.isNotX.call($$, key)) {
6,714!
147
                                        acc.ids.push(key);
6,714✔
148
                                }
149

150
                                return acc;
7,476✔
151
                        }, {ids: [] as string[], xs: [] as string[]}) :
152
                        {ids: [], xs: []};
153

154
                const params = {
3,036✔
155
                        appendXs,
156
                        xs,
157
                        idConverter: config.data_idConverter.bind($$.api),
158
                        categorized: axis?.isCategorized(),
159
                        timeSeries: axis?.isTimeSeries(),
160
                        customX: axis?.isCustomX()
161
                };
162

163
                // save x for update data by load when custom x and bb.x API
164
                setXS.bind($$)(ids, data, params);
3,036✔
165

166
                // convert to target
167
                const targets = ids.map((id, index) => {
3,036✔
168
                        const convertedId = config.data_idConverter.bind($$.api)(id);
6,714✔
169
                        const xKey = $$.getXKey(id);
6,714✔
170
                        const isCategory = params.customX && params.categorized;
6,714✔
171
                        const hasCategory = isCategory && (() => {
6,714✔
172
                                const categorySet = new Set(config.axis_x_categories);
207✔
173
                                return data.every(v => categorySet.has(v.x));
444✔
174
                        })();
175

176
                        // when .load() with 'append' option is used for indexed axis
177
                        // @ts-ignore
178
                        const isDataAppend = data.__append__;
6,714✔
179
                        const xIndex = xKey === null && isDataAppend ? $$.api.data.values(id).length : 0;
6,714✔
180

181
                        return {
6,714✔
182
                                id: convertedId,
183
                                id_org: id,
184
                                values: data.map((d, i) => {
185
                                        const rawX = d[xKey];
26,187✔
186
                                        let value = d[id];
26,187✔
187
                                        let x;
188

189
                                        value = value !== null && !isNaN(value) && !isObject(value) ?
26,187✔
190
                                                +value :
191
                                                (isArray(value) || isObject(value) ? value : null);
1,212✔
192

193
                                        // use x as categories if custom x and categorized
194
                                        if ((isCategory || state.hasRadar) && index === 0 && !isUndefined(rawX)) {
26,187✔
195
                                                if (!hasCategory && index === 0 && i === 0 && !isDataAppend) {
663✔
196
                                                        config.axis_x_categories = [];
180✔
197
                                                }
198

199
                                                x = config.axis_x_categories.indexOf(rawX);
663✔
200

201
                                                if (x === -1) {
663✔
202
                                                        x = config.axis_x_categories.length;
654✔
203
                                                        config.axis_x_categories.push(rawX);
654✔
204
                                                }
205
                                        } else {
206
                                                x = $$.generateTargetX(rawX, id, xIndex + i);
25,524✔
207
                                        }
208

209
                                        // mark as x = undefined if value is undefined and filter to remove after mapped
210
                                        if (isUndefined(value) || $$.data.xs[id].length <= i) {
26,187✔
211
                                                x = undefined;
93✔
212
                                        }
213

214
                                        return {
26,187✔
215
                                                x,
216
                                                value,
217
                                                id: convertedId,
218
                                                index: -1
219
                                        };
220
                                }).filter(v => isDefined(v.x))
26,187✔
221
                        };
222
                });
223

224
                // finish targets
225
                targets.forEach(t => {
3,036✔
226
                        // sort values by its x
227
                        if (config.data_xSort) {
6,714✔
228
                                t.values = t.values.sort((v1, v2) => {
6,624✔
229
                                        const x1 = v1.x || v1.x === 0 ? v1.x : Infinity;
19,242!
230
                                        const x2 = v2.x || v2.x === 0 ? v2.x : Infinity;
19,242!
231

232
                                        return x1 - x2;
19,242✔
233
                                });
234
                        }
235

236
                        // indexing each value
237
                        t.values.forEach((v, i) => (v.index = i));
26,094✔
238

239
                        // this needs to be sorted because its index and value.index is identical
240
                        $$.data.xs[t.id]?.sort((v1, v2) => v1 - v2);
19,467✔
241
                });
242

243
                // cache information about values
244
                state.hasNegativeValue = $$.hasNegativeValueInTargets(targets);
3,036✔
245
                state.hasPositiveValue = $$.hasPositiveValueInTargets(targets);
3,036✔
246

247
                // set target types
248
                if (chartType && $$.isValidChartType(chartType)) {
3,036✔
249
                        const targetIds = $$.mapToIds(targets)
1,896✔
250
                                .filter(id =>
251
                                        !(id in config.data_types) || !$$.isValidChartType(config.data_types[id])
4,470✔
252
                                );
253

254
                        $$.setTargetType(targetIds, chartType);
1,896✔
255
                }
256

257
                // cache as original id keyed
258
                targets.forEach(d => $$.cache.add(d.id_org, d, true));
6,714✔
259

260
                return targets as IData[];
3,036✔
261
        }
262
};
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