@redsift/dashboard 8.0.0 → 8.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (155) hide show
  1. package/.env +2 -0
  2. package/coverage/clover.xml +509 -0
  3. package/coverage/coverage-final.json +22 -0
  4. package/coverage/lcov-report/ConnectedDataGrid/ConnectedDataGrid.tsx.html +193 -0
  5. package/coverage/lcov-report/ConnectedDataGrid/index.html +131 -0
  6. package/coverage/lcov-report/ConnectedDataGrid/index.ts.html +85 -0
  7. package/coverage/lcov-report/Dashboard/Dashboard.tsx.html +229 -0
  8. package/coverage/lcov-report/Dashboard/context.ts.html +118 -0
  9. package/coverage/lcov-report/Dashboard/index.html +176 -0
  10. package/coverage/lcov-report/Dashboard/index.ts.html +97 -0
  11. package/coverage/lcov-report/Dashboard/reducer.ts.html +139 -0
  12. package/coverage/lcov-report/Dashboard/types.ts.html +190 -0
  13. package/coverage/lcov-report/HorizontalBarChart/HorizontalBarChart.tsx.html +430 -0
  14. package/coverage/lcov-report/HorizontalBarChart/index.html +161 -0
  15. package/coverage/lcov-report/HorizontalBarChart/index.ts.html +88 -0
  16. package/coverage/lcov-report/HorizontalBarChart/styles.ts.html +217 -0
  17. package/coverage/lcov-report/HorizontalBarChart/types.ts.html +184 -0
  18. package/coverage/lcov-report/PieChart/PieChart.tsx.html +736 -0
  19. package/coverage/lcov-report/PieChart/index.html +161 -0
  20. package/coverage/lcov-report/PieChart/index.ts.html +88 -0
  21. package/coverage/lcov-report/PieChart/styles.ts.html +244 -0
  22. package/coverage/lcov-report/PieChart/types.ts.html +184 -0
  23. package/coverage/lcov-report/base.css +224 -0
  24. package/coverage/lcov-report/block-navigation.js +87 -0
  25. package/coverage/lcov-report/components/ChartEmptyState/ChartEmptyState.tsx.html +679 -0
  26. package/coverage/lcov-report/components/ChartEmptyState/index.html +146 -0
  27. package/coverage/lcov-report/components/ChartEmptyState/index.ts.html +91 -0
  28. package/coverage/lcov-report/components/ChartEmptyState/styles.ts.html +184 -0
  29. package/coverage/lcov-report/components/ConnectedDataGrid/ConnectedDataGrid.tsx.html +181 -0
  30. package/coverage/lcov-report/components/ConnectedDataGrid/index.html +131 -0
  31. package/coverage/lcov-report/components/ConnectedDataGrid/index.ts.html +85 -0
  32. package/coverage/lcov-report/components/CrossfilterRegistry/CrossfilterRegistry.ts.html +163 -0
  33. package/coverage/lcov-report/components/CrossfilterRegistry/index.html +131 -0
  34. package/coverage/lcov-report/components/CrossfilterRegistry/index.ts.html +88 -0
  35. package/coverage/lcov-report/components/Dashboard/Dashboard.tsx.html +289 -0
  36. package/coverage/lcov-report/components/Dashboard/context.ts.html +115 -0
  37. package/coverage/lcov-report/components/Dashboard/index.html +176 -0
  38. package/coverage/lcov-report/components/Dashboard/index.ts.html +97 -0
  39. package/coverage/lcov-report/components/Dashboard/reducer.ts.html +382 -0
  40. package/coverage/lcov-report/components/Dashboard/types.ts.html +226 -0
  41. package/coverage/lcov-report/components/DataGrid/DataGrid.tsx.html +202 -0
  42. package/coverage/lcov-report/components/DataGrid/index.html +131 -0
  43. package/coverage/lcov-report/components/DataGrid/index.ts.html +91 -0
  44. package/coverage/lcov-report/components/EmptyChart/EmptyChart.tsx.html +244 -0
  45. package/coverage/lcov-report/components/EmptyChart/index.html +146 -0
  46. package/coverage/lcov-report/components/EmptyChart/index.ts.html +91 -0
  47. package/coverage/lcov-report/components/EmptyChart/styles.ts.html +241 -0
  48. package/coverage/lcov-report/components/HorizontalBarChart/HorizontalBarChart.tsx.html +1063 -0
  49. package/coverage/lcov-report/components/HorizontalBarChart/index.html +161 -0
  50. package/coverage/lcov-report/components/HorizontalBarChart/index.ts.html +91 -0
  51. package/coverage/lcov-report/components/HorizontalBarChart/styles.ts.html +385 -0
  52. package/coverage/lcov-report/components/HorizontalBarChart/types.ts.html +328 -0
  53. package/coverage/lcov-report/components/PDFExportButton/PdfDocument.tsx.html +688 -0
  54. package/coverage/lcov-report/components/PDFExportButton/PdfExportButton.tsx.html +583 -0
  55. package/coverage/lcov-report/components/PDFExportButton/index.html +161 -0
  56. package/coverage/lcov-report/components/PDFExportButton/index.ts.html +88 -0
  57. package/coverage/lcov-report/components/PDFExportButton/styles.ts.html +532 -0
  58. package/coverage/lcov-report/components/PDFExportButton/utils.ts.html +283 -0
  59. package/coverage/lcov-report/components/PieChart/PieChart.tsx.html +1363 -0
  60. package/coverage/lcov-report/components/PieChart/index.html +161 -0
  61. package/coverage/lcov-report/components/PieChart/index.ts.html +91 -0
  62. package/coverage/lcov-report/components/PieChart/styles.ts.html +388 -0
  63. package/coverage/lcov-report/components/PieChart/types.ts.html +325 -0
  64. package/coverage/lcov-report/components/ResetButton/ResetButton.tsx.html +160 -0
  65. package/coverage/lcov-report/components/ResetButton/index.html +131 -0
  66. package/coverage/lcov-report/components/ResetButton/index.ts.html +91 -0
  67. package/coverage/lcov-report/components/ScatterPlot/ScatterPlot.tsx.html +2881 -0
  68. package/coverage/lcov-report/components/ScatterPlot/index.html +176 -0
  69. package/coverage/lcov-report/components/ScatterPlot/index.ts.html +91 -0
  70. package/coverage/lcov-report/components/ScatterPlot/styles.ts.html +505 -0
  71. package/coverage/lcov-report/components/ScatterPlot/types.ts.html +370 -0
  72. package/coverage/lcov-report/components/ScatterPlot/utils.ts.html +136 -0
  73. package/coverage/lcov-report/components/StaticPieChart/StaticPieChart.tsx.html +286 -0
  74. package/coverage/lcov-report/components/StaticPieChart/index.html +131 -0
  75. package/coverage/lcov-report/components/StaticPieChart/index.ts.html +88 -0
  76. package/coverage/lcov-report/components/TimeSeriesBarChart/TimeSeriesBarChart.tsx.html +1744 -0
  77. package/coverage/lcov-report/components/TimeSeriesBarChart/index.html +161 -0
  78. package/coverage/lcov-report/components/TimeSeriesBarChart/index.ts.html +91 -0
  79. package/coverage/lcov-report/components/TimeSeriesBarChart/styles.ts.html +361 -0
  80. package/coverage/lcov-report/components/TimeSeriesBarChart/types.ts.html +319 -0
  81. package/coverage/lcov-report/components/WithFilters/FilterableBarChart.tsx.html +628 -0
  82. package/coverage/lcov-report/components/WithFilters/FilterableDataGrid.tsx.html +220 -0
  83. package/coverage/lcov-report/components/WithFilters/FilterablePieChart.tsx.html +622 -0
  84. package/coverage/lcov-report/components/WithFilters/FilterableScatterPlot.tsx.html +1090 -0
  85. package/coverage/lcov-report/components/WithFilters/WithFilters.tsx.html +172 -0
  86. package/coverage/lcov-report/components/WithFilters/index.html +191 -0
  87. package/coverage/lcov-report/components/WithFilters/index.ts.html +91 -0
  88. package/coverage/lcov-report/components/index.html +116 -0
  89. package/coverage/lcov-report/components/index.ts.html +97 -0
  90. package/coverage/lcov-report/favicon.png +0 -0
  91. package/coverage/lcov-report/hooks/index.html +116 -0
  92. package/coverage/lcov-report/hooks/useCategoricalChartAsListbox.ts.html +478 -0
  93. package/coverage/lcov-report/hooks/useChartAsListbox.ts.html +655 -0
  94. package/coverage/lcov-report/index.html +206 -0
  95. package/coverage/lcov-report/prettify.css +1 -0
  96. package/coverage/lcov-report/prettify.js +2 -0
  97. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  98. package/coverage/lcov-report/sorter.js +196 -0
  99. package/coverage/lcov-report/utils/groupReduceCount.ts.html +94 -0
  100. package/coverage/lcov-report/utils/groupReduceSum.ts.html +97 -0
  101. package/coverage/lcov-report/utils/groupReducers/groupReduceCount.ts.html +100 -0
  102. package/coverage/lcov-report/utils/groupReducers/groupReduceSum.ts.html +103 -0
  103. package/coverage/lcov-report/utils/groupReducers/index.html +146 -0
  104. package/coverage/lcov-report/utils/groupReducers/index.ts.html +91 -0
  105. package/coverage/lcov-report/utils/index.html +116 -0
  106. package/coverage/lcov-report/utils/index.ts.html +88 -0
  107. package/coverage/lcov.info +1070 -0
  108. package/dist/package.json +113 -0
  109. package/index.ts +1 -0
  110. package/jest.config.js +3 -0
  111. package/package.json +2 -3
  112. package/rollup.config.js +13 -0
  113. package/src/components/ChartEmptyState/ChartEmptyState.stories.tsx +23 -0
  114. package/src/components/ChartEmptyState/ChartEmptyState.tsx +198 -0
  115. package/src/components/ChartEmptyState/index.ts +2 -0
  116. package/src/components/ChartEmptyState/styles.ts +33 -0
  117. package/src/components/ChartEmptyState/types.ts +15 -0
  118. package/src/components/CrossfilterRegistry/CrossfilterRegistry.ts +26 -0
  119. package/src/components/CrossfilterRegistry/index.ts +1 -0
  120. package/src/components/Dashboard/Dashboard.stories.tsx +602 -0
  121. package/src/components/Dashboard/Dashboard.test.tsx +19 -0
  122. package/src/components/Dashboard/Dashboard.tsx +68 -0
  123. package/src/components/Dashboard/__snapshots__/Dashboard.stories.storyshot +24646 -0
  124. package/src/components/Dashboard/context.ts +10 -0
  125. package/src/components/Dashboard/index.ts +4 -0
  126. package/src/components/Dashboard/reducer.ts +99 -0
  127. package/src/components/Dashboard/types.ts +47 -0
  128. package/src/components/PdfExportButton/PdfDocument.tsx +203 -0
  129. package/src/components/PdfExportButton/PdfExportButton.tsx +168 -0
  130. package/src/components/PdfExportButton/index.ts +3 -0
  131. package/src/components/PdfExportButton/styles.ts +151 -0
  132. package/src/components/PdfExportButton/types.ts +59 -0
  133. package/src/components/TimeSeriesBarChart/TimeSeriesBarChart.tsx +565 -0
  134. package/src/components/TimeSeriesBarChart/index.ts +4 -0
  135. package/src/components/TimeSeriesBarChart/styles.ts +94 -0
  136. package/src/components/TimeSeriesBarChart/types.ts +82 -0
  137. package/src/components/WithFilters/FilterableBarChart.tsx +181 -0
  138. package/src/components/WithFilters/FilterableDataGrid.tsx +45 -0
  139. package/src/components/WithFilters/FilterablePieChart.tsx +179 -0
  140. package/src/components/WithFilters/FilterableScatterPlot.tsx +335 -0
  141. package/src/components/WithFilters/WithFilters.tsx +29 -0
  142. package/src/components/WithFilters/index.ts +2 -0
  143. package/src/components/WithFilters/types.ts +45 -0
  144. package/src/hooks/useCategoricalChartAsListbox.ts +131 -0
  145. package/src/index.ts +8 -0
  146. package/src/types.ts +39 -0
  147. package/src/utils/groupReducers/groupReduceCount.ts +5 -0
  148. package/src/utils/groupReducers/groupReduceSum.ts +6 -0
  149. package/src/utils/groupReducers/index.ts +2 -0
  150. package/src/utils/index.ts +1 -0
  151. package/tsconfig.json +3 -0
  152. /package/{CONTRIBUTING.md → dist/CONTRIBUTING.md} +0 -0
  153. /package/{index.d.ts → dist/index.d.ts} +0 -0
  154. /package/{index.js → dist/index.js} +0 -0
  155. /package/{index.js.map → dist/index.js.map} +0 -0
