@@ -4,6 +4,8 @@ import * as d3 from "d3";
44import { assert } from "../utils/assert.ts" ;
55import { tickFormatterForBins } from "./tick-formatter-for-bins.ts" ;
66import type { Bin , Scale } from "../types.ts" ;
7+ import type * as arrow from "apache-arrow" ;
8+ import { formatDataType , percentFormatter } from "./formatting.ts" ;
79
810interface HistogramOptions {
911 type : "number" | "date" ;
@@ -27,6 +29,7 @@ interface HistogramOptions {
2729 */
2830export function CrossfilterHistogramPlot (
2931 bins : Array < Bin > ,
32+ field : arrow . Field ,
3033 {
3134 type = "number" ,
3235 width = 125 ,
@@ -44,7 +47,10 @@ export function CrossfilterHistogramPlot(
4447 scale : ( type : string ) => Scale < number , number > ;
4548 update ( bins : Array < Bin > , opts : { nullCount : number } ) : void ;
4649} {
50+ const fieldType = formatDataType ( field . type ) ;
51+ const total = bins . reduce ( ( sum , bin ) => sum + bin . length , 0 ) ;
4752 let hovered = signal < number | Date | undefined > ( undefined ) ;
53+ let countLabel = signal < string > ( fieldType ) ;
4854 let nullBinWidth = nullCount === 0 ? 0 : 5 ;
4955 let spacing = nullBinWidth ? 4 : 0 ;
5056 let extent = /** @type {const } */ ( [
@@ -148,6 +154,13 @@ export function CrossfilterHistogramPlot(
148154 )
149155 . attr ( "width" , bbox . width + 5 )
150156 . attr ( "height" , bbox . height + 5 ) ;
157+
158+ const labelElement = svg
159+ . node ( )
160+ ?. parentElement ?. parentElement ?. querySelector ( ".gray" ) ;
161+ if ( labelElement ) {
162+ labelElement . textContent = countLabel . value ;
163+ }
151164 } ) ;
152165
153166 /** @type {typeof foregroundBarGroup | undefined } */
@@ -215,7 +228,8 @@ export function CrossfilterHistogramPlot(
215228 . attr ( "x" , ( d ) => x ( d . x0 ) + 1.5 )
216229 . attr ( "width" , ( d ) => x ( d . x1 ) - x ( d . x0 ) - 1.5 )
217230 . attr ( "y" , ( d ) => y ( d . length ) )
218- . attr ( "height" , ( d ) => y ( 0 ) - y ( d . length ) ) ;
231+ . attr ( "height" , ( d ) => y ( 0 ) - y ( d . length ) )
232+ . attr ( "opacity" , 1 ) ;
219233 foregroundNullGroup
220234 ?. select ( "rect" )
221235 . attr ( "y" , y ( nullCount ) )
@@ -237,10 +251,64 @@ export function CrossfilterHistogramPlot(
237251 let node = svg . node ( ) ;
238252 assert ( node , "Infallable" ) ;
239253
254+ // Function to find the closest rect to a given x-coordinate
255+ function findClosestRect ( x : number ) : SVGRectElement | null {
256+ let closestRect : SVGRectElement | null = null ;
257+ let minDistance = Infinity ;
258+
259+ foregroundBarGroup . selectAll ( "rect" ) . each ( function ( ) {
260+ const rect = d3 . select ( this ) ;
261+ const rectX = parseFloat ( rect . attr ( "x" ) ) ;
262+ const rectWidth = parseFloat ( rect . attr ( "width" ) ) ;
263+ const rectCenter = rectX + rectWidth / 2 ;
264+ const distance = Math . abs ( x - rectCenter ) ;
265+
266+ if ( distance < minDistance ) {
267+ minDistance = distance ;
268+ closestRect = this as SVGRectElement ;
269+ }
270+ } ) ;
271+
272+ return closestRect ;
273+ }
274+
275+ axes . on ( "mousemove" , ( event ) => {
276+ const relativeX = event . clientX - node . getBoundingClientRect ( ) . left ;
277+ const hoveredX = x . invert ( relativeX ) ;
278+ hovered . value = clamp ( hoveredX , xmin , xmax ) ;
279+
280+ const closestRect = findClosestRect ( relativeX ) ;
281+
282+ foregroundBarGroup . selectAll ( "rect" ) . attr ( "opacity" , function ( ) {
283+ return this === closestRect ? 1 : 0.3 ;
284+ } ) ;
285+
286+ const hoveredValue = hovered . value ;
287+
288+ const hoveredBin = hoveredValue !== undefined
289+ ? bins . find ( ( bin ) => hoveredValue >= bin . x0 && hoveredValue < bin . x1 )
290+ : undefined ;
291+ const hoveredValueCount = hoveredBin ?. length ;
292+
293+ countLabel . value =
294+ hoveredValue !== undefined && hoveredValueCount !== undefined
295+ ? `${ hoveredValueCount } row${ hoveredValueCount === 1 ? "" : "s" } (${
296+ percentFormatter ( hoveredValueCount / total )
297+ } )`
298+ : fieldType ;
299+ } ) ;
300+
240301 node . addEventListener ( "mousemove" , ( event ) => {
241302 const relativeX = event . clientX - node . getBoundingClientRect ( ) . left ;
242303 hovered . value = clamp ( x . invert ( relativeX ) , xmin , xmax ) ;
243304 } ) ;
305+
306+ axes . on ( "mouseleave" , ( ) => {
307+ hovered . value = undefined ;
308+ foregroundBarGroup . selectAll ( "rect" ) . attr ( "opacity" , 1 ) ;
309+ countLabel . value = fieldType ;
310+ } ) ;
311+
244312 node . addEventListener ( "mouseleave" , ( ) => {
245313 hovered . value = undefined ;
246314 } ) ;
0 commit comments