@cdc/chart 4.24.7 → 4.24.9
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.
- package/dist/cdcchart.js +40313 -37543
- package/examples/cases-year.json +13379 -0
- package/examples/gallery/bar-chart-vertical/combo-line-chart.json +76 -15
- package/examples/gallery/bar-chart-vertical/vertical-bar-chart-stacked.json +5 -5
- package/index.html +17 -8
- package/package.json +2 -2
- package/src/CdcChart.tsx +383 -133
- package/src/_stories/Chart.Legend.Gradient.tsx +19 -0
- package/src/_stories/_mock/legend.gradient_mock.json +236 -0
- package/src/components/Annotations/components/AnnotationDraggable.tsx +64 -11
- package/src/components/Axis/Categorical.Axis.tsx +145 -0
- package/src/components/BarChart/components/BarChart.Horizontal.tsx +4 -3
- package/src/components/BarChart/components/BarChart.StackedHorizontal.tsx +1 -1
- package/src/components/BarChart/components/BarChart.StackedVertical.tsx +2 -5
- package/src/components/BarChart/components/BarChart.Vertical.tsx +17 -8
- package/src/components/BarChart/helpers/index.ts +5 -16
- package/src/components/BrushChart.tsx +205 -0
- package/src/components/EditorPanel/EditorPanel.tsx +1766 -509
- package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +19 -5
- package/src/components/EditorPanel/components/Panels/Panel.General.tsx +190 -37
- package/src/components/EditorPanel/components/Panels/Panel.Sankey.tsx +43 -7
- package/src/components/EditorPanel/components/Panels/Panel.Series.tsx +4 -4
- package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +1 -11
- package/src/components/EditorPanel/editor-panel.scss +16 -3
- package/src/components/EditorPanel/{useEditorPermissions.js → useEditorPermissions.ts} +90 -19
- package/src/components/Legend/Legend.Component.tsx +185 -193
- package/src/components/Legend/Legend.Suppression.tsx +146 -0
- package/src/components/Legend/Legend.tsx +21 -5
- package/src/components/Legend/helpers/index.ts +33 -3
- package/src/components/LegendWrapper.tsx +26 -0
- package/src/components/LineChart/LineChartProps.ts +1 -18
- package/src/components/LineChart/components/LineChart.BumpCircle.tsx +103 -0
- package/src/components/LineChart/components/LineChart.Circle.tsx +47 -8
- package/src/components/LineChart/helpers.ts +55 -11
- package/src/components/LineChart/index.tsx +113 -38
- package/src/components/LinearChart.tsx +1366 -0
- package/src/components/PieChart/PieChart.tsx +74 -17
- package/src/components/Sankey/index.tsx +22 -16
- package/src/components/Sparkline/components/SparkLine.tsx +2 -2
- package/src/data/initial-state.js +13 -3
- package/src/hooks/useLegendClasses.ts +52 -15
- package/src/hooks/useMinMax.ts +4 -4
- package/src/hooks/useScales.ts +34 -24
- package/src/hooks/useTooltip.tsx +85 -22
- package/src/scss/DataTable.scss +2 -1
- package/src/scss/main.scss +107 -14
- package/src/types/ChartConfig.ts +34 -8
- package/src/types/ChartContext.ts +5 -4
- package/examples/feature/line/line-chart.json +0 -449
- package/src/components/BrushHandle.jsx +0 -17
- package/src/components/LineChart/index.scss +0 -1
|
@@ -33,7 +33,17 @@ type TooltipData = {
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
const PieChart = props => {
|
|
36
|
-
const {
|
|
36
|
+
const {
|
|
37
|
+
transformedData: data,
|
|
38
|
+
config,
|
|
39
|
+
colorScale,
|
|
40
|
+
currentViewport,
|
|
41
|
+
dimensions,
|
|
42
|
+
highlight,
|
|
43
|
+
highlightReset,
|
|
44
|
+
seriesHighlight,
|
|
45
|
+
isDraggingAnnotation
|
|
46
|
+
} = useContext(ConfigContext)
|
|
37
47
|
const { tooltipData, showTooltip, hideTooltip, tooltipOpen, tooltipLeft, tooltipTop } = useTooltip<TooltipData>()
|
|
38
48
|
const { handleTooltipMouseOver, handleTooltipMouseOff, TooltipListItem } = useCoveTooltip({
|
|
39
49
|
xScale: false,
|
|
@@ -135,7 +145,18 @@ const PieChart = props => {
|
|
|
135
145
|
<>
|
|
136
146
|
{transitions.map(({ item: arc, props, key }, animatedPieIndex) => {
|
|
137
147
|
return (
|
|
138
|
-
<Group
|
|
148
|
+
<Group
|
|
149
|
+
className={arc.data[config.xAxis.dataKey]}
|
|
150
|
+
key={`${key}-${animatedPieIndex}`}
|
|
151
|
+
style={{
|
|
152
|
+
opacity:
|
|
153
|
+
config.legend.behavior === 'highlight' &&
|
|
154
|
+
seriesHighlight.length > 0 &&
|
|
155
|
+
seriesHighlight.indexOf(arc.data[config.runtime.xAxis.dataKey]) === -1
|
|
156
|
+
? 0.5
|
|
157
|
+
: 1
|
|
158
|
+
}}
|
|
159
|
+
>
|
|
139
160
|
<animated.path
|
|
140
161
|
d={interpolate([props.startAngle, props.endAngle], (startAngle, endAngle) =>
|
|
141
162
|
path({
|
|
@@ -163,7 +184,14 @@ const PieChart = props => {
|
|
|
163
184
|
return (
|
|
164
185
|
<animated.g key={`${key}${i}`}>
|
|
165
186
|
{hasSpaceForLabel && (
|
|
166
|
-
<Text
|
|
187
|
+
<Text
|
|
188
|
+
style={{ fill: textColor }}
|
|
189
|
+
x={centroidX}
|
|
190
|
+
y={centroidY}
|
|
191
|
+
dy='.33em'
|
|
192
|
+
textAnchor='middle'
|
|
193
|
+
pointerEvents='none'
|
|
194
|
+
>
|
|
167
195
|
{Math.round((((arc.endAngle - arc.startAngle) * 180) / Math.PI / 360) * 100) + '%'}
|
|
168
196
|
</Text>
|
|
169
197
|
)}
|
|
@@ -174,17 +202,18 @@ const PieChart = props => {
|
|
|
174
202
|
)
|
|
175
203
|
}
|
|
176
204
|
|
|
177
|
-
let
|
|
205
|
+
let chartWidth = props.parentWidth
|
|
206
|
+
let width = props.parentWidth
|
|
178
207
|
|
|
179
208
|
if (config && config.legend && !config.legend.hide && currentViewport === 'lg') {
|
|
180
|
-
width =
|
|
209
|
+
width = Number(chartWidth) * 0.73
|
|
181
210
|
}
|
|
182
211
|
|
|
183
212
|
const height = config.heights.vertical
|
|
184
213
|
|
|
185
214
|
const radius = Math.min(width, height) / 2
|
|
186
215
|
const centerY = height / 2
|
|
187
|
-
const centerX =
|
|
216
|
+
const centerX = props.parentWidth / 2
|
|
188
217
|
const donutThickness = config.pieType === 'Donut' ? 75 : radius
|
|
189
218
|
|
|
190
219
|
useEffect(() => {
|
|
@@ -205,10 +234,24 @@ const PieChart = props => {
|
|
|
205
234
|
|
|
206
235
|
const createLegendLabels = createFormatLabels(config, [], _data, _colorScale)
|
|
207
236
|
|
|
237
|
+
const getSvgClasses = () => {
|
|
238
|
+
let classes = ['animated-pie', 'group']
|
|
239
|
+
if (config.animate === false || animatedPie) {
|
|
240
|
+
classes.push('animated')
|
|
241
|
+
}
|
|
242
|
+
return classes.join(' ')
|
|
243
|
+
}
|
|
244
|
+
|
|
208
245
|
return (
|
|
209
246
|
<>
|
|
210
247
|
<ErrorBoundary component='PieChart'>
|
|
211
|
-
<svg
|
|
248
|
+
<svg
|
|
249
|
+
width={radius * 2}
|
|
250
|
+
height={height}
|
|
251
|
+
className={getSvgClasses()}
|
|
252
|
+
role='img'
|
|
253
|
+
aria-label={handleChartAriaLabels(config)}
|
|
254
|
+
>
|
|
212
255
|
<Group top={centerY} left={radius}>
|
|
213
256
|
{/* prettier-ignore */}
|
|
214
257
|
<Pie
|
|
@@ -223,17 +266,31 @@ const PieChart = props => {
|
|
|
223
266
|
</Group>
|
|
224
267
|
</svg>
|
|
225
268
|
<div ref={triggerRef} />
|
|
226
|
-
{!isDraggingAnnotation &&
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
269
|
+
{!isDraggingAnnotation &&
|
|
270
|
+
tooltipData &&
|
|
271
|
+
Object.entries(tooltipData.data).length > 0 &&
|
|
272
|
+
tooltipOpen &&
|
|
273
|
+
showTooltip &&
|
|
274
|
+
tooltipData.dataYPosition &&
|
|
275
|
+
tooltipData.dataXPosition && (
|
|
276
|
+
<>
|
|
277
|
+
<style>{`.tooltip {background-color: rgba(255,255,255, ${
|
|
278
|
+
config.tooltips.opacity / 100
|
|
279
|
+
}) !important`}</style>
|
|
280
|
+
<TooltipWithBounds
|
|
281
|
+
key={Math.random()}
|
|
282
|
+
className={'tooltip cdc-open-viz-module'}
|
|
283
|
+
left={tooltipLeft}
|
|
284
|
+
top={tooltipTop}
|
|
285
|
+
>
|
|
286
|
+
<ul>
|
|
287
|
+
{typeof tooltipData === 'object' &&
|
|
288
|
+
Object.entries(tooltipData.data).map((item, index) => <TooltipListItem item={item} key={index} />)}
|
|
289
|
+
</ul>
|
|
290
|
+
</TooltipWithBounds>
|
|
291
|
+
</>
|
|
292
|
+
)}
|
|
235
293
|
</ErrorBoundary>
|
|
236
|
-
<LegendComponent config={config} colorScale={_colorScale} seriesHighlight={seriesHighlight} highlight={highlight} highlightReset={highlightReset} currentViewport={currentViewport} formatLabels={createLegendLabels} />
|
|
237
294
|
</>
|
|
238
295
|
)
|
|
239
296
|
}
|
|
@@ -28,17 +28,23 @@ const Sankey = ({ width, height, runtime }: SankeyProps) => {
|
|
|
28
28
|
|
|
29
29
|
//Tooltip
|
|
30
30
|
const [tooltipID, setTooltipID] = useState<string>('')
|
|
31
|
+
//Mobile Pop Up
|
|
32
|
+
const [showPopup, setShowPopup] = useState(false)
|
|
31
33
|
|
|
32
34
|
const handleNodeClick = (nodeId: string) => {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
+
// Store the previous tooltipID
|
|
36
|
+
const previousTooltipID = tooltipID
|
|
35
37
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
38
|
+
// If the previous tooltipID exists, clear it
|
|
39
|
+
if (previousTooltipID) {
|
|
40
|
+
setTooltipID('')
|
|
41
|
+
}
|
|
39
42
|
|
|
40
|
-
|
|
41
|
-
|
|
43
|
+
// Update the tooltipID with the new nodeId if it's different from the previous one
|
|
44
|
+
if (previousTooltipID !== nodeId) {
|
|
45
|
+
setTooltipID(nodeId)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
42
48
|
|
|
43
49
|
useEffect(() => {
|
|
44
50
|
if (window.innerWidth < 768 && window.innerHeight > window.innerWidth) {
|
|
@@ -209,7 +215,7 @@ const Sankey = ({ width, height, runtime }: SankeyProps) => {
|
|
|
209
215
|
fillOpacity={opacityValue}
|
|
210
216
|
rx={sankeyConfig.rxValue}
|
|
211
217
|
// todo: move enable tooltips to sankey
|
|
212
|
-
data-tooltip-html={data.tooltips && config.enableTooltips ? sankeyToolTip : null}
|
|
218
|
+
data-tooltip-html={data.tooltips && config.enableTooltips && tooltipID !== '' ? sankeyToolTip : null}
|
|
213
219
|
data-tooltip-id={`cdc-open-viz-tooltip-${runtime.uniqueId}-sankey`}
|
|
214
220
|
onClick={() => handleNodeClick(node.id)}
|
|
215
221
|
style={{ pointerEvents: 'visible', cursor: 'pointer' }}
|
|
@@ -240,7 +246,7 @@ const Sankey = ({ width, height, runtime }: SankeyProps) => {
|
|
|
240
246
|
className='node-text'
|
|
241
247
|
style={{ pointerEvents: 'auto', cursor: 'pointer' }} // Enable pointer events
|
|
242
248
|
onClick={() => handleNodeClick(node.id)}
|
|
243
|
-
data-tooltip-html={data.tooltips && config.enableTooltips ? sankeyToolTip : null}
|
|
249
|
+
data-tooltip-html={data.tooltips && config.enableTooltips && tooltipID !== '' ? sankeyToolTip : null}
|
|
244
250
|
data-tooltip-id={`cdc-open-viz-tooltip-${runtime.uniqueId}-sankey`}
|
|
245
251
|
>
|
|
246
252
|
{(data?.storyNodeText?.find(storyNode => storyNode.StoryNode === node.id) || {}).segmentTextBefore}
|
|
@@ -255,7 +261,7 @@ const Sankey = ({ width, height, runtime }: SankeyProps) => {
|
|
|
255
261
|
textAnchor='start'
|
|
256
262
|
style={{ pointerEvents: 'auto', cursor: 'pointer' }} // Enable pointer events
|
|
257
263
|
onClick={() => handleNodeClick(node.id)}
|
|
258
|
-
data-tooltip-html={data.tooltips && config.enableTooltips ? sankeyToolTip : null}
|
|
264
|
+
data-tooltip-html={data.tooltips && config.enableTooltips && tooltipID !== '' ? sankeyToolTip : null}
|
|
259
265
|
data-tooltip-id={`cdc-open-viz-tooltip-${runtime.uniqueId}-sankey`}
|
|
260
266
|
>
|
|
261
267
|
{typeof node.value === 'number' ? node.value.toLocaleString() : node.value}
|
|
@@ -271,7 +277,7 @@ const Sankey = ({ width, height, runtime }: SankeyProps) => {
|
|
|
271
277
|
verticalAnchor='end'
|
|
272
278
|
style={{ pointerEvents: 'auto', cursor: 'pointer' }} // Enable pointer events
|
|
273
279
|
onClick={() => handleNodeClick(node.id)}
|
|
274
|
-
data-tooltip-html={data.tooltips && config.enableTooltips ? sankeyToolTip : null}
|
|
280
|
+
data-tooltip-html={data.tooltips && config.enableTooltips && tooltipID !== '' ? sankeyToolTip : null}
|
|
275
281
|
data-tooltip-id={`cdc-open-viz-tooltip-${runtime.uniqueId}-sankey`}
|
|
276
282
|
>
|
|
277
283
|
{(data?.storyNodeText?.find(storyNode => storyNode.StoryNode === node.id) || {}).segmentTextAfter}
|
|
@@ -282,7 +288,7 @@ const Sankey = ({ width, height, runtime }: SankeyProps) => {
|
|
|
282
288
|
<Text
|
|
283
289
|
style={{ pointerEvents: 'auto', cursor: 'pointer' }} // Enable pointer events
|
|
284
290
|
onClick={() => handleNodeClick(node.id)}
|
|
285
|
-
data-tooltip-html={data.tooltips && config.enableTooltips ? sankeyToolTip : null}
|
|
291
|
+
data-tooltip-html={data.tooltips && config.enableTooltips && tooltipID !== '' ? sankeyToolTip : null}
|
|
286
292
|
data-tooltip-id={`cdc-open-viz-tooltip-${runtime.uniqueId}-sankey`}
|
|
287
293
|
x={node.x0! + textPositionHorizontal}
|
|
288
294
|
y={(node.y1! + node.y0!) / 2 + textPositionVertical}
|
|
@@ -304,7 +310,7 @@ const Sankey = ({ width, height, runtime }: SankeyProps) => {
|
|
|
304
310
|
textAnchor='start'
|
|
305
311
|
style={{ pointerEvents: 'auto', cursor: 'pointer' }} // Enable pointer events
|
|
306
312
|
onClick={() => handleNodeClick(node.id)}
|
|
307
|
-
data-tooltip-html={data.tooltips && config.enableTooltips ? sankeyToolTip : null}
|
|
313
|
+
data-tooltip-html={data.tooltips && config.enableTooltips && tooltipID !== '' ? sankeyToolTip : null}
|
|
308
314
|
data-tooltip-id={`cdc-open-viz-tooltip-${runtime.uniqueId}-sankey`}
|
|
309
315
|
>
|
|
310
316
|
<tspan className={classStyle}>{sankeyConfig.nodeValueStyle.textBefore + (typeof node.value === 'number' ? node.value.toLocaleString() : node.value) + sankeyConfig.nodeValueStyle.textAfter}</tspan>
|
|
@@ -339,7 +345,7 @@ const Sankey = ({ width, height, runtime }: SankeyProps) => {
|
|
|
339
345
|
strokeWidth={link.width! + 2}
|
|
340
346
|
style={{ pointerEvents: 'auto', cursor: 'pointer' }} // Enable pointer events
|
|
341
347
|
onClick={() => handleNodeClick(link.target.id || null)}
|
|
342
|
-
data-tooltip-html={data.tooltips && config.enableTooltips ? sankeyToolTip : null}
|
|
348
|
+
data-tooltip-html={data.tooltips && config.enableTooltips && tooltipID !== '' ? sankeyToolTip : null}
|
|
343
349
|
data-tooltip-id={`cdc-open-viz-tooltip-${runtime.uniqueId}-sankey`}
|
|
344
350
|
/>
|
|
345
351
|
)
|
|
@@ -376,7 +382,7 @@ const Sankey = ({ width, height, runtime }: SankeyProps) => {
|
|
|
376
382
|
fillOpacity={opacityValue}
|
|
377
383
|
rx={sankeyConfig.rxValue}
|
|
378
384
|
// todo: move enable tooltips to sankey
|
|
379
|
-
data-tooltip-html={data.tooltips && config.enableTooltips ? sankeyToolTip : null}
|
|
385
|
+
data-tooltip-html={data.tooltips && config.enableTooltips && tooltipID !== '' ? sankeyToolTip : null}
|
|
380
386
|
data-tooltip-id={`tooltip`}
|
|
381
387
|
onClick={() => handleNodeClick(node.id)}
|
|
382
388
|
style={{ pointerEvents: 'visible', cursor: 'pointer' }}
|
|
@@ -467,7 +473,7 @@ const Sankey = ({ width, height, runtime }: SankeyProps) => {
|
|
|
467
473
|
|
|
468
474
|
{/* ReactTooltip needs to remain even if tooltips are disabled -- it handles when a user clicks off of the node and resets
|
|
469
475
|
the sankey diagram. When tooltips are disabled this will nothing */}
|
|
470
|
-
<ReactTooltip id={`cdc-open-viz-tooltip-${runtime.uniqueId}-sankey`} afterHide={() =>
|
|
476
|
+
<ReactTooltip id={`cdc-open-viz-tooltip-${runtime.uniqueId}-sankey`} afterHide={() => setTooltipID('')} events={['click']} place={'bottom'} style={{ backgroundColor: `rgba(238, 238, 238, 1)`, color: 'black', boxShadow: `0 3px 10px rgb(0 0 0 / 0.2)` }} />
|
|
471
477
|
{showPopup && (
|
|
472
478
|
<div className='popup'>
|
|
473
479
|
<div className='popup-content'>
|
|
@@ -107,10 +107,10 @@ const SparkLine: React.FC<SparkLineProps> = props => {
|
|
|
107
107
|
opacity={config.legend.behavior === 'highlight' && seriesHighlight.length > 0 && seriesHighlight.indexOf(seriesKey) === -1 ? 0.5 : 1}
|
|
108
108
|
display={config.legend.behavior === 'highlight' || seriesHighlight.length === 0 || seriesHighlight.indexOf(seriesKey) !== -1 ? 'block' : 'none'}
|
|
109
109
|
>
|
|
110
|
-
{data.map((d, dataIndex) => {
|
|
110
|
+
{config.labels && data.map((d, dataIndex) => {
|
|
111
111
|
return (
|
|
112
112
|
<Group key={`series-${seriesKey}-point-${dataIndex}`}>
|
|
113
|
-
<Text
|
|
113
|
+
<Text x={xScale(getXAxisData(d))} y={yScale(getYAxisData(d, seriesKey))} fill={colorScale ? colorScale(config.runtime.seriesLabels ? config.runtime.seriesLabels[seriesKey] : seriesKey) : '#000'} textAnchor='middle'>
|
|
114
114
|
{formatNumber(d[seriesKey])}
|
|
115
115
|
</Text>
|
|
116
116
|
</Group>
|
|
@@ -28,7 +28,7 @@ export default {
|
|
|
28
28
|
showDownloadButton: false,
|
|
29
29
|
showMissingDataLabel: true,
|
|
30
30
|
showSuppressedSymbol: true,
|
|
31
|
-
|
|
31
|
+
hideNullValue: true
|
|
32
32
|
},
|
|
33
33
|
padding: {
|
|
34
34
|
left: 5,
|
|
@@ -61,7 +61,8 @@ export default {
|
|
|
61
61
|
tickRotation: 0,
|
|
62
62
|
anchors: [],
|
|
63
63
|
shoMissingDataLabel: true,
|
|
64
|
-
showMissingDataLine: true
|
|
64
|
+
showMissingDataLine: true,
|
|
65
|
+
categories: []
|
|
65
66
|
},
|
|
66
67
|
boxplot: {
|
|
67
68
|
plots: [],
|
|
@@ -159,11 +160,20 @@ export default {
|
|
|
159
160
|
dynamicLegendItemLimit: 5,
|
|
160
161
|
dynamicLegendItemLimitMessage: 'Dynamic Legend Item Limit Hit.',
|
|
161
162
|
dynamicLegendChartMessage: 'Select Options from the Legend',
|
|
163
|
+
label: '',
|
|
162
164
|
lineMode: false,
|
|
163
165
|
verticalSorted: false,
|
|
164
166
|
highlightOnHover: false,
|
|
165
167
|
hideSuppressedLabels: false,
|
|
166
|
-
|
|
168
|
+
hideSuppressionLink: false,
|
|
169
|
+
seriesHighlight: [],
|
|
170
|
+
style: 'circles',
|
|
171
|
+
subStyle: 'linear blocks',
|
|
172
|
+
tickRotation: '',
|
|
173
|
+
hideBorder: {
|
|
174
|
+
side: false,
|
|
175
|
+
topBottom: true
|
|
176
|
+
}
|
|
167
177
|
},
|
|
168
178
|
brush: {
|
|
169
179
|
height: 25,
|
|
@@ -1,31 +1,68 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
type ConfigType = {
|
|
2
|
+
legend: {
|
|
3
|
+
position: 'left' | 'bottom' | 'top' | 'right'
|
|
4
|
+
singleRow?: boolean
|
|
5
|
+
reverseLabelOrder?: boolean
|
|
6
|
+
verticalSorted?: boolean
|
|
7
|
+
hideBorder: {
|
|
8
|
+
side?: boolean
|
|
9
|
+
topBottom?: boolean
|
|
10
|
+
}
|
|
8
11
|
}
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const useLegendClasses = (config: ConfigType) => {
|
|
15
|
+
const containerClasses = ['legend-container']
|
|
16
|
+
const innerClasses = ['legend-container__inner']
|
|
17
|
+
|
|
18
|
+
// Handle legend positioning
|
|
19
|
+
switch (config.legend.position) {
|
|
20
|
+
case 'left':
|
|
21
|
+
containerClasses.push('left')
|
|
22
|
+
break
|
|
23
|
+
case 'right':
|
|
24
|
+
containerClasses.push('right')
|
|
25
|
+
break
|
|
26
|
+
case 'bottom':
|
|
27
|
+
containerClasses.push('bottom')
|
|
28
|
+
innerClasses.push('double-column', 'bottom')
|
|
29
|
+
break
|
|
30
|
+
case 'top':
|
|
31
|
+
containerClasses.push('top')
|
|
32
|
+
innerClasses.push('double-column', 'top')
|
|
33
|
+
break
|
|
12
34
|
}
|
|
13
35
|
|
|
14
|
-
|
|
36
|
+
// Handle single row configuration for 'bottom' and 'top' positions
|
|
37
|
+
if (['bottom', 'top'].includes(config.legend.position) && config.legend.singleRow) {
|
|
15
38
|
innerClasses.push('single-row')
|
|
16
39
|
}
|
|
17
40
|
|
|
18
|
-
//
|
|
41
|
+
// Reverse label order
|
|
19
42
|
if (config.legend.reverseLabelOrder) {
|
|
20
|
-
innerClasses.push('d-flex')
|
|
21
|
-
innerClasses.push('flex-column-reverse')
|
|
43
|
+
innerClasses.push('d-flex', 'flex-column-reverse')
|
|
22
44
|
}
|
|
23
|
-
|
|
45
|
+
|
|
46
|
+
// Vertical sorting for 'bottom' and 'top' positions
|
|
47
|
+
if (['bottom', 'top'].includes(config.legend.position) && config.legend.verticalSorted) {
|
|
24
48
|
innerClasses.push('vertical-sorted')
|
|
25
49
|
}
|
|
26
50
|
|
|
51
|
+
// Configure border classes
|
|
52
|
+
if (
|
|
53
|
+
config.legend.hideBorder.side &&
|
|
54
|
+
(['right', 'left'].includes(config.legend.position) || !config.legend.position)
|
|
55
|
+
) {
|
|
56
|
+
containerClasses.push('no-border')
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (config.legend.hideBorder.topBottom && ['top', 'bottom'].includes(config.legend.position)) {
|
|
60
|
+
containerClasses.push('no-border')
|
|
61
|
+
}
|
|
62
|
+
|
|
27
63
|
return {
|
|
28
64
|
containerClasses,
|
|
29
65
|
innerClasses
|
|
30
66
|
}
|
|
31
67
|
}
|
|
68
|
+
export default useLegendClasses
|
package/src/hooks/useMinMax.ts
CHANGED
|
@@ -38,10 +38,10 @@ const useMinMax = ({ config, minValue, maxValue, existPositiveValue, data, isAll
|
|
|
38
38
|
const { visualizationType, series } = config
|
|
39
39
|
const { max: enteredMaxValue, min: enteredMinValue } = config.runtime.yAxis
|
|
40
40
|
const minRequiredCIPadding = 1.15 // regardless of Editor if CI data, there must be 10% padding added
|
|
41
|
-
|
|
41
|
+
const isLogarithmicAxis = config.yAxis.type === 'logarithmic'
|
|
42
42
|
// do validation bafore applying t0 charts
|
|
43
43
|
const isMaxValid = existPositiveValue ? enteredMaxValue >= maxValue : enteredMaxValue >= 0
|
|
44
|
-
const isMinValid =
|
|
44
|
+
const isMinValid = isLogarithmicAxis ? enteredMinValue >= 0 : (enteredMinValue <= 0 && minValue >= 0) || (enteredMinValue <= minValue && minValue < 0)
|
|
45
45
|
|
|
46
46
|
min = enteredMinValue && isMinValid ? enteredMinValue : minValue
|
|
47
47
|
max = enteredMaxValue && isMaxValid ? enteredMaxValue : Number.MIN_VALUE
|
|
@@ -143,7 +143,7 @@ const useMinMax = ({ config, minValue, maxValue, existPositiveValue, data, isAll
|
|
|
143
143
|
min = 0
|
|
144
144
|
}
|
|
145
145
|
if (enteredMinValue) {
|
|
146
|
-
const isMinValid =
|
|
146
|
+
const isMinValid = isLogarithmicAxis ? enteredMinValue >= 0 && enteredMinValue < minValue : enteredMinValue < minValue
|
|
147
147
|
min = enteredMinValue && isMinValid ? enteredMinValue : minValue
|
|
148
148
|
}
|
|
149
149
|
}
|
|
@@ -154,7 +154,7 @@ const useMinMax = ({ config, minValue, maxValue, existPositiveValue, data, isAll
|
|
|
154
154
|
}
|
|
155
155
|
|
|
156
156
|
if (config.visualizationType === 'Line' && !checkLineToBarGraph()) {
|
|
157
|
-
const isMinValid =
|
|
157
|
+
const isMinValid = isLogarithmicAxis ? enteredMinValue >= 0 && enteredMinValue < minValue : enteredMinValue < minValue
|
|
158
158
|
// update minValue for (0) Suppression points
|
|
159
159
|
const suppressedMinValue = tableData?.some((dataItem, index) => {
|
|
160
160
|
return config.preliminaryData?.some(pd => {
|
package/src/hooks/useScales.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { useContext } from 'react'
|
|
|
3
3
|
import ConfigContext from '../ConfigContext'
|
|
4
4
|
import { ChartConfig } from '../types/ChartConfig'
|
|
5
5
|
import { ChartContext } from '../types/ChartContext'
|
|
6
|
-
|
|
6
|
+
import * as d3 from 'd3'
|
|
7
7
|
const scaleTypes = {
|
|
8
8
|
TIME: 'time',
|
|
9
9
|
LOG: 'log',
|
|
@@ -27,12 +27,10 @@ const useScales = (properties: useScaleProps) => {
|
|
|
27
27
|
|
|
28
28
|
const { rawData, dimensions } = useContext<ChartContext>(ConfigContext)
|
|
29
29
|
|
|
30
|
-
const [screenWidth
|
|
30
|
+
const [screenWidth] = dimensions
|
|
31
31
|
const seriesDomain = config.runtime.barSeriesKeys || config.runtime.seriesKeys
|
|
32
32
|
const xAxisType = config.runtime.xAxis.type
|
|
33
33
|
const isHorizontal = config.orientation === 'horizontal'
|
|
34
|
-
const getXAxisDataKeys = d => d[config.runtime.originalXAxis.dataKey]
|
|
35
|
-
const xAxisDataKeysMapped = data.map(d => getXAxisDataKeys(d))
|
|
36
34
|
|
|
37
35
|
const { visualizationType } = config
|
|
38
36
|
|
|
@@ -43,7 +41,6 @@ const useScales = (properties: useScaleProps) => {
|
|
|
43
41
|
let g1xScale = null
|
|
44
42
|
let seriesScale = null
|
|
45
43
|
let xScaleNoPadding = null
|
|
46
|
-
let xScaleBrush = null
|
|
47
44
|
let xScaleAnnotation = scaleLinear({
|
|
48
45
|
domain: [0, 100],
|
|
49
46
|
range: [0, xMax]
|
|
@@ -52,7 +49,7 @@ const useScales = (properties: useScaleProps) => {
|
|
|
52
49
|
// handle Horizontal bars
|
|
53
50
|
if (isHorizontal) {
|
|
54
51
|
xScale = composeXScale({ min: min * 1.03, ...properties })
|
|
55
|
-
xScale.type = config.
|
|
52
|
+
xScale.type = config.yAxis.type === 'logarithmic' ? scaleTypes.LOG : scaleTypes.LINEAR
|
|
56
53
|
yScale = getYScaleFunction(xAxisType, xAxisDataMapped)
|
|
57
54
|
yScale.rangeRound([0, yMax])
|
|
58
55
|
seriesScale = composeScalePoint(seriesDomain, [0, yMax])
|
|
@@ -60,7 +57,6 @@ const useScales = (properties: useScaleProps) => {
|
|
|
60
57
|
|
|
61
58
|
// handle Vertical bars
|
|
62
59
|
if (!isHorizontal) {
|
|
63
|
-
xScaleBrush = composeScalePoint(xAxisDataKeysMapped, [0, xMax], 0.5)
|
|
64
60
|
xScale = composeScaleBand(xAxisDataMapped, [0, xMax], 1 - config.barThickness)
|
|
65
61
|
yScale = composeYScale(properties)
|
|
66
62
|
seriesScale = composeScaleBand(seriesDomain, [0, xScale.bandwidth()], 0)
|
|
@@ -73,8 +69,8 @@ const useScales = (properties: useScaleProps) => {
|
|
|
73
69
|
}
|
|
74
70
|
|
|
75
71
|
if (config.xAxis.type === 'date-time') {
|
|
76
|
-
let xAxisMin = Math.min(...xAxisDataMapped)
|
|
77
|
-
let xAxisMax = Math.max(...xAxisDataMapped)
|
|
72
|
+
let xAxisMin = Math.min(...xAxisDataMapped.map(Number))
|
|
73
|
+
let xAxisMax = Math.max(...xAxisDataMapped.map(Number))
|
|
78
74
|
xAxisMin -= (config.xAxis.padding ? config.xAxis.padding * 0.01 : 0) * (xAxisMax - xAxisMin)
|
|
79
75
|
xAxisMax += (config.xAxis.padding ? config.xAxis.padding * 0.01 : 0) * (xAxisMax - xAxisMin)
|
|
80
76
|
xScale = scaleTime({
|
|
@@ -129,7 +125,9 @@ const useScales = (properties: useScaleProps) => {
|
|
|
129
125
|
// handle Box plot
|
|
130
126
|
if (visualizationType === 'Box Plot') {
|
|
131
127
|
const allOutliers = []
|
|
132
|
-
const hasOutliers =
|
|
128
|
+
const hasOutliers =
|
|
129
|
+
config.boxplot.plots.map(b => b.columnOutliers.map(outlier => allOutliers.push(outlier))) &&
|
|
130
|
+
!config.boxplot.hideOutliers
|
|
133
131
|
|
|
134
132
|
// check if outliers are lower
|
|
135
133
|
if (hasOutliers) {
|
|
@@ -215,8 +213,11 @@ const useScales = (properties: useScaleProps) => {
|
|
|
215
213
|
if (screenWidth > 480) {
|
|
216
214
|
if (config.forestPlot.type === 'Linear') {
|
|
217
215
|
xScale = scaleLinear({
|
|
218
|
-
domain: [
|
|
219
|
-
|
|
216
|
+
domain: [
|
|
217
|
+
Math.min(...data.map(d => parseFloat(d[config.forestPlot.lower]))) - xAxisPadding,
|
|
218
|
+
Math.max(...data.map(d => parseFloat(d[config.forestPlot.upper]))) + xAxisPadding
|
|
219
|
+
],
|
|
220
|
+
range: [leftWidthOffset, Number(screenWidth) - rightWidthOffset]
|
|
220
221
|
})
|
|
221
222
|
xScale.type = scaleTypes.LINEAR
|
|
222
223
|
}
|
|
@@ -234,7 +235,10 @@ const useScales = (properties: useScaleProps) => {
|
|
|
234
235
|
} else {
|
|
235
236
|
if (config.forestPlot.type === 'Linear') {
|
|
236
237
|
xScale = scaleLinear({
|
|
237
|
-
domain: [
|
|
238
|
+
domain: [
|
|
239
|
+
Math.min(...data.map(d => parseFloat(d[config.forestPlot.lower]))) - xAxisPadding,
|
|
240
|
+
Math.max(...data.map(d => parseFloat(d[config.forestPlot.upper]))) + xAxisPadding
|
|
241
|
+
],
|
|
238
242
|
range: [leftWidthOffsetMobile, xMax - rightWidthOffsetMobile],
|
|
239
243
|
type: scaleTypes.LINEAR
|
|
240
244
|
})
|
|
@@ -255,7 +259,7 @@ const useScales = (properties: useScaleProps) => {
|
|
|
255
259
|
}
|
|
256
260
|
}
|
|
257
261
|
}
|
|
258
|
-
return { xScale, yScale, seriesScale, g1xScale, g2xScale, xScaleNoPadding,
|
|
262
|
+
return { xScale, yScale, seriesScale, g1xScale, g2xScale, xScaleNoPadding, xScaleAnnotation }
|
|
259
263
|
}
|
|
260
264
|
|
|
261
265
|
export default useScales
|
|
@@ -295,31 +299,37 @@ export const getTickValues = (xAxisDataMapped, xScale, num) => {
|
|
|
295
299
|
/// helper functions
|
|
296
300
|
const composeXScale = ({ min, max, xMax, config }) => {
|
|
297
301
|
// Adjust min value if using logarithmic scale
|
|
298
|
-
|
|
302
|
+
const isLogarithmicAxis = config.yAxis.type === 'logarithmic'
|
|
303
|
+
min = isLogarithmicAxis && min >= 0 && min < 1 ? min + 0.1 : min
|
|
299
304
|
// Select the appropriate scale function
|
|
300
|
-
const scaleFunc =
|
|
305
|
+
const scaleFunc = isLogarithmicAxis ? scaleLog : scaleLinear
|
|
301
306
|
// Return the configured scale function
|
|
302
307
|
return scaleFunc({
|
|
303
308
|
domain: [min, max],
|
|
304
309
|
range: [0, xMax],
|
|
305
|
-
nice:
|
|
306
|
-
zero:
|
|
310
|
+
nice: isLogarithmicAxis,
|
|
311
|
+
zero: isLogarithmicAxis
|
|
307
312
|
})
|
|
308
313
|
}
|
|
309
314
|
|
|
310
315
|
const composeYScale = ({ min, max, yMax, config, leftMax }) => {
|
|
311
316
|
// Adjust min value if using logarithmic scale
|
|
312
|
-
|
|
317
|
+
const isLogarithmicAxis = config.yAxis.type === 'logarithmic'
|
|
318
|
+
min = isLogarithmicAxis && min >= 0 && min < 1 ? min + 0.1 : min
|
|
313
319
|
// Select the appropriate scale function
|
|
314
|
-
const scaleFunc =
|
|
320
|
+
const scaleFunc = isLogarithmicAxis ? scaleLog : scaleLinear
|
|
315
321
|
|
|
316
322
|
if (config.visualizationType === 'Combo') max = leftMax
|
|
323
|
+
|
|
324
|
+
// If the visualization type is a bump chart then the domain and range need different values
|
|
325
|
+
const domainSet = config.visualizationType === 'Bump Chart' ? [1, max] : [min, max]
|
|
326
|
+
const yRange = config.visualizationType === 'Bump Chart' ? [30, yMax] : [yMax, 0]
|
|
317
327
|
// Return the configured scale function
|
|
318
328
|
return scaleFunc({
|
|
319
|
-
domain:
|
|
320
|
-
range:
|
|
321
|
-
nice:
|
|
322
|
-
zero:
|
|
329
|
+
domain: domainSet,
|
|
330
|
+
range: yRange,
|
|
331
|
+
nice: isLogarithmicAxis,
|
|
332
|
+
zero: isLogarithmicAxis
|
|
323
333
|
})
|
|
324
334
|
}
|
|
325
335
|
|