Skip to content

Commit

Permalink
Merge branch 'trunk' into fix/atomic-promote-with-blaze
Browse files Browse the repository at this point in the history
  • Loading branch information
BogdanUngureanu committed Dec 18, 2024
2 parents 6d4b695 + f52f223 commit 30b8fd3
Show file tree
Hide file tree
Showing 55 changed files with 1,278 additions and 500 deletions.
17 changes: 17 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: minor
Type: added

Chart library: add legend component
4 changes: 4 additions & 0 deletions projects/js-packages/charts/changelog/add-charts-bar-multi
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: patch
Type: added

Adding support for multiple data series to the Bar chart component.
1 change: 1 addition & 0 deletions projects/js-packages/charts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"@visx/responsive": "3.12.0",
"@visx/axis": "^3.12.0",
"@visx/group": "^3.12.0",
"@visx/legend": "^3.12.0",
"@visx/scale": "^3.12.0",
"@visx/shape": "^3.12.0",
"@visx/text": "3.12.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
.bar-chart {
position: relative;

&-legend {
margin-top: 1rem;
}
}
146 changes: 97 additions & 49 deletions projects/js-packages/charts/src/components/bar-chart/bar-chart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,51 +7,42 @@ import { useTooltip } from '@visx/tooltip';
import clsx from 'clsx';
import { FC, useCallback, type MouseEvent } from 'react';
import { useChartTheme } from '../../providers/theme';
import { Legend } from '../legend';
import { BaseTooltip } from '../tooltip';
import styles from './bar-chart.module.scss';
import type { BaseChartProps, DataPoint } from '../shared/types';
import type { BaseChartProps, SeriesData } from '../shared/types';

interface BarChartProps extends BaseChartProps {
/**
* Array of data points to display in the chart
*/
data: DataPoint[];
}
interface BarChartProps extends BaseChartProps< SeriesData[] > {}

type BarChartTooltipData = { value: number; xLabel: string; yLabel: string; seriesIndex: number };

