Skip to content

Commit

Permalink
Merge pull request #352 from visualize-admin/error-bars
Browse files Browse the repository at this point in the history
  • Loading branch information
ptbrowne authored Feb 16, 2022
2 parents 4be5af1 + ac3e772 commit ad01209
Show file tree
Hide file tree
Showing 14 changed files with 792 additions and 52 deletions.
3 changes: 2 additions & 1 deletion app/charts/column/chart-column.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import { Tooltip } from "../shared/interaction/tooltip";
import { InteractiveLegendColor, LegendColor } from "../shared/legend-color";
import { ColumnsGrouped } from "./columns-grouped";
import { GroupedColumnChart } from "./columns-grouped-state";
import { Columns } from "./columns-simple";
import { Columns, ErrorWhiskers } from "./columns-simple";
import { ColumnsStacked } from "./columns-stacked";
import { StackedColumnsChart } from "./columns-stacked-state";
import { ColumnChart } from "./columns-state";
Expand Down Expand Up @@ -171,6 +171,7 @@ export const ChartColumns = memo(
<AxisHeightLinear />
<AxisWidthBand />
<Columns />
<ErrorWhiskers />
<AxisWidthBandDomain />
<InteractionColumns />
{interactiveFiltersConfig?.time.active && <BrushTime />}
Expand Down
75 changes: 75 additions & 0 deletions app/charts/column/columns-simple.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,83 @@
import { memo } from "react";
import { useTheme } from "../../themes";
import { useChartState } from "../shared/use-chart-state";
import { ColumnsState } from "./columns-state";
import { Column } from "./rendering-utils";

export const VerticalWhisker = memo(
({
x,
y1,
y2,
width,
}: {
x: number;
y1: number;
y2: number;
width: number;
color?: string;
}) => {
return (
<>
<rect
x={x}
y={y1}
width={width}
height={2}
fill={"black"}
stroke="none"
/>
<rect
x={x + width / 2 - 1}
y={y2}
width={2}
height={y1 - y2}
fill={"black"}
stroke="none"
/>
<rect
x={x}
y={y2}
width={width}
height={2}
fill={"black"}
stroke="none"
/>
</>
);
}
);

export const ErrorWhiskers = () => {
const { preparedData, bounds, getX, xScale, getY, getYError, yScale } =
useChartState() as ColumnsState;
const { margins } = bounds;

if (!getYError) {
return null;
}

return (
<g transform={`translate(${margins.left} ${margins.top})`}>
{preparedData.map((d, i) => {
const x0 = xScale(getX(d)) as number;
const bandwidth = xScale.bandwidth();
const barwidth = Math.min(bandwidth, 15);
const [y1, y2] = getYError(d);
return (
<VerticalWhisker
key={i}
x={x0 + bandwidth / 2 - barwidth / 2}
width={barwidth}
y1={yScale(y1)}
y2={yScale(y2)}
/>
);
})}
</g>
);
};

export const Columns = () => {
const { preparedData, bounds, getX, xScale, getY, yScale } =
useChartState() as ColumnsState;
Expand Down
24 changes: 24 additions & 0 deletions app/charts/column/columns-state.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ export interface ColumnsState {
xEntireScale: ScaleTime<number, number>;
xScaleInteraction: ScaleBand<string>;
getY: (d: Observation) => number | null;
getYError: null | ((d: Observation) => [number, number]);
yScale: ScaleLinear<number, number>;
getSegment: (d: Observation) => string;
segments: string[];
Expand Down Expand Up @@ -102,7 +103,29 @@ const useColumnsState = ({
const getX = useStringVariable(fields.x.componentIri);
const getXAsDate = useTemporalVariable(fields.x.componentIri);
const getY = useOptionalNumericVariable(fields.y.componentIri);
const errorMeasure = useMemo(() => {
return [...measures, ...dimensions].find((m) => {
return m.related?.some(
(r) => r.type === "StandardError" && r.iri === fields.y.componentIri
);
});
}, [dimensions, fields.y.componentIri, measures]);
const getSegment = useSegment(fields.segment?.componentIri);
const getYError = errorMeasure
? (d: Observation) => {
const y = getY(d) as number;
const errorIri = errorMeasure.iri;
let error =
d[errorIri] !== null ? parseFloat(d[errorIri] as string) : null;
if (errorMeasure.unit === "%" && error !== null) {
error = (error * y) / 100;
}
return (error === null ? [y, y] : [y - error, y + error]) as [
number,
number
];
}
: null;

const sortingType = fields.x.sorting?.sortingType;
const sortingOrder = fields.x.sorting?.sortingOrder;
Expand Down Expand Up @@ -271,6 +294,7 @@ const useColumnsState = ({
timeUnit,
xScaleInteraction,
getY,
getYError,
yScale,
getSegment,
yAxisLabel,
Expand Down
56 changes: 16 additions & 40 deletions app/charts/shared/chart-helpers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -118,47 +118,23 @@ export const usePreparedData = ({
return preparedData;
};

// retrieving variables
export const useNumericVariable = (
key: string
): ((d: Observation) => number) => {
const getVariable = useCallback((d: Observation) => Number(d[key]), [key]);

return getVariable;
};

export const useOptionalNumericVariable = (
key: string
): ((d: Observation) => number | null) => {
const getVariable = useCallback(
(d: Observation) => (d[key] !== null ? Number(d[key]) : null),
[key]
);

return getVariable;
};

export const useStringVariable = (
key: string
): ((d: Observation) => string) => {
const getVariable = useCallback(
(d: Observation) => (d[key] !== null ? `${d[key]}` : ""),
[key]
);

return getVariable;
};
export const makeUseParsedVariable =
<T extends unknown>(parser: (d: Observation[string]) => T) =>
(key: string) => {
return useCallback((d: Observation) => parser(d[key]), [key]);
};

export const useTemporalVariable = (
key: string
): ((d: Observation) => Date) => {
const getVariable = useCallback(
(d: Observation) => parseDate(`${d[key]}`),
[key]
);

return getVariable;
};
// retrieving variables
export const useNumericVariable = makeUseParsedVariable((x) => Number(x));
export const useOptionalNumericVariable = makeUseParsedVariable((x) =>
x !== null ? Number(x) : null
);
export const useStringVariable = makeUseParsedVariable((x) =>
x !== null ? `${x}` : ""
);
export const useTemporalVariable = makeUseParsedVariable((x) =>
parseDate(`${x}`)
);

const getSegment =
(segmentKey: string | undefined) =>
Expand Down
Loading

0 comments on commit ad01209

Please sign in to comment.