Skip to content

Commit 2da60cb

Browse files
authored
refactor: Refactors options merging in chart core (#132)
1 parent 724e5a0 commit 2da60cb

File tree

1 file changed

+100
-97
lines changed

1 file changed

+100
-97
lines changed

src/core/chart-core.tsx

Lines changed: 100 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -121,9 +121,70 @@ export function InternalCoreChart({
121121
const inverted = !!options.chart?.inverted;
122122
const isRtl = getIsRtl(rootRef?.current);
123123

124-
// Compute RTL-adjusted options that will be used for both legend positioning and chart rendering
125-
const rtlAdjustedOptions: Highcharts.Options = {
124+
// The Highcharts options takes all provided Highcharts options and custom properties and merges them together, so that
125+
// the Cloudscape features and custom Highcharts extensions co-exist.
126+
// For certain options we provide Cloudscape styling, but in all cases this can be explicitly overridden.
127+
options = {
126128
...options,
129+
// Hide credits by default.
130+
credits: options.credits ?? { enabled: false },
131+
// Hide chart title by default.
132+
title: options.title ?? { text: "" },
133+
// Use Cloudscape color palette by default.
134+
// This cannot be reset to Highcharts' default from the outside, but it is possible to provide custom palette.
135+
colors: options.colors ?? Styles.colors,
136+
chart: {
137+
// Animations are disabled by default.
138+
// This is done for UX and a11y reasons as Highcharts animations do not match our animation timings, and do
139+
// not respect disabled motion settings. These issues can likely be resolved or we can provide custom animations
140+
// instead, but this is not a priority.
141+
animation: false,
142+
...Styles.chart,
143+
...options.chart,
144+
className: clsx(testClasses["chart-plot"], options.chart?.className),
145+
// The debug errors are enabled by default in development mode, but this only works
146+
// if the Highcharts debugger module is loaded.
147+
displayErrors: options.chart?.displayErrors ?? isDevelopment,
148+
style: { ...Styles.chartPlotCss, ...options.chart?.style },
149+
},
150+
series: options.series,
151+
// Highcharts legend is disabled by default in favour of the custom Cloudscape legend.
152+
legend: options.legend ?? { enabled: false },
153+
lang: {
154+
...options.lang,
155+
accessibility: {
156+
// The default chart title is disabled by default to prevent the default "Chart" in the screen-reader detail.
157+
defaultChartTitle: "",
158+
chartContainerLabel: labels.chartLabel,
159+
svgContainerLabel: labels.chartContainerLabel,
160+
// We used {names[0]} to inject the axis title, see: https://api.highcharts.com/highcharts/lang.accessibility.axis.xAxisDescriptionSingular.
161+
axis: {
162+
xAxisDescriptionSingular: labels.chartXAxisLabel ? `${labels.chartXAxisLabel}, {names[0]}` : undefined,
163+
yAxisDescriptionSingular: labels.chartYAxisLabel ? `${labels.chartYAxisLabel}, {names[0]}` : undefined,
164+
},
165+
...options.lang?.accessibility,
166+
},
167+
},
168+
accessibility: {
169+
description: labels.chartDescription,
170+
screenReaderSection: {
171+
// We use custom before-message to remove detail that are currently not supported with our i18n.
172+
// See: https://api.highcharts.com/highcharts/accessibility.screenReaderSection.beforeChartFormat.
173+
beforeChartFormat: "<div>{xAxisDescription}</div><div>{yAxisDescription}</div>",
174+
// We hide after message as it is currently not supported with our i18n.
175+
afterChartFormat: "",
176+
},
177+
...options.accessibility,
178+
// Highcharts keyboard navigation is disabled by default in favour of the custom Cloudscape navigation.
179+
keyboardNavigation: options.accessibility?.keyboardNavigation ?? { enabled: !keyboardNavigation },
180+
point: {
181+
// Point description formatter is overridden to respect custom axes value formatters.
182+
descriptionFormatter: (point) => getPointAccessibleDescription(point, labels),
183+
...options.accessibility?.point,
184+
},
185+
},
186+
// We use the rtl adjusted axes (instead of the original options.xAxis/yAxis) to ensure the chart renders
187+
// with the correct axis orientation for RTL layouts, matching what was used for legend positioning above.
127188
xAxis: castArray(options.xAxis)?.map((xAxisOptions) => ({
128189
...Styles.xAxisOptions,
129190
...xAxisOptions,
@@ -148,48 +209,59 @@ export function InternalCoreChart({
148209
// correspond the order of stacks.
149210
reversedStacks: yAxisOptions.reversedStacks ?? true,
150211
})),
212+
plotOptions: {
213+
...options.plotOptions,
214+
series: {
215+
// Animations are disabled by default, same as in the options.chart.
216+
animation: false,
217+
// Sticky tracking is disabled by default due to sub-optimal UX in dense charts.
218+
stickyTracking: false,
219+
borderColor: Styles.seriesBorderColor,
220+
...options.plotOptions?.series,
221+
dataLabels: { style: Styles.seriesDataLabelsCss, ...options.plotOptions?.series?.dataLabels },
222+
states: {
223+
...options.plotOptions?.series?.states,
224+
inactive: { opacity: Styles.seriesOpacityInactive, ...options.plotOptions?.series?.states?.inactive },
225+
},
226+
},
227+
area: { ...Styles.areaSeries, ...options.plotOptions?.area },
228+
areaspline: { ...Styles.areaSeries, ...options.plotOptions?.areaspline },
229+
arearange: { ...Styles.areaSeries, ...options.plotOptions?.arearange },
230+
areasplinerange: { ...Styles.areaSeries, ...options.plotOptions?.areasplinerange },
231+
line: { ...Styles.lineSeries, ...options.plotOptions?.line },
232+
spline: { ...Styles.lineSeries, ...options.plotOptions?.spline },
233+
column: { ...Styles.columnSeries, ...options.plotOptions?.column },
234+
errorbar: { ...Styles.errorbarSeries, ...options.plotOptions?.errorbar },
235+
// Pie chart is shown in legend by default, that is required for standalone pie charts.
236+
pie: {
237+
showInLegend: true,
238+
...Styles.pieSeries,
239+
...options.plotOptions?.pie,
240+
dataLabels: { ...Styles.pieSeriesDataLabels, ...options.plotOptions?.pie?.dataLabels },
241+
},
242+
},
243+
// We don't use Highcharts tooltip, but certain tooltip options such as tooltip.snap or tooltip.shared
244+
// affect the hovering behavior of Highcharts. That is only the case when the tooltip is not disabled,
245+
// so we render it, but hide with styles.
246+
tooltip: options.tooltip ?? { enabled: true, snap: Styles.tooltipSnap, style: { opacity: 0 } },
151247
};
152248

153-
const legendProps = getLegendsProps(rtlAdjustedOptions, legendOptions);
249+
const legendProps = getLegendsProps(options, legendOptions);
154250

155251
return (
156252
<div {...rootProps}>
157253
<ChartContainer
158254
{...containerProps}
159255
chart={(height) => {
160-
// The Highcharts options takes all provided Highcharts options and custom properties and merges them together, so that
161-
// the Cloudscape features and custom Highcharts extensions co-exist.
162-
// For certain options we provide Cloudscape styling, but in all cases this can be explicitly overridden.
163-
// We use the rtl adjusted axes (instead of the original options.xAxis/yAxis) to ensure the chart renders
164-
// with the correct axis orientation for RTL layouts, matching what was used for legend positioning above.
256+
// We declare event handlers here to ensure that no event fires until the chart is initialized.
165257
const highchartsOptions: Highcharts.Options = {
166258
...options,
167-
xAxis: rtlAdjustedOptions.xAxis,
168-
yAxis: rtlAdjustedOptions.yAxis,
169-
// Hide credits by default.
170-
credits: options.credits ?? { enabled: false },
171-
// Hide chart title by default.
172-
title: options.title ?? { text: "" },
173-
// Use Cloudscape color palette by default.
174-
// This cannot be reset to Highcharts' default from the outside, but it is possible to provide custom palette.
175-
colors: options.colors ?? Styles.colors,
176259
chart: {
177-
// Animations are disabled by default.
178-
// This is done for UX and a11y reasons as Highcharts animations do not match our animation timings, and do
179-
// not respect disabled motion settings. These issues can likely be resolved or we can provide custom animations
180-
// instead, but this is not a priority.
181-
animation: false,
182-
...Styles.chart,
183260
...options.chart,
184-
className: clsx(testClasses["chart-plot"], options.chart?.className),
185261
// Use height computed from chartHeight, chartMinHeight, and fitHeight settings by default.
186262
// It is possible to override it by explicitly providing chart.height, but this will not be
187263
// compatible with any of the above settings.
188264
height: options.chart?.height ?? height,
189-
// The debug errors are enabled by default in development mode, but this only works
190-
// if the Highcharts debugger module is loaded.
191-
displayErrors: options.chart?.displayErrors ?? isDevelopment,
192-
style: { ...Styles.chartPlotCss, ...options.chart?.style },
193265
// We override certain chart events to inject additional behaviors, but it is still possible to define
194266
// custom callbacks. The Cloudscape behaviors can be disabled or altered via components API. For instance,
195267
// if no-data props are not provided - the related on-render computations will be skipped.
@@ -209,60 +281,10 @@ export function InternalCoreChart({
209281
},
210282
},
211283
},
212-
series: options.series,
213-
// Highcharts legend is disabled by default in favour of the custom Cloudscape legend.
214-
legend: options.legend ?? { enabled: false },
215-
lang: {
216-
...options.lang,
217-
accessibility: {
218-
// The default chart title is disabled by default to prevent the default "Chart" in the screen-reader detail.
219-
defaultChartTitle: "",
220-
chartContainerLabel: labels.chartLabel,
221-
svgContainerLabel: labels.chartContainerLabel,
222-
// We used {names[0]} to inject the axis title, see: https://api.highcharts.com/highcharts/lang.accessibility.axis.xAxisDescriptionSingular.
223-
axis: {
224-
xAxisDescriptionSingular: labels.chartXAxisLabel
225-
? `${labels.chartXAxisLabel}, {names[0]}`
226-
: undefined,
227-
yAxisDescriptionSingular: labels.chartYAxisLabel
228-
? `${labels.chartYAxisLabel}, {names[0]}`
229-
: undefined,
230-
},
231-
...options.lang?.accessibility,
232-
},
233-
},
234-
accessibility: {
235-
description: labels.chartDescription,
236-
screenReaderSection: {
237-
// We use custom before-message to remove detail that are currently not supported with our i18n.
238-
// See: https://api.highcharts.com/highcharts/accessibility.screenReaderSection.beforeChartFormat.
239-
beforeChartFormat: "<div>{xAxisDescription}</div><div>{yAxisDescription}</div>",
240-
// We hide after message as it is currently not supported with our i18n.
241-
afterChartFormat: "",
242-
},
243-
...options.accessibility,
244-
// Highcharts keyboard navigation is disabled by default in favour of the custom Cloudscape navigation.
245-
keyboardNavigation: options.accessibility?.keyboardNavigation ?? { enabled: !keyboardNavigation },
246-
point: {
247-
// Point description formatter is overridden to respect custom axes value formatters.
248-
descriptionFormatter: (point) => getPointAccessibleDescription(point, labels),
249-
...options.accessibility?.point,
250-
},
251-
},
252284
plotOptions: {
253285
...options.plotOptions,
254286
series: {
255-
// Animations are disabled by default, same as in the options.chart.
256-
animation: false,
257-
// Sticky tracking is disabled by default due to sub-optimal UX in dense charts.
258-
stickyTracking: false,
259-
borderColor: Styles.seriesBorderColor,
260287
...options.plotOptions?.series,
261-
dataLabels: { style: Styles.seriesDataLabelsCss, ...options.plotOptions?.series?.dataLabels },
262-
states: {
263-
...options.plotOptions?.series?.states,
264-
inactive: { opacity: Styles.seriesOpacityInactive, ...options.plotOptions?.series?.states?.inactive },
265-
},
266288
// We override certain point events to inject additional behaviors, but it is still possible to define
267289
// custom callbacks. The Cloudscape behaviors can be disabled or altered via components API.
268290
point: {
@@ -284,26 +306,7 @@ export function InternalCoreChart({
284306
},
285307
},
286308
},
287-
area: { ...Styles.areaSeries, ...options.plotOptions?.area },
288-
areaspline: { ...Styles.areaSeries, ...options.plotOptions?.areaspline },
289-
arearange: { ...Styles.areaSeries, ...options.plotOptions?.arearange },
290-
areasplinerange: { ...Styles.areaSeries, ...options.plotOptions?.areasplinerange },
291-
line: { ...Styles.lineSeries, ...options.plotOptions?.line },
292-
spline: { ...Styles.lineSeries, ...options.plotOptions?.spline },
293-
column: { ...Styles.columnSeries, ...options.plotOptions?.column },
294-
errorbar: { ...Styles.errorbarSeries, ...options.plotOptions?.errorbar },
295-
// Pie chart is shown in legend by default, that is required for standalone pie charts.
296-
pie: {
297-
showInLegend: true,
298-
...Styles.pieSeries,
299-
...options.plotOptions?.pie,
300-
dataLabels: { ...Styles.pieSeriesDataLabels, ...options.plotOptions?.pie?.dataLabels },
301-
},
302309
},
303-
// We don't use Highcharts tooltip, but certain tooltip options such as tooltip.snap or tooltip.shared
304-
// affect the hovering behavior of Highcharts. That is only the case when the tooltip is not disabled,
305-
// so we render it, but hide with styles.
306-
tooltip: options.tooltip ?? { enabled: true, snap: Styles.tooltipSnap, style: { opacity: 0 } },
307310
};
308311
return (
309312
<>

0 commit comments

Comments
 (0)