Line data Source code
1 : import * as React from 'react';
2 : import * as d3 from 'd3';
3 : import { COLUMN_SUMMARY_BASELINE_COLOR } from './data_table_colors.js';
4 :
5 : export const NULL_SYMBOL = "∅";
6 :
7 : interface BarGeometry {
8 : x: number;
9 : y: number;
10 : width: number;
11 : height: number;
12 : }
13 :
14 1 : function getBarGeometry(
15 1 : bin: number,
16 1 : count: bigint,
17 1 : xScale: d3.ScaleBand<string>,
18 1 : yScale: d3.ScaleLinear<number, number>,
19 1 : height: number,
20 1 : ): BarGeometry | null {
21 1 : const x = xScale(bin.toString());
22 1 : const y = yScale(Number(count));
23 1 : const width = xScale.bandwidth();
24 1 : const barHeight = height - y;
25 1 : if (x == null || isNaN(y) || isNaN(width) || isNaN(barHeight)) return null;
26 1 : return { x, y, width, height: barHeight };
27 1 : }
28 :
29 : interface HistogramBarsProps {
30 : binCounts: BigInt64Array;
31 : filteredBinCounts: BigInt64Array | null;
32 : focusedBin: number | null;
33 : xScale: d3.ScaleBand<string>;
34 : yScale: d3.ScaleLinear<number, number>;
35 : height: number;
36 : totalBarColor: string;
37 : totalBarFocusedColor: string;
38 : filteredBarColor: string;
39 : filteredBarFocusedColor: string;
40 : }
41 :
42 1 : function HistogramBarsImpl(props: HistogramBarsProps): React.ReactElement {
43 1 : const filteredBinCounts = props.filteredBinCounts;
44 1 : const totalBars = Array.from({ length: props.binCounts.length }, (_, bin) => {
45 1 : const geometry = getBarGeometry(bin, props.binCounts[bin], props.xScale, props.yScale, props.height);
46 1 : return geometry == null ? null : <rect key={bin} {...geometry} style={{ fill: props.totalBarColor }} />;
47 1 : });
48 1 : const focusedTotalGeometry = props.focusedBin == null
49 1 : ? null
50 1 : : getBarGeometry(props.focusedBin, props.binCounts[props.focusedBin], props.xScale, props.yScale, props.height);
51 1 : const filteredBars = filteredBinCounts == null
52 1 : ? null
53 1 : : Array.from({ length: filteredBinCounts.length }, (_, bin) => {
54 1 : const geometry = getBarGeometry(bin, filteredBinCounts[bin], props.xScale, props.yScale, props.height);
55 1 : return geometry == null ? null : <rect key={`filtered-${bin}`} {...geometry} style={{ fill: props.filteredBarColor }} />;
56 1 : });
57 1 : const focusedFilteredGeometry = props.focusedBin == null || filteredBinCounts == null
58 1 : ? null
59 1 : : getBarGeometry(props.focusedBin, filteredBinCounts[props.focusedBin], props.xScale, props.yScale, props.height);
60 :
61 1 : return (
62 1 : <>
63 1 : {totalBars}
64 1 : {focusedTotalGeometry != null && (
65 1 : <rect key={`focused-${props.focusedBin}`} {...focusedTotalGeometry} style={{ fill: props.totalBarFocusedColor }} />
66 : )}
67 1 : {filteredBars}
68 1 : {focusedFilteredGeometry != null && (
69 1 : <rect key={`focused-filtered-${props.focusedBin}`} {...focusedFilteredGeometry} style={{ fill: props.filteredBarFocusedColor }} />
70 : )}
71 1 : </>
72 : );
73 1 : }
74 :
75 1 : export const HistogramBars = React.memo(HistogramBarsImpl);
76 :
77 : interface HistogramNullBarProps {
78 : countNull: number;
79 : filteredNullCount: number | null;
80 : focused: boolean | null;
81 : xScale: d3.ScaleBand<string>;
82 : yScale: d3.ScaleLinear<number, number>;
83 : xWidth: number;
84 : height: number;
85 : bottomMargin: number;
86 : transformX: number;
87 : totalBarColor: string;
88 : totalBarFocusedColor: string;
89 : filteredBarColor: string;
90 : filteredBarFocusedColor: string;
91 : onPointerOver: React.MouseEventHandler<SVGGElement>;
92 : onPointerOut: React.MouseEventHandler<SVGGElement>;
93 : }
94 :
95 1 : export function HistogramNullBar(props: HistogramNullBarProps): React.ReactElement | null {
96 1 : const nullX = props.xScale(NULL_SYMBOL);
97 1 : const nullY = props.yScale(props.countNull);
98 1 : const nullWidth = props.xScale.bandwidth();
99 1 : const nullHeight = props.height - nullY;
100 1 : if (nullX == null || isNaN(nullY) || isNaN(nullWidth) || isNaN(nullHeight)) return null;
101 :
102 1 : const filteredNullY = props.filteredNullCount == null ? null : props.yScale(props.filteredNullCount);
103 1 : const filteredNullHeight = filteredNullY == null ? null : props.height - filteredNullY;
104 1 : const hasValidFilteredBar = filteredNullY != null
105 1 : && filteredNullHeight != null
106 1 : && !isNaN(filteredNullY)
107 1 : && !isNaN(filteredNullHeight);
108 :
109 1 : return (
110 1 : <g
111 1 : transform={`translate(${props.transformX}, 0)`}
112 1 : onPointerOver={props.onPointerOver}
113 1 : onPointerMove={props.onPointerOver}
114 1 : onPointerOut={props.onPointerOut}
115 : >
116 1 : <rect
117 1 : x={nullX}
118 1 : y={nullY}
119 1 : width={nullWidth}
120 1 : height={nullHeight}
121 1 : style={{ fill: props.focused ? props.totalBarFocusedColor : props.totalBarColor }}
122 1 : />
123 1 : {hasValidFilteredBar && (
124 1 : <rect
125 1 : x={nullX}
126 1 : y={filteredNullY!}
127 1 : width={nullWidth}
128 1 : height={filteredNullHeight!}
129 1 : style={{ fill: props.focused ? props.filteredBarFocusedColor : props.filteredBarColor }}
130 1 : />
131 : )}
132 1 : <g transform={`translate(0, ${props.height})`}>
133 1 : <line
134 1 : x1={0} y1={1}
135 1 : x2={props.xWidth} y2={1}
136 1 : style={{ stroke: COLUMN_SUMMARY_BASELINE_COLOR }}
137 1 : />
138 1 : <rect
139 1 : x={nullX}
140 1 : y={0}
141 1 : width={nullWidth}
142 1 : height={props.bottomMargin}
143 1 : fillOpacity={0}
144 1 : />
145 1 : </g>
146 1 : </g>
147 : );
148 1 : }
|