Skip to content

Commit

Permalink
Merge pull request #810 from visualize-admin/fix/legend-items-sort
Browse files Browse the repository at this point in the history
  • Loading branch information
ptbrowne authored Oct 27, 2022
2 parents c282772 + 018b9c4 commit 1c68672
Show file tree
Hide file tree
Showing 6 changed files with 138 additions and 36 deletions.
100 changes: 70 additions & 30 deletions app/charts/shared/legend-color.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Theme, Typography } from "@mui/material";
import { makeStyles } from "@mui/styles";
import clsx from "clsx";
import { ascending } from "d3";
import React, { memo, useMemo } from "react";

import {
Expand All @@ -11,6 +12,7 @@ import { useInteractiveFilters } from "@/charts/shared/use-interactive-filters";
import Flex from "@/components/flex";
import { Checkbox } from "@/components/form";
import {
DataSource,
isSegmentInConfig,
useReadOnlyConfiguratorState,
} from "@/configurator";
Expand Down Expand Up @@ -141,6 +143,33 @@ export const InteractiveLegendColor = () => {
);
};

const useDimension = ({
dataset,
dataSource,
locale,
dimensionIri,
}: {
dataset: string;
dataSource: DataSource;
locale: string;
dimensionIri?: string;
}) => {
const [{ data: cubeMetadata }] = useDataCubeMetadataWithComponentValuesQuery({
variables: {
iri: dataset,
sourceType: dataSource.type,
sourceUrl: dataSource.url,
locale: locale,
},
pause: !dimensionIri,
});
return useMemo(() => {
return cubeMetadata?.dataCubeByIri?.dimensions.find(
(d) => d.iri === dimensionIri
);
}, [cubeMetadata?.dataCubeByIri?.dimensions, dimensionIri]);
};

