Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add delta calculation #675

Draft
wants to merge 19 commits into
base: main
Choose a base branch
from
Draft
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
128 changes: 118 additions & 10 deletions src/components/chart-elements/AreaChart/AreaChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
Legend,
Line,
AreaChart as ReChartsAreaChart,
ReferenceArea,
ResponsiveContainer,
Tooltip,
XAxis,
Expand All @@ -33,6 +34,8 @@ import {
tremorTwMerge,
} from "lib";
import { CurveType } from "../../../lib/inputTypes";
import DeltaCalculationProps from "components/chart-elements/common/DeltaCalculationProps";
import DeltaCalculationReferenceShape from "components/chart-elements/common/DeltaCalculationReferenceShape";

export interface AreaChartProps extends BaseChartProps {
stack?: boolean;
Expand Down Expand Up @@ -69,18 +72,53 @@ const AreaChart = React.forwardRef<HTMLDivElement, AreaChartProps>((props, ref)
maxValue,
connectNulls = false,
allowDecimals = true,
enableDeltaCalculation = false,
isIncreasePositive = true,
noDataText,
className,
onValueChange,
...other
} = props;
const [deltaCalculation, setDeltaCalculation] = useState<DeltaCalculationProps | null>(null);
const [legendHeight, setLegendHeight] = useState(60);
const [activeDot, setActiveDot] = useState<ActiveDot | undefined>(undefined);
const [activeLegend, setActiveLegend] = useState<string | undefined>(undefined);
const categoryColors = constructCategoryColors(categories, colors);

const yAxisDomain = getYAxisDomain(autoMinValue, minValue, maxValue);
const hasOnValueChange = !!onValueChange;
const hasDeltaCalculation =
deltaCalculation &&
deltaCalculation.leftArea?.activeLabel &&
deltaCalculation.rightArea?.activeLabel;

const isBeforeLeftValue =
deltaCalculation?.leftArea?.chartX > deltaCalculation?.rightArea?.chartX;
const deltaCalculationData =
hasDeltaCalculation &&
deltaCalculation.leftArea?.activeLabel !== deltaCalculation.rightArea?.activeLabel
? data.map((item, idx) => {
if (
isBeforeLeftValue
? idx <= deltaCalculation.leftArea.activeTooltipIndex &&
idx >= deltaCalculation.rightArea.activeTooltipIndex
: idx >= deltaCalculation.leftArea.activeTooltipIndex &&
idx <= deltaCalculation.rightArea.activeTooltipIndex
) {
return {
...item,
...categories.reduce((acc, category) => {
return {
...acc,
[`${category}TremorRange`]: item[category],
};
}, {}),
};
}

return item;
})
: data;

function onDotClick(itemData: any, event: React.MouseEvent) {
event.stopPropagation();
Expand Down Expand Up @@ -126,12 +164,13 @@ const AreaChart = React.forwardRef<HTMLDivElement, AreaChartProps>((props, ref)
}
setActiveDot(undefined);
}

return (
<div ref={ref} className={tremorTwMerge("w-full h-80", className)} {...other}>
<ResponsiveContainer className="h-full w-full">
{data?.length ? (
<ResponsiveContainer className="h-full w-full select-none">
{deltaCalculationData?.length ? (
<ReChartsAreaChart
data={data}
data={deltaCalculationData}
onClick={
hasOnValueChange && (activeLegend || activeDot)
? () => {
Expand All @@ -141,8 +180,22 @@ const AreaChart = React.forwardRef<HTMLDivElement, AreaChartProps>((props, ref)
}
: undefined
}
//had to fix legend click
onMouseDown={(value, e) => {
e.stopPropagation();
enableDeltaCalculation && setDeltaCalculation({ leftArea: value });
}}
onMouseMove={(value, e) => {
e.stopPropagation();
enableDeltaCalculation &&
deltaCalculation &&
setDeltaCalculation((prev) => ({ ...prev, rightArea: value }));
}}
onMouseUp={(value, e) => {
e.stopPropagation();
enableDeltaCalculation && deltaCalculation && setDeltaCalculation(null);
}}
>
{" "}
{showGridLines ? (
<CartesianGrid
className={tremorTwMerge(
Expand Down Expand Up @@ -202,7 +255,10 @@ const AreaChart = React.forwardRef<HTMLDivElement, AreaChartProps>((props, ref)
<Tooltip
wrapperStyle={{ outline: "none" }}
isAnimationActive={false}
cursor={{ stroke: "#d1d5db", strokeWidth: 1 }}
cursor={{
stroke: "#d1d5db",
strokeWidth: hasDeltaCalculation ? 0 : 1,
}}
content={
showTooltip ? (
({ active, payload, label }) => (
Expand All @@ -212,6 +268,8 @@ const AreaChart = React.forwardRef<HTMLDivElement, AreaChartProps>((props, ref)
label={label}
valueFormatter={valueFormatter}
categoryColors={categoryColors}
deltaCalculation={deltaCalculation}
isIncreasePositive={isIncreasePositive}
/>
)
) : (
Expand Down Expand Up @@ -255,7 +313,7 @@ const AreaChart = React.forwardRef<HTMLDivElement, AreaChartProps>((props, ref)
y2="1"
>
<stop
offset="5%"
offset="25%"
stopColor="currentColor"
stopOpacity={
activeDot || (activeLegend && activeLegend !== category) ? 0.15 : 0.4
Expand Down Expand Up @@ -288,6 +346,22 @@ const AreaChart = React.forwardRef<HTMLDivElement, AreaChartProps>((props, ref)
</defs>
);
})}
{hasDeltaCalculation ? (
<ReferenceArea
x1={deltaCalculation.leftArea.activeLabel}
x2={deltaCalculation.rightArea.activeLabel}
fillOpacity={0.2}
shape={({ x, y, width, height }) => (
<DeltaCalculationReferenceShape
x={x}
y={y}
width={width}
height={height}
fill={["natural", "monotone"].includes(curveType)}
/>
)}
/>
) : null}
{categories.map((category) => (
<Area
className={
Expand All @@ -296,7 +370,13 @@ const AreaChart = React.forwardRef<HTMLDivElement, AreaChartProps>((props, ref)
colorPalette.text,
).strokeColor
}
strokeOpacity={activeDot || (activeLegend && activeLegend !== category) ? 0.3 : 1}
strokeOpacity={
activeDot ||
(activeLegend && activeLegend !== category) ||
(hasDeltaCalculation && !["natural", "monotone"].includes(curveType))
? 0.3
: 1
}
activeDot={(props: any) => {
const { cx, cy, stroke, strokeLinecap, strokeLinejoin, strokeWidth, dataKey } =
props;
Expand Down Expand Up @@ -324,20 +404,22 @@ const AreaChart = React.forwardRef<HTMLDivElement, AreaChartProps>((props, ref)
}}
dot={(props: any) => {
const {
payload,
stroke,
strokeLinecap,
strokeLinejoin,
strokeWidth,
cx,
cy,
dataKey,
index,
index: idx,
} = props;

if (
(hasOnlyOneValueForThisKey(data, category) &&
!(activeDot || (activeLegend && activeLegend !== category))) ||
(activeDot?.index === index && activeDot?.dataKey === category)
(activeDot?.index === idx && activeDot?.dataKey === category) ||
payload[index] === deltaCalculation?.leftArea?.activeLabel
) {
return (
<Dot
Expand Down Expand Up @@ -367,7 +449,7 @@ const AreaChart = React.forwardRef<HTMLDivElement, AreaChartProps>((props, ref)
type={curveType}
dataKey={category}
stroke=""
fill={`url(#${categoryColors.get(category)})`}
fill={hasDeltaCalculation ? "transparent" : `url(#${categoryColors.get(category)})`}
strokeWidth={2}
strokeLinejoin="round"
strokeLinecap="round"
Expand All @@ -377,6 +459,32 @@ const AreaChart = React.forwardRef<HTMLDivElement, AreaChartProps>((props, ref)
connectNulls={connectNulls}
/>
))}
{hasDeltaCalculation && !["natural", "monotone"].includes(curveType)
? categories.map((category) => (
<Area
className={
getColorClassNames(
categoryColors.get(category) ?? BaseColors.Gray,
colorPalette.text,
).strokeColor
}
key={category + "TremorRange"}
name={category + "TremorRange"}
legendType="none"
tooltipType="none"
type={curveType}
dataKey={category + "TremorRange"}
stroke=""
fill={`url(#${categoryColors.get(category)})`}
strokeWidth={2}
strokeLinejoin="round"
strokeLinecap="round"
animationDuration={animationDuration}
stackId={stack ? "a" : undefined}
connectNulls={false}
/>
))
: null}
{onValueChange
? categories.map((category) => (
<Line
Expand Down
43 changes: 41 additions & 2 deletions src/components/chart-elements/BarChart/BarChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
CartesianGrid,
Legend,
BarChart as ReChartsBarChart,
ReferenceArea,
ResponsiveContainer,
Tooltip,
XAxis,
Expand All @@ -21,6 +22,8 @@ import ChartTooltip from "../common/ChartTooltip";
import NoData from "../common/NoData";

import { BaseColors, defaultValueFormatter, themeColorRange } from "lib";
import DeltaCalculationProps from "components/chart-elements/common/DeltaCalculationProps";
import DeltaCalculationReferenceShape from "components/chart-elements/common/DeltaCalculationReferenceShape";

const renderShape = (props: any, activeBar: any | undefined, activeLegend: string | undefined) => {
const { x, y, width, height, fillOpacity, name, payload, value } = props;
Expand Down Expand Up @@ -71,11 +74,13 @@ const BarChart = React.forwardRef<HTMLDivElement, BarChartProps>((props, ref) =>
minValue,
maxValue,
allowDecimals = true,
enableDeltaCalculation = false,
noDataText,
onValueChange,
className,
...other
} = props;
const [deltaCalculation, setDeltaCalculation] = useState<DeltaCalculationProps>({});
const [legendHeight, setLegendHeight] = useState(60);
const categoryColors = constructCategoryColors(categories, colors);
const [activeBar, setActiveBar] = React.useState<any | undefined>(undefined);
Expand Down Expand Up @@ -121,12 +126,29 @@ const BarChart = React.forwardRef<HTMLDivElement, BarChartProps>((props, ref) =>

return (
<div ref={ref} className={tremorTwMerge("w-full h-80", className)} {...other}>
<ResponsiveContainer className="h-full w-full">
<ResponsiveContainer className="h-full w-full select-none">
{data?.length ? (
<ReChartsBarChart
data={data}
stackOffset={relative ? "expand" : "none"}
layout={layout === "vertical" ? "vertical" : "horizontal"}
onMouseDown={(value, e) => {
e.stopPropagation();
enableDeltaCalculation &&
layout === "horizontal" &&
setDeltaCalculation({ leftArea: value });
}}
onMouseMove={(value, e) => {
e.stopPropagation();
enableDeltaCalculation &&
layout === "horizontal" &&
deltaCalculation.leftArea &&
setDeltaCalculation((prev) => ({ ...prev, rightArea: value }));
}}
onMouseUp={(_, e) => {
e.stopPropagation();
setDeltaCalculation({});
}}
onClick={
hasOnValueChange && (activeLegend || activeBar)
? () => {
Expand Down Expand Up @@ -246,7 +268,13 @@ const BarChart = React.forwardRef<HTMLDivElement, BarChartProps>((props, ref) =>
<Tooltip
wrapperStyle={{ outline: "none" }}
isAnimationActive={false}
cursor={{ fill: "#d1d5db", opacity: "0.15" }}
cursor={{
stroke: "#d1d5db",
strokeWidth:
deltaCalculation.leftArea?.activeLabel && deltaCalculation.rightArea?.activeLabel
? 0
: 1,
}}
content={
showTooltip ? (
({ active, payload, label }) => (
Expand All @@ -256,6 +284,7 @@ const BarChart = React.forwardRef<HTMLDivElement, BarChartProps>((props, ref) =>
label={label}
valueFormatter={valueFormatter}
categoryColors={categoryColors}
deltaCalculation={deltaCalculation}
/>
)
) : (
Expand Down Expand Up @@ -302,6 +331,16 @@ const BarChart = React.forwardRef<HTMLDivElement, BarChartProps>((props, ref) =>
onClick={onBarClick}
/>
))}
{deltaCalculation.leftArea?.activeLabel && deltaCalculation.rightArea?.activeLabel ? (
<ReferenceArea
x1={deltaCalculation.leftArea.activeLabel}
x2={deltaCalculation.rightArea.activeLabel}
fillOpacity={0.2}
shape={({ x, y, width, height }) => (
<DeltaCalculationReferenceShape x={x} y={y} width={width} height={height} />
)}
/>
) : null}
</ReChartsBarChart>
) : (
<NoData noDataText={noDataText} />
Expand Down
Loading