@@ -0,0 +1,335 @@
1
+ import { DotDatum, LegendItemDatum } from '@redsift/charts';
2
+ import { Dimension, Group } from '../../types';
3
+ import React, {
4
+ useEffect,
5
+ useContext,
6
+ useId,
7
+ useState,
8
+ isValidElement,
9
+ } from 'react';
10
+ import { CrossfilterRegistry } from '../CrossfilterRegistry';
11
+ import { DashboardContext, DashboardReducerActionType } from '../Dashboard';
12
+ import { ChartEmptyState } from '../ChartEmptyState';
13
+ import { ScaleLinear as d3ScaleLinear } from 'd3';
14
+ import { ChartsWithFiltersProps } from './types';
15
+
16
+ type BrushFilter = {
17
+ minX: number;
18
+ minY: number;
19
+ maxX: number;
20
+ maxY: number;
21
+ };
22
+
23
+ const filter =
24
+ (legendFilters?: string[], brushFilters?: BrushFilter) =>
25
+ (d: [number, number, string]) => {
26
+ if (legendFilters && legendFilters.length && brushFilters) {
27
+ return (
28
+ d[0] >= brushFilters.minX &&
29
+ d[0] <= brushFilters.maxX &&
30
+ d[1] >= brushFilters.minY &&
31
+ d[1] <= brushFilters.maxY &&
32
+ legendFilters.includes(d[2])
33
+ );
34
+ } else if (legendFilters && legendFilters.length) {
35
+ return legendFilters.includes(d[2]);
36
+ } else if (brushFilters) {
37
+ return (
38
+ d[0] >= brushFilters.minX &&
39
+ d[0] <= brushFilters.maxX &&
40
+ d[1] >= brushFilters.minY &&
41
+ d[1] <= brushFilters.maxY
42
+ );
43
+ } else {
44
+ return true;
45
+ }
46
+ };
47
+
48
+ const isFiltered =
49
+ (legendFilters?: string[], brushFilters?: BrushFilter) =>
50
+ (datum: DotDatum) => {
51
+ if (legendFilters && legendFilters.length && brushFilters) {
52
+ return (
53
+ datum.data.key[0] >= brushFilters.minX &&
54
+ datum.data.key[0] <= brushFilters.maxX &&
55
+ datum.data.key[1] >= brushFilters.minY &&
56
+ datum.data.key[1] <= brushFilters.maxY &&
57
+ legendFilters.includes(datum.data.key[2] as string)
58
+ );
59
+ } else if (legendFilters && legendFilters.length) {
60
+ return legendFilters.includes(datum.data.key[2] as string);
61
+ } else if (brushFilters) {
62
+ return (
63
+ datum.data.key[0] >= brushFilters.minX &&
64
+ datum.data.key[0] <= brushFilters.maxX &&
65
+ datum.data.key[1] >= brushFilters.minY &&
66
+ datum.data.key[1] <= brushFilters.maxY
67
+ );
68
+ } else {
69
+ return true;
70
+ }
71
+ };
72
+
73
+ export const FilterableScatterPlot: React.FC<ChartsWithFiltersProps> = (
74
+ props
75
+ ) => {
76
+ const {
77
+ children,
78
+ datagridCoordinatesCategoryDimFilter,
79
+ dimension,
80
+ group,
81
+ id: propsId,
82
+ isDimensionArray,
83
+ isResetable,
84
+ localeText,
85
+ onFilter,
86
+ } = props;
87
+ const id = propsId ?? useId();
88
+
89
+ const { emptyChartTitle, emptyChartSubtitle, emptyChartResetLabel } = {
90
+ ...localeText,
91
+ };
92
+
93
+ const {
94
+ data,
95
+ dispatch,
96
+ toggleUpdateContext,
97
+ // state: { tableFilters },
98
+ } = useContext(DashboardContext);
99
+
100
+ const [ndxDimension, setNdxDimension] = useState<Dimension>();
101
+ const [ndxGroup, setNdxGroup] = useState<Group>();
102
+
103
+ useEffect(() => {
104
+ const computedNdxDimension = CrossfilterRegistry.get(data).dimension(
105
+ dimension,
106
+ isDimensionArray
107
+ );
108
+ const computedNdxGroup = group
109
+ ? group(computedNdxDimension)
110
+ : computedNdxDimension.group();
111
+ setNdxDimension(computedNdxDimension);
112
+ setNdxGroup(computedNdxGroup);
113
+ return function cleanup() {
114
+ computedNdxDimension.filterAll();
115
+ };
116
+ }, [dimension, data, group]);
117
+
118
+ const [brushFilters, setBrushFilters] = useState<BrushFilter>();
119
+ const [legendFilters, setLegendFilters] = useState<string[]>([]);
120
+
121
+ const handleBrushFilter = (newFilters: BrushFilter) => {
122
+ setBrushFilters(newFilters);
123
+ ndxDimension!.filter((d) =>
124
+ filter(
125
+ legendFilters,
126
+ newFilters
127
+ )(d as unknown as [number, number, string])
128
+ );
129
+ toggleUpdateContext?.();
130
+
131
+ if (datagridCoordinatesCategoryDimFilter) {
132
+ dispatch?.({
133
+ type: DashboardReducerActionType.FilterTable,
134
+ filter: {
135
+ id: `${id}-x`,
136
+ columnField: datagridCoordinatesCategoryDimFilter[0].field,
137
+ operatorValue: datagridCoordinatesCategoryDimFilter[0].operator,
138
+ value: [newFilters.minX, newFilters.maxX],
139
+ },
140
+ });
141
+ dispatch?.({
142
+ type: DashboardReducerActionType.FilterTable,
143
+ filter: {
144
+ id: `${id}-y`,
145
+ columnField: datagridCoordinatesCategoryDimFilter[1].field,
146
+ operatorValue: datagridCoordinatesCategoryDimFilter[1].operator,
147
+ value: [newFilters.minY, newFilters.maxY],
148
+ },
149
+ });
150
+ }
151
+
152
+ if (onFilter) {
153
+ onFilter(newFilters);
154
+ }
155
+ };
156
+
157
+ const handleLegendFilter = (category: string) => {
158
+ let newFilters: string[];
159
+ if (legendFilters.includes(category)) {
160
+ newFilters = legendFilters.filter((f) => f !== category);
161
+ } else {
162
+ newFilters = [...legendFilters, category];
163
+ }
164
+
165
+ setLegendFilters(newFilters);
166
+ ndxDimension!.filter((d) =>
167
+ filter(newFilters, brushFilters)(d as unknown as [number, number, string])
168
+ );
169
+ toggleUpdateContext?.();
170
+
171
+ if (datagridCoordinatesCategoryDimFilter?.length === 3) {
172
+ dispatch?.({
173
+ type: DashboardReducerActionType.FilterTable,
174
+ filter: {
175
+ id: `${id}-category`,
176
+ columnField: datagridCoordinatesCategoryDimFilter[2].field,
177
+ operatorValue: datagridCoordinatesCategoryDimFilter[2].operator,
178
+ value: newFilters,
179
+ },
180
+ });
181
+ }
182
+
183
+ if (onFilter) {
184
+ onFilter(newFilters);
185
+ }
186
+ };
187
+
188
+ const resetFilters = (brush = true, legend = true) => {
189
+ if (brush) {
190
+ setBrushFilters(undefined);
191
+ }
192
+ if (legend) {
193
+ setLegendFilters([]);
194
+ }
195
+ ndxDimension!.filter((d) =>
196
+ filter(
197
+ legend ? [] : legendFilters,
198
+ brush ? undefined : brushFilters
199
+ )(d as unknown as [number, number, string])
200
+ );
201
+ toggleUpdateContext?.();
202
+
203
+ if (datagridCoordinatesCategoryDimFilter) {
204
+ if (brush) {
205
+ dispatch?.({
206
+ type: DashboardReducerActionType.ResetFilter,
207
+ filter: {
208
+ id: `${id}-x`,
209
+ columnField: datagridCoordinatesCategoryDimFilter[0].field,
210
+ operatorValue: datagridCoordinatesCategoryDimFilter[0].operator,
211
+ },
212
+ });
213
+ dispatch?.({
214
+ type: DashboardReducerActionType.ResetFilter,
215
+ filter: {
216
+ id: `${id}-y`,
217
+ columnField: datagridCoordinatesCategoryDimFilter[1].field,
218
+ operatorValue: datagridCoordinatesCategoryDimFilter[1].operator,
219
+ },
220
+ });
221
+ }
222
+ if (legend && datagridCoordinatesCategoryDimFilter.length === 3) {
223
+ dispatch?.({
224
+ type: DashboardReducerActionType.ResetFilter,
225
+ filter: {
226
+ id: `${id}-category`,
227
+ columnField: datagridCoordinatesCategoryDimFilter[2].field,
228
+ operatorValue: datagridCoordinatesCategoryDimFilter[2].operator,
229
+ },
230
+ });
231
+ }
232
+ }
233
+
234
+ if (onFilter) {
235
+ onFilter();
236
+ }
237
+ };
238
+
239
+ // useEffect(() => {
240
+ // if (!datagridCoordinatesCategoryDimFilter) {
241
+ // return;
242
+ // }
243
+
244
+ // const updatedXFilters = tableFilters.find(
245
+ // (filter) =>
246
+ // filter.columnField === datagridCoordinatesCategoryDimFilter[0].field &&
247
+ // filter.operatorValue ===
248
+ // datagridCoordinatesCategoryDimFilter[0].operator
249
+ // )?.value;
250
+ // const updatedYFilters = tableFilters.find(
251
+ // (filter) =>
252
+ // filter.columnField === datagridCoordinatesCategoryDimFilter[1].field &&
253
+ // filter.operatorValue ===
254
+ // datagridCoordinatesCategoryDimFilter[1].operator
255
+ // )?.value;
256
+ // console.log(updatedXFilters, updatedYFilters, brushFilters);
257
+ // const updatedFilters = {
258
+ // minX: Number(updatedXFilters[0]),
259
+ // maxX: Number(updatedXFilters[0]),
260
+ // minY: Number(updatedYFilters[1]),
261
+ // maxY: Number(updatedYFilters[1]),
262
+ // }
263
+ // if (updatedFilters && JSON.stringify(updatedFilters) !== JSON.stringify(brushFilters)) {
264
+ // }
265
+ // }, [tableFilters]);
266
+
267
+ const emptyComponent = (
268
+ <ChartEmptyState
269
+ title={emptyChartTitle!}
270
+ subtitle={emptyChartSubtitle}
271
+ resetLabel={emptyChartResetLabel}
272
+ onReset={resetFilters}
273
+ />
274
+ );
275
+
276
+ const legendProps = {
277
+ onLegendItemClick: (datum: LegendItemDatum) => {
278
+ handleLegendFilter(datum.data.key);
279
+ },
280
+ isLegendItemSelected: (datum: LegendItemDatum) => {
281
+ return (
282
+ legendFilters.length === 0 || legendFilters.includes(datum.data.key)
283
+ );
284
+ },
285
+ legendItemRole: 'option',
286
+ };
287
+
288
+ const filterProps = {
289
+ isBrushable: true,
290
+ data: ndxGroup ? JSON.parse(JSON.stringify(ndxGroup.all())) : undefined,
291
+ dotRole: 'option',
292
+ emptyComponent,
293
+ id,
294
+ legendProps,
295
+ onBrush: (
296
+ selection: [[number, number], [number, number]],
297
+ scaleX: d3ScaleLinear<number, number>,
298
+ scaleY: d3ScaleLinear<number, number>
299
+ ) => {
300
+ if (selection) {
301
+ setBrushFilters({
302
+ minX: scaleX.invert(selection[0][0]),
303
+ minY: scaleY.invert(selection[1][1]),
304
+ maxX: scaleX.invert(selection[1][0]),
305
+ maxY: scaleY.invert(selection[0][1]),
306
+ });
307
+ }
308
+ },
309
+ onBrushEnd: (
310
+ selection: [[number, number], [number, number]],
311
+ scaleX?: d3ScaleLinear<number, number>,
312
+ scaleY?: d3ScaleLinear<number, number>
313
+ ) => {
314
+ if (selection && scaleX && scaleY) {
315
+ handleBrushFilter({
316
+ minX: scaleX.invert(selection[0][0]),
317
+ minY: scaleY.invert(selection[1][1]),
318
+ maxX: scaleX.invert(selection[1][0]),
319
+ maxY: scaleY.invert(selection[0][1]),
320
+ });
321
+ } else {
322
+ resetFilters(true, false);
323
+ }
324
+ },
325
+ onReset: isResetable ? resetFilters : undefined,
326
+ isDotSelected: (datum: DotDatum) =>
327
+ isFiltered(legendFilters, brushFilters)(datum),
328
+ };
329
+
330
+ if (isValidElement(children)) {
331
+ return React.cloneElement(children, { ...filterProps });
332
+ }
333
+
334
+ return null;
335
+ };
@@ -0,0 +1,29 @@
1
+ import React from 'react';
2
+ import { isComponent } from '@redsift/design-system';
3
+ import { FilterableBarChart } from './FilterableBarChart';
4
+ import { FilterableDataGrid } from './FilterableDataGrid';
5
+ import { FilterablePieChart } from './FilterablePieChart';
6
+ import { FilterableScatterPlot } from './FilterableScatterPlot';
7
+ import { WithFiltersProps } from './types';
8
+
9
+ export const WithFilters: React.FC<WithFiltersProps> = (props) => {
10
+ if (isComponent('DataGrid')(props.children)) {
11
+ return <FilterableDataGrid {...props} />;
12
+ }
13
+
14
+ const { dimension } = props;
15
+
16
+ if (!dimension) {
17
+ return null;
18
+ }
19
+
20
+ if (isComponent('BarChart')(props.children)) {
21
+ return <FilterableBarChart {...props} dimension={dimension} />;
22
+ } else if (isComponent('PieChart')(props.children)) {
23
+ return <FilterablePieChart {...props} dimension={dimension} />;
24
+ } else if (isComponent('ScatterPlot')(props.children)) {
25
+ return <FilterableScatterPlot {...props} dimension={dimension} />;
26
+ }
27
+
28
+ return React.isValidElement(props.children) ? props.children : null;
29
+ };
@@ -0,0 +1,2 @@
1
+ export * from './WithFilters';
2
+ export * from './types';
@@ -0,0 +1,45 @@
1
+ import { ComponentProps } from 'react';
2
+ import { DimensionSelector, Dimension, Group } from '../../types';
3
+ import { NaturallyOrderedValue } from 'crossfilter2';
4
+
5
+ interface LocaleText {
6
+ emptyChartTitle?: string;
7
+ emptyChartSubtitle?: string;
8
+ emptyChartResetLabel?: string;
9
+ resetLabel?: string;
10
+ }
11
+
12
+ type CategoryFilterType = {
13
+ field: string;
14
+ operator: 'containsAnyOf' | 'endsWithAnyOf' | 'isAnyOf' | 'startsWithAnyOf';
15
+ };
16
+
17
+ type CoordinateFilterType = {
18
+ field: string;
19
+ operator: 'isBetween';
20
+ };
21
+
22
+ export interface WithFiltersProps extends ComponentProps<'div'> {
23
+ /** In case of a chart based on one dimension (category), which datagrid column the chart is filtering and using which operator. */
24
+ datagridCategoryDimFilter?: CategoryFilterType;
25
+ /** In case of a chart based on two dimension coordinates (x, y), which datagrid columns the chart is filtering. */
26
+ datagridCoordinatesCategoryDimFilter?:
27
+ | [CoordinateFilterType, CoordinateFilterType]
28
+ | [CoordinateFilterType, CoordinateFilterType, CategoryFilterType];
29
+ /** Method that will be used by crossfilter to compute the dimensions of the charts. */
30
+ dimension?: DimensionSelector;
31
+ /** Method that will be used by crossfilter to compute the groups of the charts. */
32
+ group?: (d: Dimension) => Group;
33
+ /** Whether the dimension field is an array or not. */
34
+ isDimensionArray?: boolean;
35
+ /** Whether the reset button is hidden or not. */
36
+ isResetable?: boolean;
37
+ /** Overriden values for internal labels and texts. */
38
+ localeText?: LocaleText;
39
+ /** Method to call when a filter occured on the chart. */
40
+ onFilter?: (filters?: NaturallyOrderedValue[] | any) => void;
41
+ }
42
+
43
+ export interface ChartsWithFiltersProps
44
+ extends Required<Pick<WithFiltersProps, 'dimension'>>,
45
+ Omit<WithFiltersProps, 'dimension'> {}
@@ -0,0 +1,131 @@
1
+ import { BaseMixin } from 'dc';
2
+ import { useRef, KeyboardEvent, useMemo, MutableRefObject } from 'react';
3
+ import { Group } from '../types';
4
+
5
+ type UseCategoricalChartAsListboxProps = <T extends BaseMixin<T>>(props: {
6
+ id: string;
7
+ ref: MutableRefObject<HTMLDivElement>;
8
+ type: 'bar' | 'slice';
9
+ ndxGroup?: Group;
10
+ orientation?: 'horizontal' | 'vertical';
11
+ }) => void;
12
+
13
+ /**
14
+ * Hook to give to a chart the roles, events and attributes of a listbox.
15
+ */
16
+ export const useCategoricalChartAsListbox: UseCategoricalChartAsListboxProps =
17
+ ({ id, ref, type, ndxGroup, orientation = 'horizontal' }) => {
18
+ const currentIndexRef = useRef(0);
19
+
20
+ const props = useMemo(() => {
21
+ const numberOfGroups = ndxGroup
22
+ ?.all()
23
+ .filter((datum) => datum.value).length;
24
+ if (ref && numberOfGroups) {
25
+ const getCurrentOption = () =>
26
+ ref.current.querySelector(
27
+ `._${currentIndexRef.current}`
28
+ ) as HTMLElement;
29
+
30
+ const next = () => {
31
+ getCurrentOption().classList.remove('focused');
32
+ const index =
33
+ currentIndexRef.current === numberOfGroups - 1
34
+ ? 0
35
+ : currentIndexRef.current + 1;
36
+ currentIndexRef.current = index;
37
+ ref.current.setAttribute(
38
+ 'aria-activedescendant',
39
+ `id${id}__${type}-${currentIndexRef.current}`
40
+ );
41
+ getCurrentOption().classList.add('focused');
42
+ };
43
+
44
+ const previous = () => {
45
+ getCurrentOption().classList.remove('focused');
46
+ const index =
47
+ currentIndexRef.current === 0
48
+ ? numberOfGroups - 1
49
+ : currentIndexRef.current - 1;
50
+ currentIndexRef.current = index;
51
+ ref.current.setAttribute(
52
+ 'aria-activedescendant',
53
+ `id${id}__${type}-${currentIndexRef.current}`
54
+ );
55
+ getCurrentOption().classList.add('focused');
56
+ };
57
+
58
+ const focus = () => {
59
+ ref.current.setAttribute(
60
+ 'aria-activedescendant',
61
+ `id${id}__${type}-0`
62
+ );
63
+ getCurrentOption().classList.add('focused');
64
+ };
65
+
66
+ const blur = () => {
67
+ getCurrentOption().classList.remove('focused');
68
+ currentIndexRef.current = 0;
69
+ ref.current.setAttribute('aria-activedescendant', '');
70
+ };
71
+
72
+ const keydown = (e: Event) => {
73
+ e.stopPropagation();
74
+
75
+ switch ((e as unknown as KeyboardEvent).code) {
76
+ case 'ArrowRight':
77
+ if (orientation === 'horizontal') {
78
+ e.preventDefault();
79
+ next();
80
+ }
81
+ break;
82
+ case 'ArrowLeft':
83
+ if (orientation === 'horizontal') {
84
+ e.preventDefault();
85
+ previous();
86
+ }
87
+ break;
88
+ case 'ArrowDown':
89
+ if (orientation === 'vertical') {
90
+ e.preventDefault();
91
+ next();
92
+ }
93
+ break;
94
+ case 'ArrowUp':
95
+ if (orientation === 'vertical') {
96
+ e.preventDefault();
97
+ previous();
98
+ }
99
+ break;
100
+ case 'Enter':
101
+ case 'Space':
102
+ e.preventDefault();
103
+ getCurrentOption().dispatchEvent(new Event('click'));
104
+ break;
105
+ default:
106
+ break;
107
+ }
108
+ };
109
+
110
+ return {
111
+ 'aria-activedescendant': '',
112
+ 'aria-multiselectable': 'true',
113
+ 'aria-orientation': orientation,
114
+ role: 'listbox',
115
+ tabIndex: 0,
116
+ onFocus: focus,
117
+ onBlur: blur,
118
+ onMouseDown: (event: MouseEvent) => event.preventDefault(),
119
+ onMouseLeave: blur,
120
+ onKeyDown: keydown,
121
+ };
122
+ }
123
+ }, [
124
+ ref,
125
+ ref.current,
126
+ ndxGroup,
127
+ JSON.parse(JSON.stringify(ndxGroup?.all() || '')),
128
+ ]);
129
+
130
+ return props;
131
+ };
package/src/index.ts ADDED
@@ -0,0 +1,8 @@
1
+ export * from './types';
2
+ export * from './utils';
3
+
4
+ export * from './components/ChartEmptyState';
5
+ export * from './components/Dashboard';
6
+ export * from './components/PdfExportButton';
7
+ export * from './components/TimeSeriesBarChart';
8
+ export * from './components/WithFilters';
package/src/types.ts ADDED
@@ -0,0 +1,39 @@
1
+ import {
2
+ ComparableObject,
3
+ Crossfilter,
4
+ Dimension as CrossfilterDimension,
5
+ Group as CrossfilterGroup,
6
+ NaturallyOrderedValue,
7
+ OrderedValueSelector,
8
+ } from 'crossfilter2';
9
+
10
+ export { ChartSize, ColorTheme } from '@redsift/charts';
11
+
12
+ export type JSONValue =
13
+ | string
14
+ | number
15
+ | boolean
16
+ | { [x: string]: JSONValue }
17
+ | Array<JSONValue>
18
+ | undefined
19
+ | null;
20
+
21
+ export type JSONObject = { [x: string]: JSONValue };
22
+
23
+ export type JSONArray = Array<JSONObject>;
24
+
25
+ export type TNdx = Crossfilter<any>;
26
+
27
+ export type Group = CrossfilterGroup<any, NaturallyOrderedValue, unknown>;
28
+
29
+ export type GroupSelector = (group: Group) => Group;
30
+
31
+ export type Dimension = CrossfilterDimension<
32
+ any,
33
+ string | number | boolean | ComparableObject
34
+ >;
35
+
36
+ export type DimensionSelector = OrderedValueSelector<
37
+ any,
38
+ NaturallyOrderedValue | NaturallyOrderedValue[]
39
+ >;
@@ -0,0 +1,5 @@
1
+ import { Dimension } from '../../types';
2
+
3
+ const groupReduceCount = (d: Dimension) => d.group().reduceCount();
4
+
5
+ export const COUNT = groupReduceCount;
@@ -0,0 +1,6 @@
1
+ import { Dimension } from '../../types';
2
+
3
+ export const groupReduceSum = (field: string) => (d: Dimension) =>
4
+ d.group().reduceSum((d) => d[field]);
5
+
6
+ export const SUM = groupReduceSum;
@@ -0,0 +1,2 @@
1
+ export * from './groupReduceCount';
2
+ export * from './groupReduceSum';
@@ -0,0 +1 @@
1
+ export * from './groupReducers';
package/tsconfig.json ADDED
@@ -0,0 +1,3 @@
1
+ {
2
+ "extends": "../../tsconfig"
3
+ }
File without changes
File without changes
File without changes
File without changes