• 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

85.71
/src/Chart/api/tooltip.ts
1
/**
2
 * Copyright (c) 2017 ~ present NAVER Corp.
3
 * billboard.js project is licensed under the MIT license
4
 */
5
import {$SHAPE} from "../../config/classes";
6
import {isDefined} from "../../module/util";
7

8
/**
9
 * Define tooltip
10
 * @ignore
11
 */
12
const tooltip = {
249✔
13
        /**
14
         * Show tooltip
15
         * @function tooltip․show
16
         * @instance
17
         * @memberof Chart
18
         * @param {object} args The object can consist with following members:<br>
19
         *
20
         *    | Key | Type | Description |
21
         *    | --- | --- | --- |
22
         *    | index | Number | Determine focus by index |
23
         *    | x | Number &vert; Date | Determine focus by x Axis index |
24
         *    | mouse | Array | Determine x and y coordinate value relative the targeted '.bb-event-rect' x Axis.<br>It should be used along with `data`, `index` or `x` value. The default value is set as `[0,0]` |
25
         *    | data | Object | When [data.xs](Options.html#.data%25E2%2580%25A4xs) option is used or [tooltip.grouped](Options.html#.tooltip) set to 'false', `should be used giving this param`.<br><br>**Key:**<br>- x {number &verbar; Date}: x Axis value<br>- index {number}: x Axis index (useless for data.xs)<br>- id {string}: data id<br>- value {number}: The corresponding value for tooltip. |
26
         *
27
         * @example
28
         *  // show the 2nd x Axis coordinate tooltip
29
         *  // for Arc(gauge, donut & pie) and radar type, approach showing tooltip by using "index" number.
30
         *  chart.tooltip.show({
31
         *    index: 1
32
         *  });
33
         *
34
         *  // show tooltip for the 3rd x Axis in x:50 and y:100 coordinate of '.bb-event-rect' of the x Axis.
35
         *  chart.tooltip.show({
36
         *    x: 2,
37
         *    mouse: [50, 100]
38
         *  });
39
         *
40
         *  // show tooltip for timeseries x axis
41
         *  chart.tooltip.show({
42
         *    x: new Date("2018-01-02 00:00")
43
         *  });
44
         *
45
         *  // treemap type can be shown by using "id" only.
46
         *  chart.tooltip.show({
47
         *    data: {
48
         *        id: "data1"  // data id
49
         *    }
50
         *  });
51
         *
52
         *  // for Arc types, specify 'id' or 'index'
53
         *  chart.tooltip.show({ data: { id: "data2" }});
54
         *  chart.tooltip.show({ data: { index: 2 }});
55
         *
56
         *  // when data.xs is used
57
         *  chart.tooltip.show({
58
         *    data: {
59
         *        x: 3,  // x Axis value
60
         *        id: "data1",  // data id
61
         *        value: 500  // data value
62
         *    }
63
         *  });
64
         *
65
         *  // when data.xs isn't used, but tooltip.grouped=false is set
66
         *  chart.tooltip.show({
67
         *    data: {
68
         *        index: 3,  // or 'x' key value
69
         *        id: "data1",  // data id
70
         *        value: 500  // data value
71
         *    }
72
         *  });
73
         */
74
        show: function(args): void {
75
                const $$ = this.internal;
480✔
76
                const {$el, config, state: {eventReceiver, hasFunnel, hasTreemap, inputType}} = $$;
480✔
77
                let index;
78
                let mouse;
79

80
                // determine mouse position on the chart
81
                if (args.mouse) {
480✔
82
                        mouse = args.mouse;
21✔
83
                }
84

85
                // determine focus data
86
                if (args.data) {
480✔
87
                        const {data} = args;
102✔
88
                        const y = $$.getYScaleById(data.id)?.(data.value);
102✔
89

90
                        if ((hasFunnel || hasTreemap) && data.id) {
102✔
91
                                const selector = $$.selectorTarget(data.id, undefined, `.${$SHAPE.shape}`);
18✔
92

93
                                eventReceiver.rect = $el.main.select(selector);
18✔
94
                        } else if ($$.isMultipleX()) {
84✔
95
                                // if multiple xs, target point will be determined by mouse
96
                                mouse = [$$.xx(data), y];
24✔
97
                        } else {
98
                                if (!config.tooltip_grouped) {
60✔
99
                                        mouse = [0, y];
33✔
100
                                }
101

102
                                index = data.index ?? (
60✔
103
                                        $$.hasArcType() && data.id ?
75✔
104
                                                $$.getArcElementByIdOrIndex(data.id)?.datum().index :
105
                                                $$.getIndexByX(data.x)
106
                                );
107
                        }
108
                } else if (isDefined(args.x)) {
378✔
109
                        index = $$.getIndexByX(args.x);
252✔
110
                } else if (isDefined(args.index)) {
126✔
111
                        index = args.index;
123✔
112
                }
113

114
                if ($$.state.isCanvasMode) {
480✔
115
                        const targets = $$.filterTargetsToShow?.() || $$.data.targets;
3!
116
                        const selectedData = args.data?.id && !config.tooltip_grouped ?
3!
117
                                targets
NEW
118
                                        .filter(target => target.id === args.data.id)
×
NEW
119
                                        .map(target => target.values[index ?? args.data.index])
×
120
                                        .filter(Boolean) :
121
                                targets
122
                                        .map(target => target.values[index])
6✔
123
                                        .filter(Boolean);
124
                        const canvas = $el.canvas?.node?.();
3✔
125
                        const shape = $$.state.canvasShape || $$.getDrawShape?.();
3!
126
                        const first = selectedData[0];
3✔
127
                        const point = mouse || (
3!
128
                                first && shape?.pos?.cx && shape?.pos?.cy ?
×
129
                                        [
130
                                                $$.state.margin.left + shape.pos.cx(first),
131
                                                $$.state.margin.top + shape.pos.cy(first)
132
                                        ] :
133
                                        undefined
134
                        );
135

136
                        if (!selectedData.length || !canvas) {
3!
NEW
137
                                return;
×
138
                        }
139

140
                        $$.state.canvasFocusKey = selectedData
3✔
141
                                .map(v => `${v.id}:${v.index}`)
6✔
142
                                .join("|");
143
                        $$.renderCanvasFocus?.(selectedData, point);
3✔
144
                        $$.showTooltip?.(selectedData, canvas);
3✔
145
                        return;
3✔
146
                }
147

148
                (inputType === "mouse" ? ["mouseover", "mousemove"] : ["touchstart"]).forEach(eventName => {
477✔
149
                        $$.dispatchEvent(eventName, index, mouse);
927✔
150
                });
151
        },
152

153
        /**
154
         * Hide tooltip
155
         * @function tooltip․hide
156
         * @instance
157
         * @memberof Chart
158
         */
159
        hide: function(): void {
160
                const $$ = this.internal;
300✔
161
                const {state: {inputType, isCanvasMode}, $el: {tooltip}} = $$;
300✔
162
                const data = tooltip?.datum();
300✔
163

164
                if (isCanvasMode) {
300✔
165
                        $$.state.canvasFocusKey = null;
30✔
166
                        $$.hideTooltip(true);
30✔
167
                        $$.clearCanvasFocus?.();
30✔
168
                        return;
30✔
169
                }
170

171
                if (data?.data?.[0]) {
270✔
172
                        const {index} = data.data[0];
42✔
173

174
                        // make to finalize, possible pending event flow set from '.tooltip.show()' call
175
                        (inputType === "mouse" ? ["mouseout"] : ["touchend"]).forEach(eventName => {
42✔
176
                                $$.dispatchEvent(eventName, index);
42✔
177
                        });
178
                }
179

180
                // reset last touch point index
181
                inputType === "touch" && $$.callOverOutForTouch();
270✔
182

183
                $$.hideTooltip(true);
270✔
184
                $$.hideGridFocus?.();
270✔
185

186
                $$.unexpandCircles?.();
270✔
187
                $$.expandBarTypeShapes?.(false);
270✔
188
        }
189
};
190

191
export default {tooltip};
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