Skip to content

Commit

Permalink
fix: Update the dots container dynamically when dimensions change (#17)
Browse files Browse the repository at this point in the history
  • Loading branch information
anjuca154 authored Jan 4, 2024
1 parent b148774 commit e9624d5
Show file tree
Hide file tree
Showing 8 changed files with 229 additions and 117 deletions.
80 changes: 32 additions & 48 deletions src/lib/dot-matrix/Chart.tsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,24 @@
import React, { useMemo } from "react";

import { getNumberOfDots, getStyles, hasOverlapping } from "./utils/utils";
import {
DEFAULT_COLUMNS,
DEFAULT_DOT_WIDTH,
DEFAULT_ROWS,
Elements
} from "./constants";
import { ChartProps, DataPointType } from "./types";
import { getGradient, getStyles } from "./utils/utils";
import { DEFAULT_COLUMNS, DEFAULT_DOT_WIDTH, Elements } from "./constants";
import { ChartProps, DotsType } from "./types";
import classes from "./styles.module.scss";

const Chart = (props: ChartProps): JSX.Element => {
const {
dimensions = {},
styles,
dotsToBeMapped,
fractionalDots,
total,
width,
spaceBetweenDots
} = props;

const { rows = DEFAULT_ROWS, columns = DEFAULT_COLUMNS } = dimensions;
const { columns = DEFAULT_COLUMNS } = dimensions;

const dotWidth = useMemo(
() => (width ? width / columns - spaceBetweenDots : DEFAULT_DOT_WIDTH),
[width]
[width, columns, spaceBetweenDots]
);

return (
Expand All @@ -36,42 +29,33 @@ const Chart = (props: ChartProps): JSX.Element => {
...getStyles(Elements.DotsContainer, styles)
}}
>
{dotsToBeMapped?.map((dataItem: DataPointType, rowIndex: number) => (
<React.Fragment key={`${dataItem.name}-${rowIndex}`}>
{dataItem &&
[...Array(getNumberOfDots(dataItem, rows, columns, total))].map(
(item: null, columnIndex: number) => (
<div
id="dot-matrix-dots"
key={`${dataItem.name}-${rowIndex}-${columnIndex}`}
>
{hasOverlapping(fractionalDots, rowIndex, columnIndex) ? (
<div
className={classes.eachDot}
style={{
backgroundImage: `linear-gradient(to right, ${
dotsToBeMapped[rowIndex - 1].color
} 20%, ${dataItem?.color} 50%)`,
width: `${dotWidth}px`,
height: `${dotWidth}px`,
...getStyles(Elements.Dot, styles)
}}
/>
) : (
<div
className={classes.eachDot}
style={{
backgroundColor: dataItem?.color,
width: `${dotWidth}px`,
height: `${dotWidth}px`,
...getStyles(Elements.Dot, styles)
}}
id={`each-category-${rowIndex}-${columnIndex}`}
/>
)}
</div>
)
)}
{dotsToBeMapped?.map((dataItem: DotsType, rowIndex: number) => (
<React.Fragment key={`${dataItem.id}-${rowIndex}`}>
{[...Array(dataItem.count)].map((item: null, columnIndex: number) => (
<div
id="dot-matrix-dots"
key={`${dataItem.id}-${rowIndex}-${columnIndex}`}
>
<div
className={classes.eachDot}
style={{
backgroundColor: dataItem?.color,
backgroundImage:
Array.isArray(dataItem.gradientColors) &&
Array.isArray(dataItem.gradientPercentage)
? getGradient(
dataItem.gradientColors,
dataItem.gradientPercentage
)
: "",
width: `${dotWidth}px`,
height: `${dotWidth}px`,
...getStyles(Elements.Dot, styles)
}}
id={`each-category-${rowIndex}-${columnIndex}`}
/>
</div>
))}
</React.Fragment>
))}
</div>
Expand Down
25 changes: 16 additions & 9 deletions src/lib/dot-matrix/DotMatrix.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from "react";

import { useDotMatrix } from "./custom-hooks/useDotMatrix";
import { getLegendPosition, getStyles } from "./utils/utils";
import { getLegendPosition, getStyles, getUniqueDots } from "./utils/utils";
import {
DEFAULT_COLUMNS,
DEFAULT_GAP,
Expand Down Expand Up @@ -30,13 +30,13 @@ const DotMatrix = (props: DotMatrixPropType): JSX.Element => {

const width = useChartContainerWidth("dots-container", [
showLegend,
legendPosition
legendPosition,
dimensions.rows || DEFAULT_ROWS,
dimensions.columns || DEFAULT_COLUMNS,
spaceBetweenDots
]);

const [dotsToBeMapped, totalDots, fractionalDots] = useDotMatrix(
dataPoints,
dimensions
);
const dotsToBeMapped = useDotMatrix(dataPoints, dimensions);

return (
<div className={classes.container}>
Expand All @@ -52,13 +52,20 @@ const DotMatrix = (props: DotMatrixPropType): JSX.Element => {
styles={styles}
dimensions={dimensions}
dotsToBeMapped={dotsToBeMapped}
total={totalDots}
width={width}
fractionalDots={fractionalDots}
spaceBetweenDots={spaceBetweenDots}
/>
</div>
{showLegend && <Legend styles={styles} data={dotsToBeMapped} />}
{showLegend && (
<Legend
styles={styles}
data={getUniqueDots(dotsToBeMapped).filter(
(dots) =>
dots.color ||
(dots?.gradientColors && dots.gradientColors.length > 0)
)}
/>
)}
</div>
</div>
);
Expand Down
8 changes: 4 additions & 4 deletions src/lib/dot-matrix/Legend.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from "react";

import { getStyles } from "./utils/utils";
import { DataPointType, LegendProps } from "./types";
import { DotsType, LegendProps } from "./types";
import { Elements } from "./constants";
import classes from "./styles.module.scss";

Expand All @@ -14,12 +14,12 @@ const Legend = (props: LegendProps): JSX.Element => {
className={classes.legends}
style={{ ...getStyles(Elements.LegendContainer, styles) }}
>
{data?.map((point: DataPointType, index) => (
{data?.map((point: DotsType, index) => (
<div className={classes.legend} key={`${point.name}-${index}`}>
<div
className={classes.legendDot}
style={{
backgroundColor: point.color,
backgroundColor: point.color || (point.gradientColors && point.gradientColors[0]),
...getStyles(Elements.LegendDot, styles)
}}
/>
Expand All @@ -35,4 +35,4 @@ const Legend = (props: LegendProps): JSX.Element => {
);
};

export default Legend;
export default Legend;
14 changes: 6 additions & 8 deletions src/lib/dot-matrix/custom-hooks/useChartContainerWidth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,16 @@ import { findContainerWidth } from "../utils/utils";

const useChartContainerWidth = (
id: string,
dependencyArray: Array<boolean | string>
dependencyArray: Array<boolean | string | number>
): number => {
const [width, setWidth] = useState<number>(DEFAULT_DOT_CONTAINER_WIDTH);

useEffect(() => {
updateContainerWidth();
}, []);

useEffect(() => {
updateContainerWidth();
}, [...dependencyArray]);

useEffect(() => {
if (typeof window !== undefined) {
if (typeof window !== "undefined") {
window.addEventListener("resize", updateContainerWidth);

return () => {
Expand All @@ -29,8 +25,10 @@ const useChartContainerWidth = (

const updateContainerWidth = (): void => {
let widthValue;
if (typeof window !== undefined) widthValue = findContainerWidth(id);
if (widthValue) setWidth(widthValue);
if (typeof window !== "undefined") {
widthValue = findContainerWidth(id);
setWidth(widthValue);
}
};
return width;
};
Expand Down
84 changes: 68 additions & 16 deletions src/lib/dot-matrix/custom-hooks/useDotMatrix.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import { useMemo } from "react";

import { DataPointType, DimensionProp } from "../types";
import { DataPointType, DimensionProp, DotsType } from "../types";
import { COLOR_PALETTE, DEFAULT_COLUMNS, DEFAULT_ROWS } from "../constants";
import { isColorAlreadyUsed } from "../utils/utils";
import {
isColorAlreadyUsed,
isDecimal,
mergeAndSortById
} from "../utils/utils";

export const useDotMatrix = (
dataPoints: DataPointType[],
dimensions: DimensionProp
): [DataPointType[], number, number[]] => {
const [dotsToBeMapped, totalDots] = useMemo(() => {
): DotsType[] => {
const [dotsWithColor, totalDots] = useMemo(() => {
const dotMatrixData: DataPointType[] = [];
let totalCount = 0;
if (dataPoints) {
Expand All @@ -25,23 +29,71 @@ export const useDotMatrix = (
dotMatrixData.push({ ...point, color });
});
}
return [dotMatrixData, totalCount]
return [dotMatrixData, totalCount];
}, [dataPoints]);

// Calculates fractional part of a category based on the provided data points
// relative to the total number of dots and dimension
const fractionalDots: number[] = useMemo(() => {
const fractionalParts: Array<number> = [];
//finding the number of dots to be rendered relative to the total number of dots and dimension
const dots = useMemo(() => {
const completeDots: number[] = [];
if (totalDots) {
dotsToBeMapped?.forEach((point: DataPointType) => {
dotsWithColor?.forEach((point: DataPointType) => {
const { rows = DEFAULT_ROWS, columns = DEFAULT_COLUMNS } = dimensions;
const pointPercentage = point.count / totalDots;
const dotsCount = pointPercentage * rows * columns;
const dotFraction = dotsCount - Math.floor(dotsCount);
fractionalParts?.push(dotFraction);
completeDots.push(dotsCount);
});
}
return fractionalParts;
}, [totalDots]);
return [dotsToBeMapped, totalDots, fractionalDots];
};
return completeDots;
}, [totalDots, dimensions.columns, dimensions.rows]);

const dotsToBeMapped = useMemo(() => {
//Calculating the dots with gradient and subtracting the gradient part from the total number of dots
const currentDots = [...dots];
const gradientDots: DotsType[] = [];
for (let i = 0; i < currentDots.length - 1; i++) {
if (isDecimal(currentDots[i])) {
const roundedCurrentDotCount = Math.floor(currentDots[i]);
let remainingDecimal =
1 - (currentDots[i] - roundedCurrentDotCount);
const gradientColors = [dotsWithColor[i].color];
const percentage = [currentDots[i] - roundedCurrentDotCount];
let j = i + 1;
while (remainingDecimal !== 0 && j < currentDots.length) {
if (currentDots[j] >= remainingDecimal) {
percentage.push(remainingDecimal);
currentDots[j] = currentDots[j] - remainingDecimal;
remainingDecimal = 0;
} else {
remainingDecimal = remainingDecimal - dots[j];
percentage.push(currentDots[j] - Math.floor(currentDots[j]));
currentDots[j] = 0;
}
currentDots[i] = roundedCurrentDotCount;
gradientColors.push(dotsWithColor[j].color);
j++;
}
gradientDots.push({
id: i,
count: 1,
name: dotsWithColor[i].name,
gradientColors: gradientColors,
gradientPercentage: percentage
});
}
}
//Calculating the remaining dots with single color
const singleDots: DotsType[] = [];
for (let i = 0; i < currentDots.length; i++) {
singleDots.push({
id: i,
name: dotsWithColor[i].name,
count: Math.round(currentDots[i]),
color: dotsWithColor[i].color
});
}
//merging both arrays and sorting it with respect to id
return mergeAndSortById(gradientDots, singleDots);
}, [dataPoints, dimensions.rows, dimensions.columns]);

return dotsToBeMapped;
};
15 changes: 11 additions & 4 deletions src/lib/dot-matrix/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@ export interface DataPointType {
export interface ChartProps {
dimensions?: DimensionProp;
styles: StyleProp;
dotsToBeMapped: DataPointType[];
fractionalDots: number[];
total: number;
dotsToBeMapped: DotsType[];
width: number;
spaceBetweenDots: number;
}
Expand All @@ -29,7 +27,7 @@ export type StyleProp = {

export interface LegendProps {
styles: StyleProp;
data: DataPointType[];
data: DotsType[];
}

export interface DotMatrixPropType {
Expand All @@ -52,3 +50,12 @@ export interface DotMatrixPropType {
| "bottom-end";
styles?: StyleProp;
}

export interface DotsType {
id: number;
name?: string;
count?: number;
color?: string;
gradientColors?: (string | undefined)[];
gradientPercentage?: number[];
}
Loading

0 comments on commit e9624d5

Please sign in to comment.