Skip to content

Commit

Permalink
Merge pull request #71 from mayurmarakana89/geochart-theme-styling
Browse files Browse the repository at this point in the history
add markers to x & y sliders under geochart & update font family
  • Loading branch information
jolevesq authored Mar 26, 2024
2 parents b31d278 + 3d64a56 commit c39a899
Show file tree
Hide file tree
Showing 3 changed files with 202 additions and 18 deletions.
4 changes: 4 additions & 0 deletions src/chart-parsing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -628,9 +628,12 @@ export function createChartJSOptions<TType extends ChartType>(
x: {
type: chartConfig.geochart.xAxis?.type,
ticks: {
autoSkip: true,
maxTicksLimit: 20,
major: {
enabled: true,
},
padding: 10,
source: 'auto',
// eslint-disable-next-line @typescript-eslint/no-unused-vars
callback: (tickValue: number | Date | string, index: number, ticks: Tick[]): string => {
Expand All @@ -652,6 +655,7 @@ export function createChartJSOptions<TType extends ChartType>(
return '';
},
},
offset: true,
},
};
}
Expand Down
63 changes: 52 additions & 11 deletions src/chart-style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { Theme } from '@mui/material/styles';
export const getSxClasses = (theme: Theme) => {
return {
mainContainer: {
fontFamily: theme.typography.body1.fontFamily,
borderColor: theme.palette.geoViewColor.primary.main,
borderWidth: '2px',
borderStyle: 'solid',
Expand Down Expand Up @@ -47,29 +48,35 @@ export const getSxClasses = (theme: Theme) => {
},
},
dataset: {
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
textAlign: 'center',
},
title: {
fontWeight: 'bold',
fontSize: theme.palette.geoViewFontSize.lg,
fontFamily: theme.typography.h5.fontFamily,
fontWeight: theme.typography.h5.fontWeight,
fontSize: theme.typography.h5.fontSize,
textAlign: 'center',
margin: '10px 0px',
},
xAxisLabel: {
fontFamily: theme.typography.body1.fontFamily,
fontWeight: theme.typography.fontWeightBold,
fontSize: theme.palette.geoViewFontSize.default,
textAlign: 'center',
margin: '10px 0px',
fontWeight: 'bold',
},
yAxisLabel: {
position: 'relative',
margin: 'auto',
fontFamily: theme.typography.body1.fontFamily,
fontWeight: theme.typography.fontWeightBold,
fontSize: theme.palette.geoViewFontSize.default,
position: 'absolute',
top: '45%',
margin: '0 auto',
marginLeft: '20px',
writingMode: 'vertical-rl',
transform: 'rotate(-180deg)',
transformOrigin: 'bottom center',
fontWeight: 'bold',
marginTop: '-15%',
transformOrigin: 'center',
},
uiOptionsResetStates: {
display: 'inline-flex',
Expand All @@ -88,9 +95,9 @@ export const getSxClasses = (theme: Theme) => {
},
},
checkDatasetLabel: {
fontFamily: theme.typography.body1.fontFamily,
display: 'inline-flex',
verticalAlign: 'middle',
marginRight: '20px !important',
},
chartContent: {
position: 'relative',
Expand All @@ -99,13 +106,47 @@ export const getSxClasses = (theme: Theme) => {
'& .MuiSlider-root': {
color: theme.palette.geoViewColor.primary.main,
},
'& .MuiSlider-markLabel-overlap': {
marginTop: '20px',
},
'& .MuiSlider-markLabel-first': {
marginLeft: '-40px',
},
'& .MuiSlider-markLabel-last': {
marginLeft: '40px',
},
'& .markLabel-first': {
fontFamily: theme.typography.body1.fontFamily,
fontSize: theme.palette.geoViewFontSize.sm,
float: 'left',
marginLeft: '-60px',
color: '#000',
opacity: 0.6,
},
'& .markLabel-last': {
fontFamily: theme.typography.body1.fontFamily,
fontSize: theme.palette.geoViewFontSize.sm,
float: 'right',
marginRight: '-60px',
color: '#000',
opacity: 0.6,
},
},
ySliderWrapper: {
height: '75%',
height: '70%',
textAlign: 'center',
marginTop: '-20px',
marginLeft: '20px',
'& .MuiSlider-root': {
color: theme.palette.geoViewColor.primary.main,
},
'& .markLabel-top, & .markLabel-bottom': {
fontFamily: theme.typography.body1.fontFamily,
fontSize: theme.palette.geoViewFontSize.sm,
marginLeft: '-30px',
color: '#000',
opacity: 0.6,
},
},
loadingDatasource: {
backgroundColor: 'transparent',
Expand Down
153 changes: 146 additions & 7 deletions src/chart.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { useLayoutEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { Chart as ChartJS, ChartType, ChartOptions, ChartData, ChartDataset, registerables, ChartConfiguration, Plugin } from 'chart.js';
import { Chart as ChartReact } from 'react-chartjs-2';
Expand All @@ -15,7 +16,7 @@ import {
TypeJsonObject,
StepsPossibilitiesConst,
StepsPossibilities,
DATE_OPTIONS_LONG,
DATE_OPTIONS_AXIS,
GeoChartDatasetOption,
} from './types';
import { SchemaValidator, ValidatorResult } from './chart-schema-validator';
Expand Down Expand Up @@ -1314,7 +1315,7 @@ export function GeoChart<
// Default behavior
// If current chart has time as xAxis
if (inputs?.geochart?.xAxis.type === 'time' || inputs?.geochart?.xAxis.type === 'timeseries') {
return new Date(value).toLocaleDateString(i18n.language, DATE_OPTIONS_LONG);
return new Date(value).toLocaleDateString(i18n.language, DATE_OPTIONS_AXIS);
}

// Default value as is
Expand Down Expand Up @@ -1418,6 +1419,125 @@ export function GeoChart<
return <Box />;
};

/**
* Generate marker labels for the slider values
* @returns The array of slider markers
*/
const getMarkers = useCallback((sliderValues: number | number[], handleSliderValueDisplay: (value: number) => string) => {
const sliderMarks: {
value: number;
label: string;
}[] = [];
if (Array.isArray(sliderValues)) {
for (let i = 0; i < sliderValues.length; i++) {
sliderMarks.push({
value: sliderValues[i],
label: handleSliderValueDisplay(sliderValues[i]),
});
}
}
return sliderMarks;
}, []);

const checkOverlap = (
prev: Element | null,
curr: Element | null,
next: Element | null,
orientation: string | undefined = 'horizontal'
): boolean => {
const labelPadding = 10;

const prevDim = prev ? prev.getBoundingClientRect() : null;
const currDim = curr ? curr.getBoundingClientRect() : null;
const nextDim = next ? next.getBoundingClientRect() : null;
if (prevDim === null || currDim === null || nextDim === null) {
return false;
}
let hasPrevOverlap = false;
let hasNextOverlap = false;

if (prevDim) {
hasPrevOverlap =
orientation === 'vertical' ? prevDim.bottom + labelPadding > currDim.top : prevDim.right + labelPadding > currDim.left;
}
if (nextDim) {
hasNextOverlap =
orientation === 'vertical' ? currDim.bottom + labelPadding > nextDim.top : currDim.right + labelPadding > nextDim.left;
}

return hasPrevOverlap || hasNextOverlap;
};

// eslint-disable-next-line react-hooks/exhaustive-deps
const removeLabelOverlap = (containerId: string) => {
// Log
logger.logTraceCore('UI.SLIDER - window resize event');

// get slider labels
const markers = containerId
? document.getElementById(containerId)?.getElementsByClassName('MuiSlider-markLabel') || []
: document.getElementsByClassName('MuiSlider-markLabel');

for (let i = 0; i < markers.length; i++) {
markers[0].classList.add('MuiSlider-markLabel-first');
markers[markers.length - 1].classList.add('MuiSlider-markLabel-last');
markers[i].classList.remove('MuiSlider-markLabel-overlap');
}

let middleIndices = markers.length % 2 === 0 ? [markers.length / 2, markers.length / 2 + 1] : [Math.floor(markers.length / 2)];
let lastVisibleInFirstHalf = 0;
let firstVisibleInSecondHalf = markers.length - 1;

// Check first half
for (let prevIdx = 0, currIdx = 1; currIdx < markers.length / 2; currIdx++) {
// if there is a collision, set classname and test with the next pips
if (checkOverlap(markers[prevIdx], markers[currIdx], null)) {
markers[currIdx].classList.add('MuiSlider-markLabel-overlap');
} else {
// if there is no collision and reset the startIdx to be the one before the fwdIdx
prevIdx = currIdx - prevIdx !== 1 ? currIdx : prevIdx + 1;
lastVisibleInFirstHalf = currIdx;
}
}

// Check second half
for (let nextIdx = markers.length - 1, currIdx = markers.length - 2; currIdx > markers.length / 2; currIdx--) {
if (checkOverlap(null, markers[currIdx], markers[nextIdx])) {
markers[currIdx].classList.add('MuiSlider-markLabel-overlap');
} else {
// if there is no collision and reset the curIndex to be the one before the testIndex
nextIdx = nextIdx - currIdx !== 1 ? currIdx : nextIdx - 1;
firstVisibleInSecondHalf = currIdx;
}
}

middleIndices.push(lastVisibleInFirstHalf, firstVisibleInSecondHalf);
middleIndices = [...new Set(middleIndices)].sort((a, b) => a - b);

// Check middle elements
for (let testIdx = 0, currIdx = 1; currIdx < middleIndices.length; currIdx++) {
if (
checkOverlap(
markers[middleIndices[testIdx]],
markers[middleIndices[currIdx]],
currIdx === middleIndices.length - 1 ? null : markers[middleIndices[currIdx + 1]]
)
) {
markers[middleIndices[currIdx]].classList.add('MuiSlider-markLabel-overlap');
} else {
testIdx = currIdx - testIdx !== 1 ? currIdx : testIdx + 1;
}
}
};

useLayoutEffect(() => {
// remove overlaping labels
removeLabelOverlap('xAxisSlider');

window.addEventListener('resize', () => removeLabelOverlap);
return () => window.removeEventListener('resize', () => removeLabelOverlap);
}, [removeLabelOverlap]);

/**
* Renders the X Chart Slider JSX.Element or an empty box
* @returns The X Chart Slider JSX.Element or an empty box
Expand All @@ -1427,8 +1547,17 @@ export function GeoChart<
if (inputs && selectedDatasource) {
if (inputs.chart === 'line' && inputs.ui?.xSlider?.display) {
return (
<Box sx={sxClasses.xSliderWrapper}>
<Box id="xAxisSlider" sx={sxClasses.xSliderWrapper}>
<div style={{ height: '16px' }}>
{Array.isArray(xSliderValues) && xSliderValues[0] !== xSliderMin && (
<span className="markLabel-first">{handleSliderXValueDisplay(xSliderMin)}</span>
)}
{Array.isArray(xSliderValues) && xSliderValues[xSliderValues.length - 1] !== xSliderMax && (
<span className="markLabel-last">{handleSliderXValueDisplay(xSliderMax)}</span>
)}
</div>
<Slider
marks={getMarkers(xSliderValues, handleSliderXValueDisplay)}
min={xSliderMin}
max={xSliderMax}
step={xSliderSteps}
Expand Down Expand Up @@ -1456,7 +1585,13 @@ export function GeoChart<
if (inputs.chart === 'line' && inputs.ui?.ySlider?.display) {
return (
<Box sx={sxClasses.ySliderWrapper}>
<div style={{ height: '16px', marginBottom: '10px' }}>
{Array.isArray(ySliderValues) && ySliderValues[ySliderValues.length - 1] !== ySliderMax && (
<span className="markLabel-top">{handleSliderYValueDisplay(ySliderMax)}</span>
)}
</div>
<Slider
marks={getMarkers(ySliderValues, handleSliderYValueDisplay)}
min={ySliderMin}
max={ySliderMax}
step={ySliderSteps}
Expand All @@ -1466,6 +1601,11 @@ export function GeoChart<
onValueDisplay={handleSliderYValueDisplay}
onValueDisplayAriaLabel={handleSliderYValueDisplay}
/>
<div style={{ height: '16px' }}>
{Array.isArray(ySliderValues) && ySliderValues[0] !== ySliderMin && (
<span className="markLabel-bottom">{handleSliderYValueDisplay(ySliderMin)}</span>
)}
</div>
</Box>
);
}
Expand Down Expand Up @@ -1597,7 +1737,7 @@ export function GeoChart<
if (Object.keys(datasetRegistry).length > 1) {
const label = chartType === 'pie' || chartType === 'doughnut' ? `${t('geochart.category')}:` : '';
return (
<>
<div>
<Typography sx={sxClasses.checkDatasetWrapperLabel}>{label}</Typography>
{Object.entries(datasetRegistry)
.filter(([, dsOption]: [string, GeoChartDatasetOption]) => {
Expand All @@ -1606,7 +1746,6 @@ export function GeoChart<
.map(([dsLabel, dsOption]: [string, GeoChartDatasetOption], idx: number) => {
let color;
if (chartType === 'line' || chartType === 'bar') color = dsOption.borderColor as string;

return (
<Box sx={sxClasses.checkDatasetWrapper} key={dsLabel || idx}>
<Checkbox
Expand All @@ -1621,7 +1760,7 @@ export function GeoChart<
</Box>
);
})}
</>
</div>
);
}
}
Expand Down Expand Up @@ -1679,7 +1818,7 @@ export function GeoChart<
// The xs: 1, 11 and 12 used here are as documented online
return (
<Paper sx={{ ...sx, ...sxClasses.mainGeoChartContainer }}>
<Grid container>
<Grid container sx={{ m: '20px' }}>
<Grid item xs={12}>
<Box sx={sxClasses.header}>
{renderDatasourceSelector()}
Expand Down

0 comments on commit c39a899

Please sign in to comment.