Skip to content

Commit

Permalink
Convert summary tab charts to recharts
Browse files Browse the repository at this point in the history
  • Loading branch information
rhyskoedijk committed Dec 27, 2024
1 parent d7955d9 commit d625b76
Show file tree
Hide file tree
Showing 7 changed files with 236 additions and 207 deletions.
3 changes: 0 additions & 3 deletions ui/components/charts/BarChart.scss
Original file line number Diff line number Diff line change
@@ -1,3 +0,0 @@
.bar-chart .title {
margin-bottom: 0px;
}
93 changes: 51 additions & 42 deletions ui/components/charts/BarChart.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,45 @@
import * as React from 'react';

import { BarSeriesType, cheerfulFiestaPalette, BarChart as MuiBarChart } from '@mui/x-charts';
import { MakeOptional } from '@mui/x-charts/internals';
import {
Bar,
CartesianGrid,
Legend,
BarChart as ReBarChart,
ResponsiveContainer,
Tooltip,
XAxis,
YAxis,
} from 'recharts';

import { DEFAULT_COLORS } from './Colours';

import './BarChart.scss';

export interface BarChartSeries {
export interface ChartSeries {
name: string;
values: Record<string, number>;
}

export interface ChartBar {
name: string;
color?: string;
label: string;
data: number[];
stack?: string;
}

interface Props {
className?: string;
colors?: string[];
bands?: string[];
data: BarChartSeries[];
layout: 'horizontal' | 'vertical';
bars: ChartBar[];
data: ChartSeries[];
title?: string;
width?: number;
height?: number;
}

interface State {
series: MakeOptional<BarSeriesType, 'type'>[];
colors: string[];
bars: ChartBar[];
data: ChartSeries[];
total: number;
}

Expand All @@ -35,16 +50,15 @@ export class BarChart extends React.Component<Props, State> {
}

static getDerivedStateFromProps(props: Props): State {
console.log(props.bars, props.data);
return {
series: props.data.map((d) => ({
type: 'bar',
layout: props.layout,
label: d.label,
data: d.data,
stack: d.stack,
...(d.color ? { color: d.color } : {}),
})),
total: props.data.reduce((accX, itemX) => accX + itemX.data.reduce((accY, itemY) => accY + itemY, 0), 0),
colors: props.colors?.length ? props.colors : DEFAULT_COLORS,
bars: props.bars,
data: props.data,
total: props.data.reduce(
(accX, itemX) => accX + Object.values(itemX.values).reduce((accY, itemY) => accY + itemY, 0),
0,
),
};
}

Expand All @@ -58,31 +72,26 @@ export class BarChart extends React.Component<Props, State> {
return !this.state.total ? (
<div />
) : (
<div className={'bar-chart flex-column flex-center flex-grow ' + (this.props.className || '')}>
<div className={'bar-chart flex-column flex-center ' + (this.props.className || '')}>
{this.props.title && <h3 className="title">{this.props.title}</h3>}
<MuiBarChart
barLabel="value"
colors={this.props.colors || cheerfulFiestaPalette}
series={this.state.series}
layout={this.props.layout}
{...(this.props.layout === 'vertical'
? { xAxis: [{ scaleType: 'band', data: this.props.bands || [] }] }
: {})}
{...(this.props.layout === 'horizontal'
? { yAxis: [{ scaleType: 'band', data: this.props.bands || [] }] }
: {})}
slotProps={{
legend: {
labelStyle: {
fill: 'var(--text-primary-color)',
fontSize: '0.8em',
},
},
}}
margin={{ left: 75 }}
width={this.props.width || 600}
height={this.props.height || 200}
/>
<ResponsiveContainer width={this.props.width || '100%'} height={this.props.height || '100%'} debounce={300}>
<ReBarChart data={this.state.data} margin={{ top: 0, right: 0, bottom: 0, left: 0 }} reverseStackOrder={true}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="name" />
<YAxis />
<Tooltip />
<Legend />
{this.state.bars.map((bar, index) => (
<Bar
key={`bar-${index}`}
label={bar.name}
dataKey={(series: ChartSeries) => series.values[bar.name] || 0}
stackId={bar.stack}
fill={bar.color || this.state.colors[index % this.state.colors.length]}
/>
))}
</ReBarChart>
</ResponsiveContainer>
</div>
);
}
Expand Down
12 changes: 12 additions & 0 deletions ui/components/charts/Colours.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export const DEFAULT_COLORS = [
'#0078D7', // Primary Blue
'#004578', // Dark Blue
'#71AFE5', // Light Blue
'#D4D4D4', // Neutral Gray
'#A6A6A6', // Dark Gray
'#107C10', // Accent Green
'#FF8C00', // Accent Orange
'#D83B01', // Accent Red
'#F3F2F1', // Soft White
'#1F1F1F', // Dark Background
];
3 changes: 0 additions & 3 deletions ui/components/charts/PieChart.scss
Original file line number Diff line number Diff line change
@@ -1,3 +0,0 @@
.pie-chart .title {
margin-bottom: 4px;
}
73 changes: 35 additions & 38 deletions ui/components/charts/PieChart.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,28 @@
import * as React from 'react';

import { cheerfulFiestaPalette, PieChart as MuiPieChart, PieValueType as MuiPieChartValue } from '@mui/x-charts';
import { MakeOptional } from '@mui/x-charts/internals';
import { Cell, Legend, Pie, PieChart as RePieChart, ResponsiveContainer } from 'recharts';

import { DEFAULT_COLORS } from './Colours';

import './PieChart.scss';

export interface PieChartValue {
label: string;
export interface ChartSlice {
name: string;
value: number;
}

interface Props {
className?: string;
colors?: string[];
data: PieChartValue[];
data: ChartSlice[];
title?: string;
width?: number;
height?: number;
}

interface State {
data: MakeOptional<MuiPieChartValue, 'id'>[];
colors: string[];
slices: ChartSlice[];
total: number;
}

Expand All @@ -32,7 +34,8 @@ export class PieChart extends React.Component<Props, State> {

static getDerivedStateFromProps(props: Props): State {
return {
data: props.data,
slices: props.data,
colors: props.colors?.length ? props.colors : DEFAULT_COLORS,
total: props.data.reduce((acc, item) => acc + item.value, 0),
};
}
Expand All @@ -47,39 +50,33 @@ export class PieChart extends React.Component<Props, State> {
return !this.state.total ? (
<div />
) : (
<div className={'pie-chart flex-column flex-center flex-grow ' + (this.props.className || '')}>
<div className={'pie-chart flex-column flex-center ' + (this.props.className || '')}>
{this.props.title && <h3 className="title">{this.props.title}</h3>}
<MuiPieChart
margin={{ top: 10, left: 10, right: 10, bottom: 10 }}
colors={this.props.colors || cheerfulFiestaPalette}
series={[
{
arcLabel: (item) => `${item.label?.substring(0, 20)}`,
arcLabelMinAngle: 30,
innerRadius: '50%',
highlightScope: { fade: 'global', highlight: 'item' },
faded: { color: 'gray', additionalRadius: -10, innerRadius: 60 },
data: this.state.data || [],
},
]}
slotProps={{
legend: {
hidden: true,
direction: 'column',
position: {
horizontal: 'middle',
vertical: 'bottom',
},
labelStyle: {
fill: 'var(--text-primary-color)',
fontSize: '0.8em',
},
},
}}
width={this.props.width || 250}
height={this.props.height || 250}
/>
<ResponsiveContainer width={this.props.width || '100%'} height={this.props.height || '100%'} debounce={300}>
<RePieChart margin={{ top: 0, right: 0, bottom: 0, left: 0 }}>
<Legend
iconType="circle"
layout="vertical"
verticalAlign="bottom"
iconSize={10}
formatter={renderLegendText}
/>
<Pie data={this.state.slices} dataKey="value" innerRadius={60} outerRadius={100}>
{this.state.slices.map((cell, index) => (
<Cell key={`cell-${index}`} fill={this.state.colors[index % this.state.colors.length]} />
))}
</Pie>
</RePieChart>
</ResponsiveContainer>
</div>
);
}
}

const renderLegendText = (value: string, entry: any) => {
return (
<span className="secondary-text padding-8" style={{ fontWeight: 500 }}>
{value} ({entry.payload.value})
</span>
);
};
2 changes: 1 addition & 1 deletion ui/components/charts/Tile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export class Tile extends React.Component<Props, State> {
<Tooltip text={`${this.props.title}: ${this.props.value}`}>
<div
className={
'tile text-on-communication-background flex-column flex-center padding-8' + (this.props.className || '')
'tile text-on-communication-background flex-column flex-center padding-8 ' + (this.props.className || '')
}
style={{
backgroundColor: this.props.color ? rgbToHex(this.props.color) : undefined,
Expand Down
Loading

0 comments on commit d625b76

Please sign in to comment.