Skip to content

Commit

Permalink
Merge pull request #1199 from visualize-admin/feat/combo-chart
Browse files Browse the repository at this point in the history
feat: Add Combo charts
  • Loading branch information
bprusinowski authored Oct 10, 2023
2 parents 1ffddfc + d1ef9f9 commit 3f5fb3d
Show file tree
Hide file tree
Showing 60 changed files with 3,651 additions and 370 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ You can also check the [release page](https://github.com/visualize-admin/visuali

## Unreleased

Nothing yet.
- Features
- Added a new group of charts – Combo charts – that includes multi-measure line, dual-axis line and column-line charts

# [3.22.9] - 2023-10-06

Expand Down
24 changes: 13 additions & 11 deletions app/charts/area/areas-state.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ export type AreasState = CommonChartState &
yScale: ScaleLinear<number, number>;
segments: string[];
colors: ScaleOrdinal<string, string>;
getColorLabel: (segment: string) => string;
chartWideData: ArrayLike<Observation>;
series: $FixMe[];
getAnnotationInfo: (d: Observation) => TooltipInfo;
Expand All @@ -89,6 +90,7 @@ const useAreasState = (
segmentsByAbbreviationOrLabel,
getSegment,
getSegmentAbbreviationOrLabel,
getSegmentLabel,
} = variables;
const getIdentityY = useGetIdentityY(yMeasure.iri);
const {
Expand Down Expand Up @@ -230,15 +232,14 @@ const useAreasState = (
}, [chartWideData, segmentSortingOrder, segmentSortingType, segments]);

/** Scales */
const { colors, xScale, interactiveXTimeRangeScale } = useMemo(() => {
const { colors, xScale, xScaleTimeRange } = useMemo(() => {
const xDomain = extent(scalesData, (d) => getX(d)) as [Date, Date];
const xScale = scaleTime().domain(xDomain);
const interactiveXTimeRangeDomain = extent(timeRangeData, (d) =>
getX(d)
) as [Date, Date];
const interactiveXTimeRangeScale = scaleTime().domain(
interactiveXTimeRangeDomain
);
const xScaleTimeRangeDomain = extent(timeRangeData, (d) => getX(d)) as [
Date,
Date
];
const xScaleTimeRange = scaleTime().domain(xScaleTimeRangeDomain);
const colors = scaleOrdinal<string, string>();

if (segmentDimension && fields.segment?.colorMapping) {
Expand Down Expand Up @@ -266,7 +267,7 @@ const useAreasState = (
return {
colors,
xScale,
interactiveXTimeRangeScale,
xScaleTimeRange,
};
}, [
fields.segment?.palette,
Expand Down Expand Up @@ -339,7 +340,7 @@ const useAreasState = (

/** Adjust scales according to dimensions */
xScale.range([0, chartWidth]);
interactiveXTimeRangeScale.range([0, chartWidth]);
xScaleTimeRange.range([0, chartWidth]);
yScale.range([chartHeight, 0]);

/** Tooltip */
Expand Down Expand Up @@ -372,7 +373,7 @@ const useAreasState = (
placement: getCenteredTooltipPlacement({
chartWidth,
xAnchor,
segment: !!fields.segment,
topAnchor: !fields.segment,
}),
xValue: timeFormatUnit(getX(datum), xDimension.timeUnit),
datum: {
Expand Down Expand Up @@ -419,10 +420,11 @@ const useAreasState = (
chartData,
allData,
xScale,
interactiveXTimeRangeScale,
xScaleTimeRange,
yScale,
segments,
colors,
getColorLabel: getSegmentLabel,
chartWideData,
series,
getAnnotationInfo,
Expand Down
66 changes: 66 additions & 0 deletions app/charts/chart-config-ui-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ import {
ColorScaleType,
ColumnConfig,
ColumnSegmentField,
ComboLineColumnConfig,
ComboLineDualConfig,
ComboLineSingleConfig,
ComponentType,
GenericField,
LineConfig,
Expand Down Expand Up @@ -216,6 +219,8 @@ export interface EncodingSpec<T extends ChartConfig = ChartConfig> {
field: EncodingFieldType;
optional: boolean;
componentTypes: ComponentType[];
/** If true, won't use the ChartFieldOption component, but a custom one. Needs to be handled then in ChartOptionsSelector. */
customComponent?: boolean;
/** If false, using a dimension in this encoding will not prevent it to be used in an other encoding. Default: true */
exclusive?: boolean;
filters: boolean;
Expand Down Expand Up @@ -256,6 +261,9 @@ interface ChartSpecs {
pie: ChartSpec<PieConfig>;
scatterplot: ChartSpec<ScatterPlotConfig>;
table: ChartSpec<TableConfig>;
comboLineSingle: ChartSpec<ComboLineSingleConfig>;
comboLineDual: ChartSpec<ComboLineDualConfig>;
comboLineColumn: ChartSpec<ComboLineColumnConfig>;
}

const SEGMENT_COMPONENT_TYPES: ComponentType[] = [
Expand Down Expand Up @@ -872,6 +880,64 @@ const chartConfigOptionsUISpec: ChartSpecs = {
encodings: [],
interactiveFilters: [],
},
comboLineSingle: {
chartType: "comboLineSingle",
encodings: [
{
field: "y",
optional: false,
// TODO: maybe we should even create the components here?
customComponent: true,
componentTypes: ["NumericalMeasure"],
filters: false,
},
{
field: "x",
optional: false,
componentTypes: ["TemporalDimension"],
filters: true,
},
],
interactiveFilters: [],
},
comboLineDual: {
chartType: "comboLineDual",
encodings: [
{
field: "y",
optional: false,
customComponent: true,
componentTypes: ["NumericalMeasure"],
filters: false,
},
{
field: "x",
optional: false,
componentTypes: ["TemporalDimension"],
filters: true,
},
],
interactiveFilters: [],
},
comboLineColumn: {
chartType: "comboLineColumn",
encodings: [
{
field: "y",
optional: false,
customComponent: true,
componentTypes: ["NumericalMeasure"],
filters: false,
},
{
field: "x",
optional: false,
componentTypes: ["TemporalDimension"],
filters: true,
},
],
interactiveFilters: [],
},
};

export const getChartFieldChangeSideEffect = (
Expand Down
19 changes: 10 additions & 9 deletions app/charts/column/columns-grouped-state.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ export type GroupedColumnsState = CommonChartState &
yScale: ScaleLinear<number, number>;
segments: string[];
colors: ScaleOrdinal<string, string>;
getColorLabel: (segment: string) => string;
grouped: [string, Observation[]][];
getAnnotationInfo: (d: Observation) => TooltipInfo;
};
Expand All @@ -89,6 +90,7 @@ const useColumnsGroupedState = (
segmentsByAbbreviationOrLabel,
getSegment,
getSegmentAbbreviationOrLabel,
getSegmentLabel,
} = variables;
const {
chartData,
Expand Down Expand Up @@ -178,7 +180,7 @@ const useColumnsGroupedState = (
colors,
yScale,
paddingYScale,
interactiveXTimeRangeScale,
xScaleTimeRange,
xScale,
xScaleIn,
xScaleInteraction,
Expand Down Expand Up @@ -232,12 +234,10 @@ const useColumnsGroupedState = (
.paddingOuter(0);
const xScaleIn = scaleBand().domain(segments).padding(PADDING_WITHIN);

const interactiveXTimeRangeDomain = extent(timeRangeData, (d) =>
const xScaleTimeRangeDomain = extent(timeRangeData, (d) =>
getXAsDate(d)
) as [Date, Date];
const interactiveXTimeRangeScale = scaleTime().domain(
interactiveXTimeRangeDomain
);
const xScaleTimeRange = scaleTime().domain(xScaleTimeRangeDomain);

// y
const minValue = Math.min(
Expand Down Expand Up @@ -274,7 +274,7 @@ const useColumnsGroupedState = (
colors,
yScale,
paddingYScale,
interactiveXTimeRangeScale,
xScaleTimeRange,
xScale,
xScaleIn,
xScaleInteraction,
Expand Down Expand Up @@ -352,7 +352,7 @@ const useColumnsGroupedState = (
xScale.range([0, chartWidth]);
xScaleInteraction.range([0, chartWidth]);
xScaleIn.range([0, xScale.bandwidth()]);
interactiveXTimeRangeScale.range([0, chartWidth]);
xScaleTimeRange.range([0, chartWidth]);
yScale.range([chartHeight, 0]);

// Tooltip
Expand Down Expand Up @@ -383,7 +383,7 @@ const useColumnsGroupedState = (
const placement = getCenteredTooltipPlacement({
chartWidth,
xAnchor: xAnchorRaw,
segment: !!fields.segment,
topAnchor: !fields.segment,
});

const getError = (d: Observation) => {
Expand Down Expand Up @@ -424,10 +424,11 @@ const useColumnsGroupedState = (
xScale,
xScaleInteraction,
xScaleIn,
interactiveXTimeRangeScale,
xScaleTimeRange,
yScale,
segments,
colors,
getColorLabel: getSegmentLabel,
grouped,
getAnnotationInfo,
...variables,
Expand Down
19 changes: 10 additions & 9 deletions app/charts/column/columns-stacked-state.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ export type StackedColumnsState = CommonChartState &
yScale: ScaleLinear<number, number>;
segments: string[];
colors: ScaleOrdinal<string, string>;
getColorLabel: (segment: string) => string;
chartWideData: ArrayLike<Observation>;
series: $FixMe[];
getAnnotationInfo: (
Expand All @@ -97,6 +98,7 @@ const useColumnsStackedState = (
segmentsByAbbreviationOrLabel,
getSegment,
getSegmentAbbreviationOrLabel,
getSegmentLabel,
} = variables;
const getIdentityY = useGetIdentityY(yMeasure.iri);
const {
Expand Down Expand Up @@ -210,7 +212,7 @@ const useColumnsStackedState = (
xScale,
xTimeRangeDomainLabels,
xScaleInteraction,
interactiveXTimeRangeScale,
xScaleTimeRange,
} = useMemo(() => {
const colors = scaleOrdinal<string, string>();

Expand Down Expand Up @@ -271,18 +273,16 @@ const useColumnsStackedState = (
.paddingInner(0)
.paddingOuter(0);

const interactiveXTimeRangeDomain = extent(timeRangeData, (d) =>
const xScaleTimeRangeDomain = extent(timeRangeData, (d) =>
getXAsDate(d)
) as [Date, Date];
const interactiveXTimeRangeScale = scaleTime().domain(
interactiveXTimeRangeDomain
);
const xScaleTimeRange = scaleTime().domain(xScaleTimeRangeDomain);

return {
colors,
xScale,
xTimeRangeDomainLabels,
interactiveXTimeRangeScale,
xScaleTimeRange,
xScaleInteraction,
};
}, [
Expand Down Expand Up @@ -400,7 +400,7 @@ const useColumnsStackedState = (

xScale.range([0, chartWidth]);
xScaleInteraction.range([0, chartWidth]);
interactiveXTimeRangeScale.range([0, chartWidth]);
xScaleTimeRange.range([0, chartWidth]);
yScale.range([chartHeight, 0]);

// Tooltips
Expand Down Expand Up @@ -430,7 +430,7 @@ const useColumnsStackedState = (
const placement = getCenteredTooltipPlacement({
chartWidth,
xAnchor: xAnchorRaw,
segment: !!fields.segment,
topAnchor: !fields.segment,
});

return {
Expand Down Expand Up @@ -479,10 +479,11 @@ const useColumnsStackedState = (
allData,
xScale,
xScaleInteraction,
interactiveXTimeRangeScale,
xScaleTimeRange,
yScale,
segments,
colors,
getColorLabel: getSegmentLabel,
chartWideData,
series,
getAnnotationInfo,
Expand Down
Loading

1 comment on commit 3f5fb3d

@vercel
Copy link

@vercel vercel bot commented on 3f5fb3d Oct 10, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

visualization-tool – ./

visualization-tool-ixt1.vercel.app
visualization-tool-git-main-ixt1.vercel.app
visualization-tool-alpha.vercel.app

Please sign in to comment.