Skip to content

Commit

Permalink
feat: chart legend support px rem em % unit type
Browse files Browse the repository at this point in the history
  • Loading branch information
gmuralidharreddy authored and chejimmy committed Mar 21, 2024
1 parent 0e31d25 commit 4e023e6
Show file tree
Hide file tree
Showing 8 changed files with 313 additions and 54 deletions.
131 changes: 112 additions & 19 deletions packages/react-components/src/hooks/useECharts/useResizeableEChart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,33 @@ import {
} from '../../components/chart/eChartsConstants';
import { ResizeCallbackData } from 'react-resizable';
import { useMeasure } from 'react-use';
import { pxToPercent } from '../utils/pxToPercentage';
import {
emToPx,
perToPx,
pxToEm,
pxToPercent,
pxToRem,
remToPx,
} from '../utils/pxConversion';
import { ChartOptions } from '../../components/chart/types';
import { parseValueAndUnit } from '../utils/parseValueAndUnit';

export const getDecimalFromPercentage = (n: string): number => {
if (!n.includes('%') || n === '') {
return 0;
export const calculateAdjustedWidth = (
width: number,
staticWidth: number,
unit: string,
value: number
): number => {
switch (unit) {
case '%':
return width * (1 - perToPx(value)) - staticWidth;
case 'rem':
return width - staticWidth - remToPx(value);
case 'em':
return width - staticWidth - emToPx(value);
default:
return width - staticWidth - value;
}
const convertStringToNumber = parseInt(n.split('%')[0]);

return convertStringToNumber / 100;
};

export const getChartWidth = (
Expand All @@ -30,25 +47,44 @@ export const getChartWidth = (
isLegendVisible?: boolean,
legendWidth?: string,
isBottomAligned?: boolean
) => {
): number => {
if (!(isLegendVisible && legendWidth) || isBottomAligned) {
return width - staticWidth;
}

return width * (1 - getDecimalFromPercentage(legendWidth)) - staticWidth;
const { value, unit } = parseValueAndUnit(legendWidth);
return calculateAdjustedWidth(width, staticWidth, unit, value);
};

export const calculateAdjustedHight = (
height: number,
unit: string,
value: number
) => {
switch (unit) {
case '%':
return height * (1 - perToPx(value));
case 'rem':
return height - remToPx(value);
case 'em':
return height - emToPx(value);
default:
return height - value;
}
};

export const getChartHeight = (
height: number,
isLegendVisible?: boolean,
legendHeight?: string,
isBottomAligned?: boolean
) => {
): number => {
if (!(isLegendVisible && legendHeight && isBottomAligned)) {
return height;
}

return height * (1 - getDecimalFromPercentage(legendHeight));
const { value, unit } = parseValueAndUnit(legendHeight);
return calculateAdjustedHight(height, unit, value);
};

export const getRightLegendWidth = (
Expand Down Expand Up @@ -76,6 +112,52 @@ export const getRightLegendHeight = (
return isBottomAligned ? height - chartHeight : height;
};

export const getLegendHeight = (
height: number,
chartHeight: number,
unitType: string
) => {
const size = height - chartHeight;

switch (unitType) {
case '%':
return `${pxToPercent(size, height).toFixed(0)}%`;
case 'rem':
return `${pxToRem(size).toFixed(0)}rem`;
case 'em':
return `${pxToEm(size).toFixed(0)}em`;
default:
return `${size.toFixed(0)}px`;
}
};

export const getLegendWidth = (
width: number,
chartWidth: number,
leftLegendWidth: number,
unitType: string
) => {
const adjustedWidth = leftLegendWidth
? width - chartWidth - leftLegendWidth
: width - chartWidth;

switch (unitType) {
case '%':
return `${pxToPercent(adjustedWidth, width).toFixed(0)}%`;
case 'rem':
return `${pxToRem(adjustedWidth).toFixed(0)}rem`;
case 'em':
return `${pxToEm(adjustedWidth).toFixed(0)}em`;
default:
return `${adjustedWidth.toFixed(0)}px`;
}
};

const getLegendStyleType = (type: string): string => {
const styleTypes = ['%', 'rem', 'em', 'px']; // expected style types
return styleTypes.find((styleType) => type.includes(styleType)) ?? 'px';
};

/**
* hook to set up the size of an echarts instance within the base chart component
* note: the chart sits along side a legend table which means that the
Expand All @@ -101,12 +183,16 @@ export const useResizeableEChart = (
const { width, height } = size;
const legendWidth = legend?.width;
const legendHeight = legend?.height;
const legendUnitType = useMemo(
() => getLegendStyleType(legendWidth ?? '0px'),
[legendWidth]
);
const isLegendVisible = legend?.visible ?? false;

const [leftLegendRef, { width: leftLegendWidth }] =
useMeasure<HTMLDivElement>();

const [chartWidth, setChartWidth] = useState(
const [chartWidth, setChartWidth] = useState<number>(
getChartWidth(
width,
leftLegendWidth,
Expand All @@ -115,7 +201,7 @@ export const useResizeableEChart = (
isBottomAligned
)
);
const [chartHeight, setChartHeight] = useState(
const [chartHeight, setChartHeight] = useState<number>(
getChartHeight(height, isLegendVisible, legendHeight, isBottomAligned)
);
const rightLegendWidth = getRightLegendWidth(
Expand All @@ -136,21 +222,28 @@ export const useResizeableEChart = (
_event.stopPropagation();

if (isBottomAligned) {
setChartHeight(data.size.height);
setChartHeight(parseInt(data.size.height.toFixed(0)));
onChartOptionsChange?.({
legend: {
...legend,
height: pxToPercent(height - data.size.height, height),
height: getLegendHeight(
height,
parseInt(data.size.height.toFixed(0)),
legendUnitType
),
},
});
} else {
setChartWidth(data.size.width);
setChartWidth(parseInt(data.size.width.toFixed(0)));
onChartOptionsChange?.({
legend: {
...legend,
width: leftLegendWidth
? pxToPercent(width - data.size.width - leftLegendWidth, width)
: pxToPercent(width - data.size.width, width),
width: getLegendWidth(
width,
leftLegendWidth,
parseInt(data.size.width.toFixed(0)),
legendUnitType
),
},
});
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import { renderHook, act } from '@testing-library/react-hooks';
import {
useResizeableEChart,
getDecimalFromPercentage,
getChartHeight,
getChartWidth,
getRightLegendHeight,
getRightLegendWidth,
calculateAdjustedWidth,
calculateAdjustedHight,
getLegendHeight,
getLegendWidth,
} from './useResizeableEChart';
import { ChartOptions } from '../../components/chart/types';
import { ResizeCallbackData } from 'react-resizable';
import { perToPx } from '../utils/pxConversion';

describe('useResizeableEChart', () => {
describe('render chart when legend is not visible', () => {
Expand Down Expand Up @@ -144,21 +148,6 @@ describe('useResizeableEChart', () => {
});
});

describe('render getDecimalFromPercentage', () => {
it('should return 0 if the input does not contain "%" or is empty', () => {
expect(getDecimalFromPercentage('')).toBe(0);
expect(getDecimalFromPercentage('123')).toBe(0);
expect(getDecimalFromPercentage('123abc')).toBe(0);
expect(getDecimalFromPercentage('abc')).toBe(0);
});

it('should return the decimal value of the input', () => {
expect(getDecimalFromPercentage('100%')).toBe(1);
expect(getDecimalFromPercentage('50%')).toBe(0.5);
expect(getDecimalFromPercentage('25%')).toBe(0.25);
});
});

describe('render getChartWidth', () => {
it('should return width - staticWidth when isLegendVisible and legendWidth are falsy or isBottomAligned is true', () => {
expect(getChartWidth(100, 10)).toBe(90);
Expand All @@ -183,7 +172,8 @@ describe('useResizeableEChart', () => {
const isLegendVisible = true;
const legendHeight = '50%';
const isBottomAligned = true;
const expected = height * (1 - getDecimalFromPercentage(legendHeight));
const expected =
height * (1 - perToPx(parseInt(legendHeight.split('%')[0])));
const result = getChartHeight(
height,
isLegendVisible,
Expand Down Expand Up @@ -261,3 +251,61 @@ describe('useResizeableEChart', () => {
});
});
});

describe('calculateAdjustedWidth', () => {
it('should correctly adjust width when unit is percentage', () => {
const result = calculateAdjustedWidth(100, 10, '%', 20);
expect(result).toEqual(70);
});

it('should correctly adjust width when unit is neither percentage, rem, nor em', () => {
const result = calculateAdjustedWidth(100, 10, 'px', 15);
expect(result).toEqual(75);
});
});

describe('calculateAdjustedHight', () => {
it('should correctly adjust height when unit is percentage', () => {
const result = calculateAdjustedHight(100, '%', 20);
expect(result).toEqual(80);
});

it('should correctly adjust height when unit is neither percentage, rem, nor em', () => {
const result = calculateAdjustedHight(100, 'px', 15);
expect(result).toEqual(85);
});
});

describe('getLegendHeight', () => {
it('should return the correct legend height when unit type is percentage', () => {
const result = getLegendHeight(100, 30, '%');
expect(result).toEqual('70%');
});

it('should return the correct legend height when unit type is rem', () => {
const result = getLegendHeight(100, 30, 'rem');
expect(result).toEqual('4rem');
});

it('should return the correct legend height when unit type is neither percentage, rem, nor em', () => {
const result = getLegendHeight(100, 30, 'px');
expect(result).toEqual('70px');
});
});

describe('getLegendWidth', () => {
it('should return the correct legend width when unit type is percentage', () => {
const result = getLegendWidth(100, 70, 10, '%');
expect(result).toEqual('20%');
});

it('should return the correct legend width when unit type is rem', () => {
const result = getLegendWidth(100, 70, 10, 'rem');
expect(result).toEqual('1rem');
});

it('should return the correct legend width when unit type is neither percentage, rem, nor em', () => {
const result = getLegendWidth(100, 70, 10, 'px');
expect(result).toEqual('20px');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { parseValueAndUnit } from './parseValueAndUnit';

describe('parseValueAndUnit', () => {
it('should parse an input with only a number', () => {
expect(parseValueAndUnit('100')).toEqual({ value: 100, unit: 'px' });
});

it('should parse an input with a number followed by a unit', () => {
expect(parseValueAndUnit('75%')).toEqual({ value: 75, unit: '%' });
});

it('should parse an input with a number followed by a unit', () => {
expect(parseValueAndUnit('50em')).toEqual({ value: 50, unit: 'em' });
});

it('should return 0 for value and px for unit if input is just a string', () => {
expect(parseValueAndUnit('')).toEqual({ value: 0, unit: 'px' });
});

it('should handle input with spaces and mixed units', () => {
expect(parseValueAndUnit(' 200 px ')).toEqual({ value: 200, unit: 'px' });
});

it('should default to 0 for value if number is not parseable', () => {
expect(parseValueAndUnit('px')).toEqual({ value: 0, unit: 'px' });
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export const parseValueAndUnit = (
input: string
): { value: number; unit: string } => {
const value = parseInt(input);
const unit = input.replace(/[0-9]/g, '').trim();
return { value: value || 0, unit: unit || 'px' };
};

0 comments on commit 4e023e6

Please sign in to comment.