Skip to content

Commit

Permalink
feat(lib): Observe global theme mode changes to update graph display …
Browse files Browse the repository at this point in the history
…with new preferences
  • Loading branch information
jacques-lebourgeois committed Dec 13, 2024
1 parent 9516175 commit 0612672
Show file tree
Hide file tree
Showing 19 changed files with 272 additions and 9 deletions.
1 change: 1 addition & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ The {@link ODSChartsTheme} is used to build the ODS theme and the charts options
.manageChartResize(chart1, 'chart1')
.externalizePopover()
.externalizeLegends(chart1, 'chart1_legends')
.manageThemeObserver(chart1)
.getChartOptions()
);
</script>
Expand Down
6 changes: 5 additions & 1 deletion docs/examples/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -528,7 +528,10 @@ themeManager.externalizeLegends(myChart, '#${id}_legend');`
: ''
}
// Manage window size changed
themeManager.manageChartResize(myChart, '${chartId}');${
themeManager.manageChartResize(myChart, '${chartId}');
// Automatically manage data-bs-theme attribute change. Only needed if you want the
// chart to automatically react to the global light or dark theme change
themeManager.manageThemeObserver(myChart);${
'none' !== popoverInput
? `
// Register the externalization of the tooltip/popup
Expand Down Expand Up @@ -622,6 +625,7 @@ myChart.setOption(themeManager.getChartOptions());
iframe.contentDocument.getElementById(id + '_legend').innerHTML = '';
}
themeManager.manageChartResize(myChart, chartId);
themeManager.manageThemeObserver(myChart);
if ('none' !== popoverInput) {
themeManager.externalizePopover(
{
Expand Down
2 changes: 2 additions & 0 deletions docs/use_cases/add-unit.html
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,8 @@ <h5 class="display-5 mx-3 mb-1 mt-0">Sub-Title</h5>
themeManager.manageChartResize(myChart, 'barLine_chart');
// Register the externalization of the tooltip/popup
themeManager.externalizePopover();
// Observe dark / light mode changes
themeManager.manageThemeObserver(myChart);
// Display the chart using the configured theme and data.
myChart.setOption(themeManager.getChartOptions());
</script>
Expand Down
4 changes: 4 additions & 0 deletions docs/use_cases/legends-holders.html
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,8 @@ <h5 class="display-5 mx-3 mb-1 mt-0">Sub-Title</h5>
themeManager.manageChartResize(myChart, 'barChartSH_chart');
// Register the externalization of the tooltip/popup
themeManager.externalizePopover();
// Observe dark / light mode changes
themeManager.manageThemeObserver(myChart);
// Display the chart using the configured theme and data.
myChart.setOption(themeManager.getChartOptions());
</script>
Expand Down Expand Up @@ -346,6 +348,8 @@ <h5 class="display-5 mx-3 mb-1 mt-0">Sub-Title</h5>
themeManager.manageChartResize(myChart, 'barLine_chart');
// Register the externalization of the tooltip/popup
themeManager.externalizePopover();
// Observe dark / light mode changes
themeManager.manageThemeObserver(myChart);
// Display the chart using the configured theme and data.
myChart.setOption(themeManager.getChartOptions());
</script>
Expand Down
2 changes: 2 additions & 0 deletions docs/use_cases/size-management.html
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,8 @@ <h5 class="display-5 mx-3 mb-1 mt-0">Sub-Title</h5>
themeManager.manageChartResize(myChart, 'barLine_chart');
// Register the externalization of the tooltip/popup
themeManager.externalizePopover();
// Observe dark / light mode changes
themeManager.manageThemeObserver(myChart);
// Display the chart using the configured theme and data.
myChart.setOption(themeManager.getChartOptions());
</script>
Expand Down
2 changes: 2 additions & 0 deletions docs/use_cases/specify-color.html
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,8 @@ <h5 class="display-5 mx-3 mb-1 mt-0">Sub-Title</h5>
themeManager.manageChartResize(myChart, 'barLine_chart');
// Register the externalization of the tooltip/popup
themeManager.externalizePopover();
// Observe dark / light mode changes
themeManager.manageThemeObserver(myChart);
// Display the chart using the configured theme and data.
myChart.setOption(themeManager.getChartOptions());
</script>
Expand Down
2 changes: 2 additions & 0 deletions docs/use_cases/time-slider.html
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,8 @@ <h5 class="display-5 mx-3 mb-1 mt-0">Sub-Title</h5>
themeManager.manageChartResize(myChart, 'barLine_chart');
// Register the externalization of the tooltip/popup and use the second parameter as specified in https://ods-charts.netlify.app/api/classes/odschartspopoverdefinition to change the popup value (cf https://ods-charts.netlify.app/api/classes/odschartspopoverdefinition#getPopupContentValue)
themeManager.externalizePopover();
// Observe dark / light mode changes
themeManager.manageThemeObserver(myChart);
// Display the chart using the configured theme and data.
myChart.setOption(themeManager.getChartOptions());
</script>
Expand Down
12 changes: 12 additions & 0 deletions docs/use_cases/tooltip.html
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,8 @@ <h5 class="display-5 mx-3 mb-1 mt-0">Sub-Title</h5>
div1_themeManager.externalizeLegends(myChart, '#div1_legend');
// Manage window size changed
div1_themeManager.manageChartResize(myChart, 'div1_chart');
// Observe dark / light mode changes
div1_themeManager.manageThemeObserver(myChart);
// Register the externalization of the tooltip/popup
div1_themeManager.externalizePopover();
// Display the chart using the configured theme and data.
Expand Down Expand Up @@ -273,6 +275,8 @@ <h5 class="display-5 mx-3 mb-1 mt-0">Sub-Title</h5>
div6_themeManager.setDataOptions(div6_dataOptions);
// Register the externalization of the legend.
div6_themeManager.manageChartResize(myChart, 'div6_chart');
// Observe dark / light mode changes
div6_themeManager.manageThemeObserver(myChart);
// Register the externalization of the tooltip/popup
div6_themeManager.externalizePopover();
// Display the chart using the configured theme and data.
Expand Down Expand Up @@ -358,6 +362,8 @@ <h5 class="display-5 mx-3 mb-1 mt-0">Sub-Title</h5>
div2_themeManager.setDataOptions(div2_dataOptions);
// Register the externalization of the legend.
div2_themeManager.manageChartResize(myChart, 'div2_chart');
// Observe dark / light mode changes
div2_themeManager.manageThemeObserver(myChart);
// Register the externalization of the tooltip/popup
div2_themeManager.externalizePopover(undefined, {
...ODSCharts.ODSChartsPopoverManagers.NONE,
Expand Down Expand Up @@ -443,6 +449,8 @@ <h5 class="display-5 mx-3 mb-1 mt-0">Sub-Title</h5>
div3_themeManager.setDataOptions(div3_dataOptions);
// Register the externalization of the legend.
div3_themeManager.manageChartResize(myChart, 'div3_chart');
// Observe dark / light mode changes
div3_themeManager.manageThemeObserver(myChart);
// Register the externalization of the tooltip/popup
div3_themeManager.externalizePopover(undefined, {
...ODSCharts.ODSChartsPopoverManagers.BOOSTED5,
Expand Down Expand Up @@ -541,6 +549,8 @@ <h5 class="display-5 mx-3 mb-1 mt-0">Sub-Title</h5>
div4_themeManager.setDataOptions(div4_dataOptions);
// Register the externalization of the legend.
div4_themeManager.manageChartResize(myChart, 'div4_chart');
// Observe dark / light mode changes
div4_themeManager.manageThemeObserver(myChart);
// Register the externalization of the tooltip/popup
div4_themeManager.externalizePopover(undefined, {
...ODSCharts.ODSChartsPopoverManagers.NONE,
Expand Down Expand Up @@ -631,6 +641,8 @@ <h5 class="display-5 mx-3 mb-1 mt-0">Sub-Title</h5>
div5_themeManager.setDataOptions(div5_dataOptions);
// Register the externalization of the legend.
div5_themeManager.manageChartResize(myChart, 'div5_chart');
// Observe dark / light mode changes
div5_themeManager.manageThemeObserver(myChart);
// Register the externalization of the tooltip/popup
div5_themeManager.externalizePopover(undefined, {
...ODSCharts.ODSChartsPopoverManagers.BOOSTED5,
Expand Down
2 changes: 2 additions & 0 deletions docs/use_cases/two-colors-serie.html
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,8 @@ <h5 class="display-5 mx-3 mb-1 mt-0">Sub-Title</h5>
themeManager.externalizeLegends(myChart, '#barLine_legend');
// Manage window size changed
themeManager.manageChartResize(myChart, 'barLine_chart');
// Observe dark / light mode changes
themeManager.manageThemeObserver(myChart);
// Register the externalization of the tooltip/popup and use the second parameter as specified in https://ods-charts.netlify.app/api/classes/odschartspopoverdefinition to change the popup value (cf https://ods-charts.netlify.app/api/classes/odschartspopoverdefinition#getPopupContentValue)
themeManager.externalizePopover();
// Display the chart using the configured theme and data.
Expand Down
151 changes: 145 additions & 6 deletions src/theme/ods-chart-theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ import { DEFAULT_COLORS } from './default/ODS.colors';
import { DEFAULT_COMMON } from './default/ODS.common';
import { DEFAULT_LINES_AXIS } from './default/ODS.lines.axis';
import { ODS_CHARTS_CSS_VARIABLES } from './colors/colors-css-variables';
import { ODSChartsThemeObserver } from './theme-observer/ods-charts-theme-observer';

/**
* ODSChartsColorsSet is used for predefined color sets.
Expand Down Expand Up @@ -350,6 +351,8 @@ const THEMES: {
* themeManager.externalizeLegends(...);
* // Manage chart container size changed
* themeManager.manageChartResize(...);
* // Observe dark / light mode changes
* themeManager.manageThemeObserver(...);
* // Register the externalization of the tooltip/popup
* themeManager.externalizePopover(...);
* // Display the chart using the configured theme and data.
Expand All @@ -364,8 +367,10 @@ export class ODSChartsTheme {
private chartLegendManager: ODSChartsLegends = undefined as unknown as ODSChartsLegends;
private chartResizeManager: ODSChartsResize = undefined as unknown as ODSChartsResize;
private chartPopoverManager: ODSChartsPopover = undefined as unknown as ODSChartsPopover;
private chartThemeObserver: ODSChartsThemeObserver = undefined as unknown as ODSChartsThemeObserver;
public cssThemeName: ODSChartsCSSThemesNames;
private cssVarsMapping: { [variable: string]: string } = {};
public theme: EChartsProject;

private _computedStyle: CSSStyleDeclaration | undefined | null = undefined;

Expand Down Expand Up @@ -496,16 +501,22 @@ export class ODSChartsTheme {
return result;
}

private calculateTheme(): EChartsProject {
this.cssVarsMapping = {};
this.theme = this.replaceAllCssVars(cloneDeepObject(this.initialTheme));
return this.theme;
}

private constructor(
public name: string,
public theme: EChartsProject,
private initialTheme: EChartsProject,
public options: ODSChartsThemeOptions
) {
this.cssThemeName =
(Object.keys(ODSChartsCSSThemes).find(
(oneTheme) => ODSChartsCSSThemes[oneTheme as ODSChartsCSSThemesNames] === options.cssTheme
) as ODSChartsCSSThemesNames) || ODSChartsCSSThemesNames.CUSTOM;
this.theme = this.replaceAllCssVars(this.theme);
this.theme = this.calculateTheme();
}

/**
Expand Down Expand Up @@ -626,6 +637,104 @@ export class ODSChartsTheme {
return colors;
}

/**
* As it seems not possible to update a theme after the charts was initialized,
* this method calculate the new theme values and update these values directly inside the
* themeOptions that will be merge with the dataOptions to update the charts options with
* this new chartOptions
* @param themeOptions the basic themeOptions
* @returns this.theme, the new global theme calculated.
*/
private calculateNewThemeAndAddItInThemeOptions(themeOptions: any): any {
const newTheme = this.calculateTheme();
mergeObjects(themeOptions, {
color: newTheme.color,
backgroundColor: newTheme.backgroundColor,
title: newTheme.title,
grid: { tooltip: newTheme.tooltip },
});

if (this.dataOptions.toolbox) {
themeOptions.toolbox = newTheme.toolbox;
}
if (this.dataOptions.timeline) {
themeOptions.timeline = newTheme.timeline;
}
//TODO: check the mapping of radar
// themeOptions.radar = newTheme.radar;
//TODO: missing parallel mapping
// themeOptions.parallel = newTheme.parallel;
//TODO: check the mapping of geo
// themeOptions.geo = newTheme.geo;

if (this.dataOptions.series) {
themeOptions.series = [];
for (let index = 0; index < this.dataOptions.series.length; index++) {
switch (this.dataOptions.series[index].type) {
case 'line':
themeOptions.series[index] = { ...newTheme.line, markPoint: newTheme.markPoint };
break;
case 'bar':
themeOptions.series[index] = newTheme.bar;
break;
case 'pie':
themeOptions.series[index] = newTheme.pie;
break;
case 'scatter':
themeOptions.series[index] = newTheme.scatter;
break;
case 'boxplot':
themeOptions.series[index] = newTheme.boxplot;
break;
case 'sankey':
themeOptions.series[index] = newTheme.sankey;
break;
case 'funnel':
themeOptions.series[index] = newTheme.funnel;
break;
case 'gauge':
themeOptions.series[index] = newTheme.gauge;
break;
case 'candlestick':
themeOptions.series[index] = newTheme.candlestick;
break;
case 'graph':
themeOptions.series[index] = newTheme.graph;
break;
case 'treemap':
themeOptions.series[index] = newTheme.map;
break;
}
}
}

if (this.dataOptions.visualMap) {
themeOptions.visualMap = [];
for (let index = 0; index < this.dataOptions.series.length; index++) {
themeOptions.visualMap[index] = newTheme.visualMap;
}
}
for (const axisType of ['xAxis', 'yAxis']) {
if (this.dataOptions[axisType]) {
switch (this.dataOptions[axisType].type) {
case 'category':
themeOptions[axisType] = mergeObjects(themeOptions[axisType], newTheme.categoryAxis);
break;
case 'value':
themeOptions[axisType] = mergeObjects(themeOptions[axisType], newTheme.valueAxis);
break;
case 'log':
themeOptions[axisType] = mergeObjects(themeOptions[axisType], newTheme.logAxis);
break;
case 'time':
themeOptions[axisType] = mergeObjects(themeOptions[axisType], newTheme.timeAxis);
break;
}
}
}
return newTheme;
}

/**
* getThemeOptions() can be used to get the options that should be added to charts options to implement the Orange Design System.
*
Expand All @@ -634,9 +743,10 @@ export class ODSChartsTheme {
* getThemeOptions() needs graph data, already set or given in the dataOptions parameter
*
* @param dataOptions optionally you can use this call to set dataOptions, if not already set.
* @param addGlobalThemeOptions indicates if the specificities of the global theme used in the chart init method
* @returns modifications to be made to the [Apache Echarts data options](https://echarts.apache.org/en/option.html) to implement the Orange Design System.
*/
public getThemeOptions(dataOptions?: any): any {
public getThemeOptions(dataOptions?: any, addGlobalThemeOptions: boolean = false): any {
if (dataOptions) {
this.dataOptions = dataOptions;
}
Expand Down Expand Up @@ -715,6 +825,8 @@ export class ODSChartsTheme {
legend: cloneDeepObject(legend),
};

let usedTheme = addGlobalThemeOptions ? this.calculateNewThemeAndAddItInThemeOptions(themeOptions) : this.theme;

for (const axis of ['xAxis', 'yAxis']) {
if (!isMainAxis(this.dataOptions[axis]) && !(this.dataOptions[axis] && this.dataOptions[axis].axisLine)) {
themeOptions[axis].axisLine = { show: false };
Expand All @@ -725,7 +837,7 @@ export class ODSChartsTheme {
}
}

const displayedColors = this.getDisplayedColors(this.theme.color);
const displayedColors = this.getDisplayedColors(usedTheme.color);

themeOptions = this.replaceAllCssVars(themeOptions);

Expand All @@ -743,6 +855,10 @@ export class ODSChartsTheme {
this.chartResizeManager.addResizeManagement();
}

if (this.chartThemeObserver) {
this.chartThemeObserver.addThemeObserver();
}

if (this.chartPopoverManager) {
this.chartPopoverManager.addPopoverManagement(
this.dataOptions,
Expand Down Expand Up @@ -826,25 +942,48 @@ export class ODSChartsTheme {
return this;
}

/**
* manageThemeObserver() is used to enable auto-switch between dark and light mode.
* It observe the closest element with a data-bs-theme indicator to
* adapt the graph colour to the new theme.
* @param echart the initialized eCharts object
* @param dataOptions optionally you can use this call to set dataOptions
* @returns returns back the theme manager object
*/
public manageThemeObserver(echart: any, dataOptions?: any): ODSChartsTheme {
if (dataOptions) {
this.dataOptions = dataOptions;
}
this.chartThemeObserver = ODSChartsThemeObserver.addThemeObserver(echart, () => {
// update chart options with theme options enriched with values
// from a newly calculated global theme
echart.setOption(this.getChartOptions(undefined, true));
});
return this;
}

/**
* getChartOptions() build the eCharts options merging
* - options implementing the Orange Design System {@link ODSChartsThemeOptions}
* - optionally options implementing {@link externalizeLegends},
* - optionally options implementing {@link externalizePopover},
* - optionally options implementing {@link manageChartResize},
* - optionally options implementing {@link manageThemeObserver},
* - data from {@link setDataOptions}
*
* @param dataOptions optionally you can use this call to set dataOptions, if not already set.
* @param addGlobalThemeOptions indicates if the specificities of the global theme used in the chart init method
* must be added in the options of the chart
* @returns the Apache ECharts options to use in [Apache Echarts `setOption()`](https://echarts.apache.org/en/option.html) call.
*/
public getChartOptions(dataOptions?: any): any {
public getChartOptions(dataOptions?: any, addGlobalThemeOptions: boolean = false): any {
if (dataOptions) {
this.dataOptions = dataOptions;
}
if (!this.dataOptions) {
throw new Error('the chart basic options must be set to get the theme completion');
}
const result = mergeObjects(this.getThemeOptions(), this.dataOptions);
const result = mergeObjects(this.getThemeOptions(undefined, addGlobalThemeOptions), this.dataOptions);
return result;
}
}
Loading

0 comments on commit 0612672

Please sign in to comment.