From bcea0a63a9b39fdfddfac8bafa9f898044235b9b Mon Sep 17 00:00:00 2001 From: Wesley Dai Date: Wed, 23 Oct 2024 23:48:32 -0500 Subject: [PATCH] feat(chart): improve chart dark theme --- src/App.scss | 2 + src/components/MusicChart.tsx | 18 +- src/components/charts/ChartTheme.ts | 474 ------------------ .../charts/MonthlyFrequencyChart.tsx | 1 + .../charts/MonthlyFrequencyStackedChart.tsx | 1 + .../charts/RegionalDistributionChart.tsx | 10 +- src/constants.ts | 3 + src/styles/highcharts.scss | 94 ++++ 8 files changed, 114 insertions(+), 489 deletions(-) delete mode 100644 src/components/charts/ChartTheme.ts create mode 100644 src/styles/highcharts.scss diff --git a/src/App.scss b/src/App.scss index df60a50..f5e27ec 100644 --- a/src/App.scss +++ b/src/App.scss @@ -1,3 +1,5 @@ +@import "./styles/highcharts.scss"; + .App { p { margin: 2vh 4vw; diff --git a/src/components/MusicChart.tsx b/src/components/MusicChart.tsx index c30aeba..0e32a8c 100644 --- a/src/components/MusicChart.tsx +++ b/src/components/MusicChart.tsx @@ -1,10 +1,9 @@ /** @jsxImportSource @emotion/react */ import { css, SerializedStyles } from '@emotion/react'; -import React, { useEffect } from 'react'; +import React, { useEffect, useState } from 'react'; import Highcharts from 'highcharts'; import HighchartsReact from 'highcharts-react-official'; import { useTheme } from '../context/ThemeContext'; -import { themeGlobalDark, themeGlobalLight } from './charts/ChartTheme'; interface IMusicChartProps { styles?: SerializedStyles; @@ -14,15 +13,14 @@ interface IMusicChartProps { export const MusicChart: React.FC = (props) => { const appTheme = useTheme(); + const [classTheme, setClassTheme] = useState(''); useEffect(() => { - const themeConfig = appTheme.darkMode ? themeGlobalDark : themeGlobalLight; - const newOptions = { - ...props.options, - ...themeConfig, - }; - props.chartComponent.current?.chart?.update(newOptions); - }, [props.chartComponent, props.options, appTheme]); + const themeClass = appTheme.darkMode + ? 'highcharts-dark' + : 'highcharts-light'; + setClassTheme(themeClass); + }, [appTheme]); return (
= (props) => { > = { - colors: [ - '#8087E8', - '#A3EDBA', - '#F19E53', - '#6699A1', - '#E1D369', - '#87B4E7', - '#DA6D85', - '#BBBAC5', - ], - - chart: { - backgroundColor: { - linearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 }, - stops: [ - [0, '#1f1836'], - [1, '#45445d'], - ], - }, - style: { - fontFamily: 'IBM Plex Sans, sans-serif', - }, - }, - title: { - style: { - fontSize: '22px', - fontWeight: '500', - color: '#fff', - }, - }, - subtitle: { - style: { - fontSize: '16px', - fontWeight: '400', - color: '#fff', - }, - }, - credits: { - style: { - color: '#f0f0f0', - }, - }, - caption: { - style: { - color: '#f0f0f0', - }, - }, - tooltip: { - borderWidth: 0, - backgroundColor: '#000000', - style: { - color: '#f0f0f0', - }, - shadow: true, - }, - legend: { - backgroundColor: 'transparent', - itemStyle: { - fontWeight: '400', - fontSize: '12px', - color: '#fff', - }, - itemHoverStyle: { - fontWeight: '700', - color: '#fff', - }, - }, - // @ts-ignore typing - labels: { - style: { - color: '#707073', - }, - }, - plotOptions: { - series: { - dataLabels: { - color: '#46465C', - style: { - fontSize: '13px', - }, - }, - marker: { - lineColor: '#333', - }, - }, - boxplot: { - fillColor: '#505053', - }, - candlestick: { - lineColor: null as any, - upColor: '#DA6D85', - upLineColor: '#DA6D85', - }, - errorbar: { - color: 'white', - }, - dumbbell: { - lowColor: '#f0f0f0', - }, - map: { - borderColor: 'rgba(200, 200, 200, 1)', - nullColor: '#78758C', - }, - }, - drilldown: { - activeAxisLabelStyle: { - color: '#F0F0F3', - }, - activeDataLabelStyle: { - color: '#F0F0F3', - }, - // @ts-ignore typing - drillUpButton: { - theme: { - fill: '#fff', - }, - }, - }, - xAxis: { - gridLineColor: '#707073', - labels: { - style: { - color: '#fff', - fontSize: '12px', - }, - }, - lineColor: '#707073', - minorGridLineColor: '#505053', - tickColor: '#707073', - title: { - style: { - color: '#fff', - }, - }, - }, - yAxis: { - gridLineColor: '#707073', - labels: { - style: { - color: '#fff', - fontSize: '12px', - }, - }, - lineColor: '#707073', - minorGridLineColor: '#505053', - tickColor: '#707073', - tickWidth: 1, - title: { - style: { - color: '#fff', - fontWeight: '300', - }, - }, - }, - mapNavigation: { - enabled: true, - buttonOptions: { - theme: { - fill: '#46465C', - 'stroke-width': 1, - stroke: '#BBBAC5', - r: 2, - style: { - color: '#fff', - }, - states: { - hover: { - fill: '#000', - 'stroke-width': 1, - stroke: '#f0f0f0', - style: { - color: '#fff', - }, - }, - - select: { - fill: '#000', - 'stroke-width': 1, - stroke: '#f0f0f0', - style: { - color: '#fff', - }, - }, - }, - }, - verticalAlign: 'bottom', - }, - }, - // scroll charts - rangeSelector: { - buttonTheme: { - fill: '#46465C', - stroke: '#BBBAC5', - 'stroke-width': 1, - style: { - color: '#fff', - }, - states: { - hover: { - fill: '#1f1836', - style: { - color: '#fff', - }, - 'stroke-width': 1, - stroke: 'white', - }, - select: { - fill: '#1f1836', - style: { - color: '#fff', - }, - 'stroke-width': 1, - stroke: 'white', - }, - }, - }, - inputBoxBorderColor: '#BBBAC5', - inputStyle: { - backgroundColor: '#2F2B38', - color: '#fff', - }, - labelStyle: { - color: '#fff', - }, - }, - navigator: { - handles: { - backgroundColor: '#BBBAC5', - borderColor: '#2F2B38', - }, - outlineColor: '#CCC', - maskFill: 'rgba(255,255,255,0.1)', - series: { - color: '#A3EDBA', - lineColor: '#A3EDBA', - }, - xAxis: { - gridLineColor: '#505053', - }, - }, - scrollbar: { - barBackgroundColor: '#BBBAC5', - barBorderColor: '#808083', - buttonArrowColor: '#2F2B38', - buttonBackgroundColor: '#BBBAC5', - buttonBorderColor: '#2F2B38', - rifleColor: '#2F2B38', - trackBackgroundColor: '#78758C', - trackBorderColor: '#2F2B38', - }, -}; - -export const themeGlobalLight: Partial = { - colors: [ - '#8087E8', - '#A3EDBA', - '#F19E53', - '#6699A1', - '#E1D369', - '#87B4E7', - '#DA6D85', - '#BBBAC5', - ], - chart: { - backgroundColor: '#f0f0f0', - style: { - fontFamily: 'IBM Plex Sans, sans-serif', - }, - }, - title: { - style: { - fontSize: '22px', - fontWeight: '500', - color: '#2F2B38', - }, - }, - subtitle: { - style: { - fontSize: '16px', - fontWeight: '400', - color: '#2F2B38', - }, - }, - tooltip: { - borderWidth: 0, - backgroundColor: '#46465C', - style: { - color: '#f0f0f0', - }, - shadow: true, - }, - legend: { - backgroundColor: '#f0f0f0', - borderColor: '#BBBAC5', - borderWidth: 1, - borderRadius: 2, - itemStyle: { - fontWeight: '400', - fontSize: '12px', - color: '#2F2B38', - }, - itemHoverStyle: { - fontWeight: '700', - color: '#46465C', - }, - }, - navigation: { - buttonOptions: { - // @ts-ignore typing - symbolStroke: '#2F2B38', - theme: { - fill: '#fff', - // @ts-ignore typing - states: { - hover: { - stroke: '#46465C', - fill: '#fff', - }, - select: { - stroke: '#46465C', - fill: '#fff', - }, - }, - }, - }, - }, - // @ts-ignore typing - labels: { - style: { - color: '#46465C', - }, - }, - credits: { - style: { - color: '#46465C', - }, - }, - drilldown: { - activeAxisLabelStyle: { - color: '#2F2B38', - }, - activeDataLabelStyle: { - color: '#2F2B38', - }, - // @ts-ignore typing - drillUpButton: { - theme: { - fill: '#2F2B38', - style: { - color: '#fff', - }, - }, - }, - }, - xAxis: { - gridLineColor: '#ccc', - labels: { - style: { - color: '#46465C', - fontSize: '12px', - }, - }, - lineColor: '#ccc', - minorGridLineColor: '#ebebeb', - tickColor: '#ccc', - title: { - style: { - color: '#2F2B38', - }, - }, - }, - yAxis: { - gridLineColor: '#ccc', - labels: { - style: { - color: '#46465C', - fontSize: '12px', - }, - }, - lineColor: '#ccc', - minorGridLineColor: '#ebebeb', - tickColor: '#ccc', - tickWidth: 1, - title: { - style: { - color: '#2F2B38', - fontWeight: '300', - }, - }, - }, - // scroll charts - rangeSelector: { - buttonTheme: { - fill: '#fff', - style: { - color: '#46465C', - stroke: 'transparent', - }, - states: { - hover: { - fill: '#fff', - style: { - color: '#46465C', - }, - 'stroke-width': 1, - stroke: '#46465C', - }, - select: { - fill: '#fff', - style: { - color: '#46465C', - }, - 'stroke-width': 1, - stroke: '#46465C', - }, - }, - }, - inputBoxBorderColor: '#BBBAC5', - inputStyle: { - backgroundColor: '#fff', - color: '#46465C', - }, - labelStyle: { - color: '#46465C', - }, - }, - scrollbar: { - barBackgroundColor: '#BBBAC5', - barBorderColor: '#808083', - buttonArrowColor: '#fff', - buttonBackgroundColor: '#BBBAC5', - buttonBorderColor: '#46465C', - rifleColor: '#FFF', - trackBackgroundColor: '#dedede', - trackBorderColor: '#BBBAC5', - }, - plotOptions: { - series: { - borderWidth: 1, - borderColor: '#BBBAC5', - dataLabels: { - color: '#46465C', - style: { - fontSize: '13px', - }, - }, - marker: { - lineColor: '#46465C', - }, - }, - boxplot: { - fillColor: '#505053', - }, - candlestick: { - lineColor: null as any, - upColor: '#DA6D85', - upLineColor: '#DA6D85', - }, - errorbar: { - color: 'white', - }, - map: { - borderColor: 'rgba(200, 200, 200, 0.3)', - nullColor: 'rgba(200, 200, 200, 0.3)', - }, - }, -}; diff --git a/src/components/charts/MonthlyFrequencyChart.tsx b/src/components/charts/MonthlyFrequencyChart.tsx index 65cc26e..9719c0c 100644 --- a/src/components/charts/MonthlyFrequencyChart.tsx +++ b/src/components/charts/MonthlyFrequencyChart.tsx @@ -42,6 +42,7 @@ const MonthlyFrequencyChart: React.FC = (props) => { const options: Highcharts.Options = { chart: { type: 'line', + styledMode: true, }, title: { text: 'Song frequency per month', diff --git a/src/components/charts/MonthlyFrequencyStackedChart.tsx b/src/components/charts/MonthlyFrequencyStackedChart.tsx index d073f7b..84283ee 100644 --- a/src/components/charts/MonthlyFrequencyStackedChart.tsx +++ b/src/components/charts/MonthlyFrequencyStackedChart.tsx @@ -60,6 +60,7 @@ const MonthlyFrequencyStackedChart: React.FC = () => { const options: Highcharts.Options = { chart: { type: 'area', + styledMode: true, }, title: { text: 'Song frequency per month (all years)', diff --git a/src/components/charts/RegionalDistributionChart.tsx b/src/components/charts/RegionalDistributionChart.tsx index e118227..92afc0f 100644 --- a/src/components/charts/RegionalDistributionChart.tsx +++ b/src/components/charts/RegionalDistributionChart.tsx @@ -4,9 +4,8 @@ import React, { useRef } from 'react'; import { useDataSourceState } from '../../context/DataSourceContext'; import { IMusicRecordGrid } from '../../models/DataModel'; import Highcharts, { DataLabelsOptions } from 'highcharts'; -import { complement } from 'polished'; import { ICommonChartProps } from './CommonChartProps'; -import { cornFlowerBlue } from '../../constants'; +import { cornFlowerBlueComplIdx, cornFlowerBlueIdx } from '../../constants'; import { MapleClient } from '../../models/MapleClient'; import { MusicChart } from '../MusicChart'; import HighchartsReact from 'highcharts-react-official'; @@ -29,6 +28,7 @@ const RegionalDistributionChart: React.FC = (props) => { const options: Highcharts.Options = { chart: { type: 'pie', + styledMode: true, }, title: { text: 'Distribution of song region', @@ -39,10 +39,10 @@ const RegionalDistributionChart: React.FC = (props) => { name: 'Total Songs', type: 'pie', data: [ - { name: 'Domestic', color: cornFlowerBlue, y: yearlyKorea }, + { name: 'Domestic', colorIndex: cornFlowerBlueIdx, y: yearlyKorea }, { name: 'Overseas', - color: complement(cornFlowerBlue), + colorIndex: cornFlowerBlueComplIdx, y: yearlyOverseas, }, ], @@ -58,7 +58,7 @@ const RegionalDistributionChart: React.FC = (props) => { data: [ { name: 'Korea', - color: cornFlowerBlue, + colorIndex: cornFlowerBlueIdx, y: yearlyKorea, }, { name: 'Japan', y: getYearlyRegionalCount(MapleClient.Japan) }, diff --git a/src/constants.ts b/src/constants.ts index 42e2282..24cfc18 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -4,3 +4,6 @@ export const PAGINATION_PAGE_SIZE = 25; export const KMST_GENERATION_CUTOFF_DATE = parseISO('2014-11-20'); export const cornFlowerBlue = '#6495ed'; + +export const cornFlowerBlueIdx = 8; +export const cornFlowerBlueComplIdx = 9; diff --git a/src/styles/highcharts.scss b/src/styles/highcharts.scss new file mode 100644 index 0000000..753da8b --- /dev/null +++ b/src/styles/highcharts.scss @@ -0,0 +1,94 @@ +@import url("https://code.highcharts.com/css/highcharts.css"); + +@media (prefers-color-scheme: dark) { + :root { + /* Colors for data series and points */ + --highcharts-color-0: #8087e8; + --highcharts-color-1: #a3edba; + --highcharts-color-2: #f19e53; + --highcharts-color-3: #6699a1; + --highcharts-color-4: #e1d369; + --highcharts-color-5: #87b4e7; + --highcharts-color-6: #da6d85; + --highcharts-color-7: #bbbac5; + // Custom + --highcharts-color-8: #6495ed; + --highcharts-color-9: #edbc64; + + /* UI colors */ + --highcharts-background-color: #222; + + /* + Neutral color variations + https://www.highcharts.com/samples/highcharts/css/palette-helper + */ + --highcharts-neutral-color-100: rgb(255, 255, 255); + --highcharts-neutral-color-80: rgb(214, 214, 214); + --highcharts-neutral-color-60: rgb(173, 173, 173); + --highcharts-neutral-color-40: rgb(133, 133, 133); + --highcharts-neutral-color-20: rgb(92, 92, 92); + --highcharts-neutral-color-10: rgb(71, 71, 71); + --highcharts-neutral-color-5: rgb(61, 61, 61); + --highcharts-neutral-color-3: rgb(57, 57, 57); + + /* Highlight color variations */ + --highcharts-highlight-color-100: rgb(122, 167, 255); + --highcharts-highlight-color-80: rgb(108, 144, 214); + --highcharts-highlight-color-60: rgb(94, 121, 173); + --highcharts-highlight-color-20: rgb(65, 74, 92); + --highcharts-highlight-color-10: rgb(58, 63, 71); + } +} + +.highcharts-dark { + /* Colors for data series and points */ + --highcharts-color-0: #8087e8; + --highcharts-color-1: #a3edba; + --highcharts-color-2: #f19e53; + --highcharts-color-3: #6699a1; + --highcharts-color-4: #e1d369; + --highcharts-color-5: #87b4e7; + --highcharts-color-6: #da6d85; + --highcharts-color-7: #bbbac5; + // Custom + --highcharts-color-8: #6495ed; + --highcharts-color-9: #edbc64; + + /* UI colors */ + --highcharts-background-color: #222; + + /* Neutral color variations */ + --highcharts-neutral-color-100: rgb(255, 255, 255); + --highcharts-neutral-color-80: rgb(214, 214, 214); + --highcharts-neutral-color-60: rgb(173, 173, 173); + --highcharts-neutral-color-40: rgb(133, 133, 133); + --highcharts-neutral-color-20: rgb(92, 92, 92); + --highcharts-neutral-color-10: rgb(71, 71, 71); + --highcharts-neutral-color-5: rgb(61, 61, 61); + --highcharts-neutral-color-3: rgb(57, 57, 57); + + /* Highlight color variations */ + --highcharts-highlight-color-100: rgb(122, 167, 255); + --highcharts-highlight-color-80: rgb(108, 144, 214); + --highcharts-highlight-color-60: rgb(94, 121, 173); + --highcharts-highlight-color-20: rgb(65, 74, 92); + --highcharts-highlight-color-10: rgb(58, 63, 71); +} + +.highcharts-light { + /* Colors for data series and points */ + --highcharts-color-0: #8087e8; + --highcharts-color-1: #a3edba; + --highcharts-color-2: #f19e53; + --highcharts-color-3: #6699a1; + --highcharts-color-4: #e1d369; + --highcharts-color-5: #87b4e7; + --highcharts-color-6: #da6d85; + --highcharts-color-7: #bbbac5; + // Custom + --highcharts-color-8: #6495ed; + --highcharts-color-9: #edbc64; + + /* UI colors */ + --highcharts-background-color: #fff; +}