const BarChart: FC< BarChartProps > = ( {
data,
width,
height,
margin = { top: 20, right: 20, bottom: 40, left: 40 },
withTooltips = false,
showLegend = false,
legendOrientation = 'horizontal',
className,
} ) => {
const theme = useChartTheme();
const { tooltipOpen, tooltipLeft, tooltipTop, tooltipData, hideTooltip, showTooltip } =
useTooltip< DataPoint >();

const margins = margin;
const xMax = width - margins.left - margins.right;
const yMax = height - margins.top - margins.bottom;

const xScale = scaleBand< string >( {
range: [ 0, xMax ],
domain: data.map( d => d.label ),
padding: 0.2,
} );

const yScale = scaleLinear< number >( {
range: [ yMax, 0 ],
domain: [ 0, Math.max( ...data.map( d => d.value ) ) ],
} );
useTooltip< BarChartTooltipData >();

const handleMouseMove = useCallback(
( event: MouseEvent< SVGRectElement >, datum: DataPoint ) => {
(
event: MouseEvent< SVGRectElement >,
value: number,
xLabel: string,
yLabel: string,
seriesIndex: number
) => {
const coords = localPoint( event );
if ( ! coords ) return;

showTooltip( {
tooltipData: datum,
tooltipData: { value, xLabel, yLabel, seriesIndex },
tooltipLeft: coords.x,
tooltipTop: coords.y - 10,
} );
Expand All @@ -63,45 +54,102 @@ const BarChart: FC< BarChartProps > = ( {
hideTooltip();
}, [ hideTooltip ] );

if ( ! data?.length ) {
return <div className={ clsx( 'bar-chart-empty', styles[ 'bat-chart-empty' ] ) }>Empty...</div>;
}

const margins = margin;
const xMax = width - margins.left - margins.right;
const yMax = height - margins.top - margins.bottom;

// Get labels for x-axis from the first series (assuming all series have same labels)
const labels = data[ 0 ].data?.map( d => d?.label );

// Create scales
const xScale = scaleBand< string >( {
range: [ 0, xMax ],
domain: labels,
padding: 0.2,
} );

const innerScale = scaleBand( {
range: [ 0, xScale.bandwidth() ],
domain: data.map( ( _, i ) => i.toString() ),
padding: 0.1,
} );

const yScale = scaleLinear< number >( {
range: [ yMax, 0 ],
domain: [
0,
Math.max( ...data.map( series => Math.max( ...series.data.map( d => d?.value || 0 ) ) ) ),
],
} );

// Create legend items from group labels, this iterates over groups rather than data points
const legendItems = data.map( ( group, index ) => ( {
label: group.label, // Label for each unique group
value: '', // Empty string since we don't want to show a specific value
color: theme.colors[ index % theme.colors.length ],
} ) );

return (
<div className={ clsx( 'bar-chart', className, styles[ 'bar-chart' ] ) }>
<svg width={ width } height={ height }>
<Group left={ margins.left } top={ margins.top }>
{ data.map( d => {
const handleBarMouseMove = event => handleMouseMove( event, d );

return (
<Bar
key={ `bar-${ d.label }` }
x={ xScale( d.label ) }
y={ yScale( d.value ) }
width={ xScale.bandwidth() }
height={ yMax - ( yScale( d.value ) ?? 0 ) }
fill={ theme.colors[ 0 ] }
onMouseMove={ withTooltips ? handleBarMouseMove : undefined }
onMouseLeave={ withTooltips ? handleMouseLeave : undefined }
/>
);
} ) }
{ data.map( ( series, seriesIndex ) => (
<Group key={ seriesIndex }>
{ series.data.map( d => {
const xPos = xScale( d.label );
if ( xPos === undefined ) return null;

const barWidth = innerScale.bandwidth();
const barX = xPos + ( innerScale( seriesIndex.toString() ) ?? 0 );

const handleBarMouseMove = event =>
handleMouseMove( event, d.value, d.label, series.label, seriesIndex );

return (
<Bar
key={ `bar-${ seriesIndex }-${ d.label }` }
x={ barX }
y={ yScale( d.value ) }
width={ barWidth }
height={ yMax - ( yScale( d.value ) ?? 0 ) }
fill={ theme.colors[ seriesIndex % theme.colors.length ] }
onMouseMove={ withTooltips ? handleBarMouseMove : undefined }
onMouseLeave={ withTooltips ? handleMouseLeave : undefined }
/>
);
} ) }
</Group>
) ) }
<AxisLeft scale={ yScale } />
<AxisBottom scale={ xScale } top={ yMax } />
</Group>
</svg>

{ withTooltips && tooltipOpen && tooltipData && (
<BaseTooltip
data={ {
label: tooltipData.label,
value: tooltipData.value,
} }
top={ tooltipTop }
left={ tooltipLeft }
<BaseTooltip top={ tooltipTop } left={ tooltipLeft }>
<div>
<div>{ tooltipData.yLabel }</div>
<div>
{ tooltipData.xLabel }: { tooltipData.value }
</div>
</div>
</BaseTooltip>
) }

{ showLegend && (
<Legend
items={ legendItems }
orientation={ legendOrientation }
className={ styles[ 'bar-chart-legend' ] }
/>
) }
</div>
);
};

BarChart.displayName = 'BarChart';

export default BarChart;
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import BarChart from '../index';
import data from './sample-data';
import type { Meta } from '@storybook/react';
import type { Meta, StoryObj } from '@storybook/react';

export default {
title: 'JS Packages/Charts/Types/Bar Chart',
Expand All @@ -17,27 +17,67 @@ export default {
],
} satisfies Meta< typeof BarChart >;

const Template = args => <BarChart { ...args } />;
type StoryType = StoryObj< typeof BarChart >;

export const Default = Template.bind( {} );
Default.args = {
width: 500,
height: 300,
margin: { top: 20, right: 20, bottom: 40, left: 40 },
withTooltips: false,
data: data[ 0 ].data,
// Default story with multiple series
export const Default: StoryType = {
args: {
width: 800,
height: 500,
withTooltips: true,
data: [ data[ 0 ], data[ 1 ], data[ 2 ] ], // limit to 3 series for better readability
showLegend: false,
legendOrientation: 'horizontal',
},
};

export const WithTooltips = Template.bind( {} );
WithTooltips.args = {
...Default.args,
withTooltips: true,
// Story with single data series
export const SingleSeries: StoryType = {
args: {
...Default.args,
data: [ data[ 0 ] ],
},
parameters: {
docs: {
description: {
story: 'Bar chart with a single data series.',
},
},
},
};

WithTooltips.parameters = {
docs: {
description: {
story: 'Bar chart with interactive tooltips that appear on hover.',
// Story without tooltip
export const ManyDataSeries: StoryType = {
args: {
...Default.args,
width: 1200,
height: 700,
data,
},
parameters: {
docs: {
description: {
story: 'Bar chart with many data series.',
},
},
},
};

export const WithLegend = {
args: {
...Default.args,
data,
showTooltips: true,
showLegend: true,
legendOrientation: 'horizontal',
},
};

export const WithVerticalLegend = {
args: {
...WithLegend.args,
data: [ data[ 0 ] ],
showLegend: true,
legendOrientation: 'vertical',
},
};
Loading

0 comments on commit 30b8fd3

Please sign in to comment.