Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
36 changes: 36 additions & 0 deletions src/__stories__/Other/LegendPosition.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import React from 'react';

import type {Meta} from '@storybook/react-webpack5';

import {ChartStory} from '../ChartStory';
import {lineBasicData} from '../__data__';

const meta: Meta<typeof ChartStory> = {
title: 'Other',
component: ChartStory,
};

export default meta;

export const LegendPosition = {
name: 'Legend Position',
args: {
position: 'bottom',
},
argTypes: {
position: {
control: 'inline-radio',
options: ['top', 'bottom'],
},
},
render: (args: {position: 'top' | 'bottom'}) => {
const data = {
...lineBasicData,
legend: {
enabled: true,
position: args.position,
},
};
return <ChartStory data={data} />;
},
};
78 changes: 59 additions & 19 deletions src/__tests__/legend.visual.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,28 +37,31 @@ const pieOverflowedLegendItemsData: ChartData = {
},
};

const piePaginatedLegendData: ChartData = {
legend: {
enabled: true,
type: 'discrete',
},
series: {
data: [
{
type: 'pie',
dataLabels: {enabled: false},
data: range(1, 40).map((i) => ({
name: `Label ${i + 1}`,
value: i,
})),
},
],
},
};

test.describe('Legend', () => {
test.describe('Discrete', () => {
test('Pagination svg', async ({mount}) => {
const data: ChartData = {
legend: {
enabled: true,
type: 'discrete',
},
series: {
data: [
{
type: 'pie',
dataLabels: {enabled: false},
data: range(1, 40).map((i) => ({
name: `Label ${i + 1}`,
value: i,
})),
},
],
},
};
const component = await mount(<ChartTestStory data={data} styles={{width: '150px'}} />);
const component = await mount(
<ChartTestStory data={piePaginatedLegendData} styles={{width: '150px'}} />,
);
await expect(component.locator('svg')).toHaveScreenshot();
const arrowNext = component.getByText('▼');
await arrowNext.click();
Expand Down Expand Up @@ -123,5 +126,42 @@ test.describe('Legend', () => {
await legendItem.click();
await expect(component.locator('svg')).toHaveScreenshot();
});

test.describe('Position top', () => {
test('Basic', async ({mount}) => {
const data = cloneDeep(pieOverflowedLegendItemsData);
set(data, 'legend.position', 'top');
const component = await mount(
<ChartTestStory data={data} styles={{width: '270px'}} />,
);
await expect(component.locator('svg')).toHaveScreenshot();
});

test('With html', async ({mount}) => {
const data = cloneDeep(pieOverflowedLegendItemsData);
set(data, 'legend.position', 'top');
set(data, 'legend.html', true);
const component = await mount(
<ChartTestStory data={data} styles={{width: '270px'}} />,
);
await expect(component.locator('svg')).toHaveScreenshot();
});

test('Paginated', async ({mount}) => {
const data = cloneDeep(piePaginatedLegendData);
set(data, 'legend.position', 'top');

const component = await mount(
<ChartTestStory data={data} styles={{width: '150px'}} />,
);
await expect(component.locator('svg')).toHaveScreenshot();

const arrowNext = component.getByText('▼');
await arrowNext.click();
await expect(component.locator('svg')).toHaveScreenshot();
await arrowNext.click();
await expect(component.locator('svg')).toHaveScreenshot();
});
});
});
});
7 changes: 6 additions & 1 deletion src/components/ChartInner/useChartInnerProps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ export function useChartInnerProps(props: Props) {
preparedXAxis: xAxis,
width,
});

const preparedSplit = useSplit({split: data.split, boundsHeight, chartWidth: width});
const {xScale, yScale} = useAxisScales({
boundsWidth,
Expand Down Expand Up @@ -184,7 +185,11 @@ export function useChartInnerProps(props: Props) {
yScale,
});

const boundsOffsetTop = chart.margin.top;
const boundsOffsetTop =
chart.margin.top +
(preparedLegend?.enabled && preparedLegend.position === 'top'
? preparedLegend.height + preparedLegend.margin
: 0);
// We need to calculate the width of each left axis because the first axis can be hidden
const boundsOffsetLeft =
chart.margin.left +
Expand Down
13 changes: 11 additions & 2 deletions src/hooks/useChartDimensions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const getBottomOffset = (args: {
const {hasAxisRelatedSeries, preparedLegend, preparedXAxis} = args;
let result = 0;

if (preparedLegend?.enabled) {
if (preparedLegend?.enabled && preparedLegend.position === 'bottom') {
result += preparedLegend.height + preparedLegend.margin;
}

Expand All @@ -47,6 +47,14 @@ const getBottomOffset = (args: {
return result;
};

const getTopOffset = ({preparedLegend}: {preparedLegend: PreparedLegend | null}) => {
if (preparedLegend?.enabled && preparedLegend.position === 'top') {
return preparedLegend.height + preparedLegend.margin;
}

return 0;
};

export const useChartDimensions = (args: Args) => {
const {margin, width, height, preparedLegend, preparedXAxis, preparedYAxis, preparedSeries} =
args;
Expand All @@ -59,8 +67,9 @@ export const useChartDimensions = (args: Args) => {
preparedLegend,
preparedXAxis,
});
const topOffset = getTopOffset({preparedLegend});

const boundsHeight = height - margin.top - margin.bottom - bottomOffset;
const boundsHeight = height - margin.top - margin.bottom - bottomOffset - topOffset;

return {boundsWidth, boundsHeight};
}, [margin, width, height, preparedLegend, preparedXAxis, preparedYAxis, preparedSeries]);
Expand Down
6 changes: 5 additions & 1 deletion src/hooks/useSeries/prepare-legend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ export async function getPreparedLegend(args: {
ticks,
colorScale,
html: get(legend, 'html', false),
position: get(legend, 'position', 'bottom'),
};
}

Expand Down Expand Up @@ -259,7 +260,10 @@ export function getLegendComponents(args: {
preparedLegend.height = legendHeight;
}

const top = chartHeight - chartMargin.bottom - preparedLegend.height;
const top =
preparedLegend.position === 'top'
? chartMargin.top
: chartHeight - chartMargin.bottom - preparedLegend.height;
const offset: LegendConfig['offset'] = {
left: chartMargin.left,
top,
Expand Down
6 changes: 6 additions & 0 deletions src/types/chart/legend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,12 @@ export interface ChartLegend {
* @default false
* */
html?: boolean;
/**
* The position of the legend box within the chart area.
*
* @default 'bottom'
* */
position?: 'top' | 'bottom';
}

export interface BaseLegendSymbol {
Expand Down
Loading