Skip to content

Commit

Permalink
Merge pull request #1565 from visualize-admin/feat/dynamic-y-scale-fo…
Browse files Browse the repository at this point in the history
…r-interval-measures

feat: Make the y scale's min value dynamic in some cases
  • Loading branch information
bprusinowski authored Jun 6, 2024
2 parents bf397b7 + cb94af6 commit bb597a9
Show file tree
Hide file tree
Showing 20 changed files with 147 additions and 57 deletions.
2 changes: 1 addition & 1 deletion app/charts/area/areas-state-props.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export const useAreasStateVariables = (
const temporalXVariables = useTemporalXVariables(x, {
dimensionsByIri,
});
const numericalYVariables = useNumericalYVariables(y, {
const numericalYVariables = useNumericalYVariables("area", y, {
measuresByIri,
});
const segmentVariables = useSegmentVariables(segment, {
Expand Down
2 changes: 1 addition & 1 deletion app/charts/column/columns-grouped-state-props.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export const useColumnsGroupedStateVariables = (
dimensionsByIri,
observations,
});
const numericalYVariables = useNumericalYVariables(y, {
const numericalYVariables = useNumericalYVariables("column", y, {
measuresByIri,
});
const numericalYErrorVariables = useNumericalYErrorVariables(y, {
Expand Down
18 changes: 7 additions & 11 deletions app/charts/column/columns-grouped-state.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { extent, group, max, min, rollup, sum } from "d3-array";
import { extent, group, max, rollup, sum } from "d3-array";
import {
ScaleBand,
ScaleLinear,
Expand Down Expand Up @@ -78,6 +78,7 @@ const useColumnsGroupedState = (
getXLabel,
yMeasure,
getY,
getMinY,
showYStandardError,
yErrorMeasure,
getYError,
Expand Down Expand Up @@ -238,11 +239,8 @@ const useColumnsGroupedState = (
const xScaleTimeRange = scaleTime().domain(xScaleTimeRangeDomain);

// y
const minValue = Math.min(
min(scalesData, (d) =>
getYErrorRange ? getYErrorRange(d)[0] : getY(d)
) ?? 0,
0
const minValue = getMinY(scalesData, (d) =>
getYErrorRange ? getYErrorRange(d)[0] : getY(d)
);
const maxValue = Math.max(
max(scalesData, (d) =>
Expand All @@ -252,11 +250,8 @@ const useColumnsGroupedState = (
);
const yScale = scaleLinear().domain([minValue, maxValue]).nice();

const minPaddingValue = Math.min(
min(paddingData, (d) =>
getYErrorRange ? getYErrorRange(d)[0] : getY(d)
) ?? 0,
0
const minPaddingValue = getMinY(paddingData, (d) =>
getYErrorRange ? getYErrorRange(d)[0] : getY(d)
);
const maxPaddingValue = Math.max(
max(paddingData, (d) =>
Expand Down Expand Up @@ -298,6 +293,7 @@ const useColumnsGroupedState = (
getXAsDate,
getYErrorRange,
getY,
getMinY,
]);

// Group
Expand Down
2 changes: 1 addition & 1 deletion app/charts/column/columns-stacked-state-props.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export const useColumnsStackedStateVariables = (
dimensionsByIri,
observations,
});
const numericalYVariables = useNumericalYVariables(y, {
const numericalYVariables = useNumericalYVariables("column", y, {
measuresByIri,
});
const segmentVariables = useSegmentVariables(segment, {
Expand Down
2 changes: 1 addition & 1 deletion app/charts/column/columns-state-props.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export const useColumnsStateVariables = (
dimensionsByIri,
observations,
});
const numericalYVariables = useNumericalYVariables(y, {
const numericalYVariables = useNumericalYVariables("column", y, {
measuresByIri,
});
const numericalYErrorVariables = useNumericalYErrorVariables(y, {
Expand Down
18 changes: 7 additions & 11 deletions app/charts/column/columns-state.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { extent, max, min, rollup, sum } from "d3-array";
import { extent, max, rollup, sum } from "d3-array";
import {
ScaleBand,
ScaleLinear,
Expand Down Expand Up @@ -69,6 +69,7 @@ const useColumnsState = (
xTimeUnit,
yMeasure,
getY,
getMinY,
showYStandardError,
yErrorMeasure,
getYError,
Expand Down Expand Up @@ -133,11 +134,8 @@ const useColumnsState = (

const xScaleTimeRange = scaleTime().domain(xScaleTimeRangeDomain);

const minValue = Math.min(
min(scalesData, (d) =>
getYErrorRange ? getYErrorRange(d)[0] : getY(d)
) ?? 0,
0
const minValue = getMinY(scalesData, (d) =>
getYErrorRange ? getYErrorRange(d)[0] : getY(d)
);
const maxValue = Math.max(
max(scalesData, (d) =>
Expand All @@ -147,11 +145,8 @@ const useColumnsState = (
);
const yScale = scaleLinear().domain([minValue, maxValue]).nice();

const paddingMinValue = Math.min(
min(paddingData, (d) =>
getYErrorRange ? getYErrorRange(d)[0] : getY(d)
) ?? 0,
0
const paddingMinValue = getMinY(paddingData, (d) =>
getYErrorRange ? getYErrorRange(d)[0] : getY(d)
);
const paddingMaxValue = Math.max(
max(paddingData, (d) =>
Expand Down Expand Up @@ -185,6 +180,7 @@ const useColumnsState = (
xDimension,
chartConfig.cubes,
sumsByX,
getMinY,
]);

const { left, bottom } = useChartPadding({
Expand Down
21 changes: 17 additions & 4 deletions app/charts/combo/combo-line-column-state-props.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { min } from "d3-array";
import { useCallback, useMemo } from "react";

import { BaseYGetter, sortComboData } from "@/charts/combo/combo-state-props";
Expand All @@ -11,6 +12,7 @@ import {
ChartStateData,
RenderingVariables,
SortingVariables,
shouldUseDynamicMinScaleValue,
useBandXVariables,
useBaseVariables,
useChartData,
Expand Down Expand Up @@ -69,8 +71,14 @@ export const useComboLineColumnStateVariables = (
iri: lineIri,
label: getLabelWithUnit(measuresByIri[lineIri]),
color: fields.y.colorMapping[lineIri],
getY: (d) => {
return d[lineIri] !== null ? Number(d[lineIri]) : null;
getY: (d) => (d[lineIri] !== null ? Number(d[lineIri]) : null),
getMinY: (data) => {
const minY =
min(data, (d) => (d[lineIri] !== null ? Number(d[lineIri]) : null)) ??
0;
return shouldUseDynamicMinScaleValue(measuresByIri[lineIri].scaleType)
? minY
: Math.min(0, minY);
},
};
const columnYGetter: YGetter = {
Expand All @@ -80,8 +88,13 @@ export const useComboLineColumnStateVariables = (
iri: columnIri,
label: getLabelWithUnit(measuresByIri[columnIri]),
color: fields.y.colorMapping[columnIri],
getY: (d) => {
return d[columnIri] !== null ? Number(d[columnIri]) : null;
getY: (d) => (d[columnIri] !== null ? Number(d[columnIri]) : null),
getMinY: (data) => {
const minY =
min(data, (d) =>
d[columnIri] !== null ? Number(d[columnIri]) : null
) ?? 0;
return Math.min(0, minY);
},
};

Expand Down
4 changes: 2 additions & 2 deletions app/charts/combo/combo-line-column-state.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -106,14 +106,14 @@ const useComboLineColumnState = (
scalesData,
paddingData,
getY: variables.y.left.getY,
startAtZero: variables.y.left.chartType === "column",
getMinY: variables.y.left.getMinY,
});
const { yScale: yScaleRight, paddingYScale: paddingRightYScale } = useYScales(
{
scalesData,
paddingData,
getY: variables.y.right.getY,
startAtZero: variables.y.right.chartType === "column",
getMinY: variables.y.right.getMinY,
}
);
const [minLeftValue, maxLeftValue] = yScaleLeft.domain();
Expand Down
26 changes: 22 additions & 4 deletions app/charts/combo/combo-line-dual-state-props.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { min } from "d3-array";
import { useCallback, useMemo } from "react";

import { BaseYGetter, sortComboData } from "@/charts/combo/combo-state-props";
Expand All @@ -10,6 +11,7 @@ import {
ChartStateData,
SortingVariables,
TemporalXVariables,
shouldUseDynamicMinScaleValue,
useBaseVariables,
useChartData,
useTemporalXVariables,
Expand Down Expand Up @@ -52,8 +54,15 @@ export const useComboLineDualStateVariables = (
iri: leftIri,
label: getLabelWithUnit(measuresByIri[leftIri]),
color: fields.y.colorMapping[leftIri],
getY: (d) => {
return d[leftIri] !== null ? Number(d[leftIri]) : null;
getY: (d) => (d[leftIri] !== null ? Number(d[leftIri]) : null),
getMinY: (data) => {
const minY =
min(data, (d) =>
d[leftIri] !== null ? Number(d[leftIri]) : null
) ?? 0;
return shouldUseDynamicMinScaleValue(measuresByIri[leftIri].scaleType)
? minY
: Math.min(0, minY);
},
},
right: {
Expand All @@ -62,8 +71,17 @@ export const useComboLineDualStateVariables = (
iri: rightIri,
label: getLabelWithUnit(measuresByIri[rightIri]),
color: fields.y.colorMapping[rightIri],
getY: (d) => {
return d[rightIri] !== null ? Number(d[rightIri]) : null;
getY: (d) => (d[rightIri] !== null ? Number(d[rightIri]) : null),
getMinY: (data) => {
const minY =
min(data, (d) =>
d[rightIri] !== null ? Number(d[rightIri]) : null
) ?? 0;
return shouldUseDynamicMinScaleValue(
measuresByIri[rightIri].scaleType
)
? minY
: Math.min(0, minY);
},
},
},
Expand Down
2 changes: 2 additions & 0 deletions app/charts/combo/combo-line-dual-state.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -89,12 +89,14 @@ const useComboLineDualState = (
scalesData,
paddingData,
getY: variables.y.left.getY,
getMinY: variables.y.left.getMinY,
});
const { yScale: yScaleRight, paddingYScale: paddingRightYScale } = useYScales(
{
scalesData,
paddingData,
getY: variables.y.right.getY,
getMinY: variables.y.right.getMinY,
}
);

Expand Down
11 changes: 9 additions & 2 deletions app/charts/combo/combo-line-single-state-props.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { min } from "d3-array";
import { useCallback, useMemo } from "react";

import { BaseYGetter, sortComboData } from "@/charts/combo/combo-state-props";
Expand All @@ -6,6 +7,7 @@ import {
ChartStateData,
SortingVariables,
TemporalXVariables,
shouldUseDynamicMinScaleValue,
useBaseVariables,
useChartData,
useTemporalXVariables,
Expand Down Expand Up @@ -45,8 +47,13 @@ export const useComboLineSingleStateVariables = (
iri,
label: measuresByIri[iri].label,
color: fields.y.colorMapping[iri],
getY: (d) => {
return d[iri] !== null ? Number(d[iri]) : null;
getY: (d) => (d[iri] !== null ? Number(d[iri]) : null),
getMinY: (data) => {
const minY =
min(data, (d) => (d[iri] !== null ? Number(d[iri]) : null)) ?? 0;
return shouldUseDynamicMinScaleValue(measuresByIri[iri].scaleType)
? minY
: Math.min(0, minY);
},
})),
},
Expand Down
1 change: 1 addition & 0 deletions app/charts/combo/combo-line-single-state.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ const useComboLineSingleState = (
scalesData,
paddingData,
getY: variables.y.lines.map((d) => d.getY),
getMinY: variables.y.lines.map((d) => d.getMinY),
});

// Dimensions
Expand Down
1 change: 1 addition & 0 deletions app/charts/combo/combo-state-props.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export type BaseYGetter = {
label: string;
color: string;
getY: (d: Observation) => number | null;
getMinY: (data: Observation[]) => number;
};

export const sortComboData = (
Expand Down
16 changes: 10 additions & 6 deletions app/charts/combo/combo-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,23 +98,27 @@ type UseLeftRightYScalesOptions = {
getY:
| ((d: Observation) => number | null)
| ((d: Observation) => number | null)[];
startAtZero?: boolean;
getMinY:
| ((data: Observation[]) => number)
| ((data: Observation[]) => number)[];
};

export const useYScales = (options: UseLeftRightYScalesOptions) => {
const { scalesData, paddingData, getY, startAtZero } = options;
const getMinY = (o: Observation) => {
return Array.isArray(getY) ? min(getY, (d) => d(o)) : getY(o);
const { scalesData, paddingData, getY, getMinY: _getMinY } = options;
const getMinY = (data: Observation[]) => {
return Array.isArray(_getMinY)
? min(_getMinY.map((gmy) => gmy(data))) ?? 0
: _getMinY(data);
};
const getMaxY = (o: Observation) => {
return Array.isArray(getY) ? max(getY, (d) => d(o)) : getY(o);
};

const minValue = startAtZero ? 0 : min(scalesData, getMinY) ?? 0;
const minValue = getMinY(scalesData);
const maxValue = max(scalesData, getMaxY) ?? 0;
const yScale = scaleLinear().domain([minValue, maxValue]).nice();

const paddingMinValue = startAtZero ? 0 : min(paddingData, getMinY) ?? 0;
const paddingMinValue = getMinY(paddingData);
const paddingMaxValue = max(paddingData, getMaxY) ?? 0;
const paddingYScale = scaleLinear()
.domain([paddingMinValue, paddingMaxValue])
Expand Down
2 changes: 1 addition & 1 deletion app/charts/line/lines-state-props.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export const useLinesStateVariables = (
const temporalXVariables = useTemporalXVariables(x, {
dimensionsByIri,
});
const numericalYVariables = useNumericalYVariables(y, {
const numericalYVariables = useNumericalYVariables("line", y, {
measuresByIri,
});
const segmentVariables = useSegmentVariables(segment, {
Expand Down
7 changes: 4 additions & 3 deletions app/charts/line/lines-state.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { extent, group, max, min } from "d3-array";
import { extent, group, max } from "d3-array";
import {
ScaleLinear,
ScaleOrdinal,
Expand Down Expand Up @@ -75,6 +75,7 @@ const useLinesState = (
getXAsString,
yMeasure,
getY,
getMinY,
segmentDimension,
segmentsByAbbreviationOrLabel,
getSegment,
Expand Down Expand Up @@ -132,12 +133,12 @@ const useLinesState = (
const xScaleTimeRange = scaleTime().domain(xScaleTimeRangeDomain);

// y
const minValue = Math.min(min(scalesData, getY) ?? 0, 0);
const minValue = getMinY(scalesData, getY);
const maxValue = max(scalesData, getY) ?? 0;
const yDomain = [minValue, maxValue];
const yScale = scaleLinear().domain(yDomain).nice();

const paddingMinValue = Math.min(min(paddingData, getY) ?? 0, 0);
const paddingMinValue = getMinY(paddingData, getY);
const paddingMaxValue = max(paddingData, getY) ?? 0;
const paddingYScale = scaleLinear()
.domain([paddingMinValue, paddingMaxValue])
Expand Down
2 changes: 1 addition & 1 deletion app/charts/pie/pie-state-props.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export const usePieStateVariables = (
const filters = useChartConfigFilters(chartConfig);

const baseVariables = useBaseVariables(chartConfig);
const numericalYVariables = useNumericalYVariables(y, {
const numericalYVariables = useNumericalYVariables("pie", y, {
measuresByIri,
});
const segmentVariables = useSegmentVariables(segment, {
Expand Down
Loading

0 comments on commit bb597a9

Please sign in to comment.