@@ -79,25 +79,28 @@ function renderChart({
79
79
.append (' g' )
80
80
.attr (' transform' , ` translate(${margin .left },${margin .top }) ` )
81
81
82
- // X scale
83
- const x = d3
84
- .scaleBand ()
82
+ // Create a padded scale for the bars (with padding for visual spacing)
83
+ const paddedScale = d3
84
+ .scaleBand < string > ()
85
85
.domain (props .data .map ((d ) => d .key ))
86
86
.range (vertical ? [0 , width ] : [0 , height ])
87
87
.padding (0.4 )
88
88
89
- // Y scale
89
+ // Y scale for bar values
90
90
const y = d3
91
91
.scaleLinear ()
92
92
.domain ([0 , d3 .max (props .data , (d ) => d .value )! ])
93
93
.nice ()
94
94
.range (vertical ? [height , 0 ] : [0 , width ])
95
95
96
- // Add X axis
96
+ // Compute a constant offset to center the colored bar inside its full band.
97
+ const groupOffset = (paddedScale .step () - paddedScale .bandwidth ()) / 2
98
+
99
+ // For the axes, use the paddedScale so ticks remain centered on the bars.
97
100
svg
98
101
.append (' g' )
99
102
.attr (' transform' , ` translate(0,${height }) ` )
100
- .call (vertical ? d3 .axisBottom (x ) : d3 .axisBottom (y ).ticks (props .ticks ))
103
+ .call (vertical ? d3 .axisBottom (paddedScale ) : d3 .axisBottom (y ).ticks (props .ticks ))
101
104
.selectAll (' text' )
102
105
.attr (' fill' , c .text2 )
103
106
.style (' font-size' , ' 14px' )
@@ -111,7 +114,7 @@ function renderChart({
111
114
// Add Y axis
112
115
svg
113
116
.append (' g' )
114
- .call (vertical ? d3 .axisLeft (y ).ticks (props .ticks ) : d3 .axisLeft (x ))
117
+ .call (vertical ? d3 .axisLeft (y ).ticks (props .ticks ) : d3 .axisLeft (paddedScale ))
115
118
.selectAll (' text' )
116
119
.attr (' fill' , c .text2 )
117
120
.style (' font-size' , ' 14px' )
@@ -123,7 +126,7 @@ function renderChart({
123
126
124
127
// Add horizontal grid lines
125
128
const gridLines = svg
126
- .selectAll (' line.grid ' )
129
+ .selectAll ()
127
130
.data (y .ticks (props .ticks ))
128
131
.enter ()
129
132
.append (' line' )
@@ -168,10 +171,30 @@ function renderChart({
168
171
}
169
172
170
173
// Add bars
171
- const bars = svg
172
- .selectAll (' rect ' )
174
+ const barGroups = svg
175
+ .selectAll ()
173
176
.data (props .data )
174
177
.enter ()
178
+ .append (' g' )
179
+ .attr (' transform' , (d ) =>
180
+ vertical
181
+ ? ` translate(${(paddedScale (d .key )! - groupOffset )},0) `
182
+ : ` translate(0,${(paddedScale (d .key )! - groupOffset )}) `
183
+ )
184
+
185
+ // Each group gets a transparent rect covering the full band (using paddedScale.step())
186
+ barGroups
187
+ .append (' rect' )
188
+ .attr (' fill' , ' transparent' )
189
+ .attr (' pointer-events' , ' all' )
190
+ .attr (' x' , 0 )
191
+ .attr (' y' , (d ) => vertical ? y (d .value ) - groupOffset : 0 )
192
+ .attr (' width' , (d ) => vertical ? paddedScale .step () : y (d .value ) + groupOffset )
193
+ .attr (' height' , (d ) => vertical ? height - y (d .value ) + groupOffset : paddedScale .step ())
194
+
195
+ // Append the colored bar rect inside each group.
196
+ // We now offset it by groupOffset so its left edge is at paddedScale(d.key)
197
+ const bars = barGroups
175
198
.append (' rect' )
176
199
.attr (' fill' , (d ) => color (d ))
177
200
.attr (' rx' , 2 )
@@ -180,24 +203,24 @@ function renderChart({
180
203
if (! animate ) {
181
204
if (vertical ) {
182
205
bars
183
- .attr (' x' , ( d ) => x ( d . key ) ! )
206
+ .attr (' x' , groupOffset )
184
207
.attr (' y' , (d ) => y (d .value ))
185
- .attr (' width' , x .bandwidth ())
208
+ .attr (' width' , paddedScale .bandwidth ())
186
209
.attr (' height' , (d ) => height - y (d .value ))
187
210
} else {
188
211
bars
189
212
.attr (' x' , 0 )
190
- .attr (' y' , ( d ) => x ( d . key ) ! )
213
+ .attr (' y' , groupOffset )
191
214
.attr (' width' , (d ) => y (d .value ))
192
- .attr (' height' , x .bandwidth ())
215
+ .attr (' height' , paddedScale .bandwidth ())
193
216
}
194
217
} else {
195
218
// Animate the bars
196
219
if (vertical ) {
197
220
bars
198
- .attr (' x' , ( d ) => x ( d . key ) ! )
221
+ .attr (' x' , groupOffset )
199
222
.attr (' y' , height )
200
- .attr (' width' , x .bandwidth ())
223
+ .attr (' width' , paddedScale .bandwidth ())
201
224
.attr (' height' , 0 )
202
225
.transition ()
203
226
.duration (800 )
@@ -207,16 +230,43 @@ function renderChart({
207
230
} else {
208
231
bars
209
232
.attr (' x' , 0 )
210
- .attr (' y' , ( d ) => x ( d . key ) ! )
233
+ .attr (' y' , groupOffset )
211
234
.attr (' width' , 0 )
212
- .attr (' height' , x .bandwidth ())
235
+ .attr (' height' , paddedScale .bandwidth ())
213
236
.transition ()
214
237
.duration (800 )
215
238
.delay ((_ , i ) => i * 100 )
216
239
.attr (' width' , (d ) => y (d .value ))
217
240
}
218
241
}
219
242
243
+ if (props .tooltip ) {
244
+ const Tooltip = d3
245
+ .select (chartRef .value )
246
+ .append (' div' )
247
+ .attr (' class' , ' tooltip' )
248
+
249
+ function updatePos(event : PointerEvent ) {
250
+ const [x, y] = d3 .pointer (event , chartRef .value )
251
+ Tooltip
252
+ .style (' left' , ` ${x + 14 }px ` )
253
+ .style (' top' , ` ${y + 14 }px ` )
254
+ }
255
+
256
+ barGroups
257
+ .on (' pointerenter' , (event : PointerEvent , d ) => {
258
+ Tooltip
259
+ .html (props .tooltipFormat (d , color (d )))
260
+ .style (' opacity' , ' 1' )
261
+ updatePos (event )
262
+ })
263
+ .on (' pointermove' , updatePos )
264
+ .on (' pointerleave' , () => {
265
+ Tooltip
266
+ .style (' opacity' , ' 0' )
267
+ })
268
+ }
269
+
220
270
// Render outline for debugging
221
271
if (props .debug ) {
222
272
d3
@@ -261,6 +311,19 @@ watch(
261
311
position : relative;
262
312
}
263
313
314
+ :deep (.tooltip) {
315
+ opacity : 0 ;
316
+ pointer-events : none;
317
+ position : absolute;
318
+ top : 0 ;
319
+ left : 0 ;
320
+ padding : 2 px 8 px ;
321
+ background-color : var (--c-bg-elv-2 );
322
+ border : 1 px solid var (--c-divider );
323
+ border-radius : 6 px ;
324
+ font-size : 12 px ;
325
+ }
326
+
264
327
:deep (.tick line) {
265
328
display : none;
266
329
}
0 commit comments