const useLegendGroups = ({
labels,
title,
Expand All @@ -160,27 +189,23 @@ const useLegendGroups = ({
}

const locale = useLocale();

// FIXME: should color field also be included here?
const segment = isSegmentInConfig(configState.chartConfig)
const segmentField = isSegmentInConfig(configState.chartConfig)
? configState.chartConfig.fields.segment
: null;
const { dataSet, dataSource } = configState;
const [{ data: cubeMetadata }] = useDataCubeMetadataWithComponentValuesQuery({
variables: {
iri: dataSet,
sourceType: dataSource.type,
sourceUrl: dataSource.url,
locale: locale,
},

const { dataSet: dataset, dataSource } = configState;
const segmentDimension = useDimension({
dataset,
dataSource: dataSource,
locale: locale,
dimensionIri: segmentField?.componentIri,
});
const segmentDimension = useMemo(() => {
return cubeMetadata?.dataCubeByIri?.dimensions.find(
(d) => d.iri === segment?.componentIri
);
}, [cubeMetadata?.dataCubeByIri?.dimensions, segment?.componentIri]);

const [hierarchyResp] = useDimensionHierarchyQuery({
variables: {
cubeIri: dataSet,
cubeIri: dataset,
dimensionIri: segmentDimension?.iri!,
sourceType: dataSource.type,
sourceUrl: dataSource.url,
Expand All @@ -192,22 +217,33 @@ const useLegendGroups = ({
hierarchyResp?.data?.dataCubeByIri?.dimensionByIri?.hierarchy;

const groups = useMemo(() => {
const groupsMap = new Map<HierarchyValue[], string[]>();
if (!hierarchy) {
return new Map([[title ? [{ label: title }] : [], labels]]);
}
const labelSet = new Set(labels);
const groups = new Map<HierarchyValue[], string[]>();
[
...dfs(hierarchy, (node, { parents }) => {
groupsMap.set(title ? [{ label: title } as HierarchyValue] : [], labels);
} else {
const labelSet = new Set(labels);
dfs(hierarchy, (node, { parents }) => {
if (!labelSet.has(node.label)) {
return;
}
groups.set(parents, groups.get(parents) || []);
groups.get(parents)?.push(node.label);
}),
];
groupsMap.set(parents, groupsMap.get(parents) || []);
groupsMap.get(parents)?.push(node.label);
});
}

const groups = Array.from(groupsMap.entries());
if (segmentField && "sorting" in segmentField) {
// Re-sort hierarchy groups against the label order that we have received
const labelOrder = Object.fromEntries(
labels.map((x, i) => [x, i] as const)
);
groups.forEach(([_groupName, entries]) => {
entries.sort((a, b) => ascending(labelOrder[a], labelOrder[b]));
});
}

return groups;
}, [hierarchy, labels, title]);
}, [hierarchy, labels, segmentField, title]);

return groups;
};
Expand Down Expand Up @@ -276,16 +312,16 @@ const LegendColorContent = ({
symbol: LegendSymbol;
}) => {
const classes = useStyles();
const groupList = Array.from(groups.entries());

return (
<Flex
className={clsx(
classes.legendContainer,
groupList.length === 1 ? classes.legendContainerNoGroups : undefined
groups.length === 1 ? classes.legendContainerNoGroups : undefined
)}
>
{groups
? groupList.map(([g, colorValues]) => {
? groups.map(([g, colorValues]) => {
const headerLabelsArray = g.map((n) => n.label);

return (
Expand Down Expand Up @@ -328,5 +364,9 @@ export const LegendItem = ({
symbol: LegendSymbol;
}) => {
const classes = useItemStyles({ symbol, color });
return <Flex className={classes.legendItem}>{item}</Flex>;
return (
<Flex data-testid="legendItem" className={classes.legendItem}>
{item}
</Flex>
);
};
7 changes: 6 additions & 1 deletion app/graphql/resolvers/rdf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,12 @@ export const dataCubeDimensionByIri: NonNullable<
> = async ({ cube, locale }, { iri }, { setup }, info) => {
const { sparqlClient } = await setup(info);
const dimension = (
await getCubeDimensions({ cube, locale, sparqlClient })
await getCubeDimensions({
cube,
locale,
sparqlClient,
dimensionIris: [iri],
})
).find((d) => iri === d.data.iri);
return dimension ?? null;
};
Expand Down
3 changes: 2 additions & 1 deletion app/pages/_document.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ class MyDocument extends Document {
return (
<Html data-app-version={`${process.env.NEXT_PUBLIC_VERSION}`}>
<Head>
{/* eslint-disable-next-line @next/next/no-sync-scripts */}
<script src="/api/client-env"></script>
{GA_TRACKING_ID && (
<>
Expand All @@ -24,7 +25,7 @@ class MyDocument extends Document {
</Head>
<body>
<Main />
<script noModule src="/static/ie-check.js"></script>
<script noModule src="/static/ie-check.js" defer></script>
<NextScript />
</body>
</Html>
Expand Down
8 changes: 7 additions & 1 deletion app/rdf/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,13 +158,19 @@ export const getCubeDimensions = async ({
cube,
locale,
sparqlClient,
dimensionIris,
}: {
cube: Cube;
locale: string;
sparqlClient: ParsingClient;
dimensionIris?: string[];
}): Promise<ResolvedDimension[]> => {
try {
const dimensions = cube.dimensions.filter(isObservationDimension);
const dimensions = cube.dimensions
.filter(isObservationDimension)
.filter((x) =>
dimensionIris ? dimensionIris.includes(x.path.value) : true
);
const dimensionUnits = dimensions.flatMap(getDimensionUnits);

const dimensionUnitIndex = index(
Expand Down
4 changes: 1 addition & 3 deletions e2e/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,7 @@ export const createActions = ({
chartLoadedOptions?: Parameters<typeof selectors.chart.loaded>[0]
) => {
await page.goto(
`en/browse/create/new?cube=${encodeURIComponent(
iri
)}&dataSource=${dataSource}`
`en/create/new?cube=${encodeURIComponent(iri)}&dataSource=${dataSource}`
);

await selectors.chart.loaded(chartLoadedOptions);
Expand Down
52 changes: 52 additions & 0 deletions e2e/sorting.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,55 @@ test("Segment sorting", async ({
await actions.mui.selectOption("Automatic");
}
});

test("Segment sorting with hierarchy", async ({
actions,
selectors,
screen,
within,
page,
}) => {
await actions.chart.createFrom(
"https://environment.ld.admin.ch/foen/nfi/49-19-44/cube/1",
"Int"
);
await actions.editor.selectActiveField("Color");

// Wait for color section to be ready
await selectors.edition.controlSection("Color").waitFor();

await within(selectors.edition.controlSection("Color"))
.getByText("None")
.click();

await actions.mui.selectOption("production region");
await selectors.chart.loaded();

await selectors.edition.filtersLoaded();
await selectors.chart.colorLegend(undefined, { setTimeout: 5_000 });

await within(await selectors.chart.colorLegend()).findByText(
"Southern Alps",
undefined,
{ timeout: 5000 }
);

const legendItems = await selectors.chart.colorLegendItems();

expect(await legendItems.allInnerTexts()).toEqual([
"Jura",
"Plateau",
"Pre-Alps",
"Alps",
"Southern Alps",
]);

await screen.getByText("Z → A").click();
expect(await legendItems.allInnerTexts()).toEqual([
"Southern Alps",
"Alps",
"Pre-Alps",
"Plateau",
"Jura",
]);
});

1 comment on commit 1c68672

@vercel
Copy link

@vercel vercel bot commented on 1c68672 Oct 27, 2022

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-alpha.vercel.app
visualization-tool-ixt1.vercel.app
visualization-tool-git-main-ixt1.vercel.app

Please sign in to comment.