Skip to content

Commit

Permalink
feat: Ability to reorder filters
Browse files Browse the repository at this point in the history
Filter values are filtered given the filters above
  • Loading branch information
ptbrowne committed Jan 21, 2022
1 parent 0901d4c commit 6a0afa2
Show file tree
Hide file tree
Showing 5 changed files with 165 additions and 12 deletions.
3 changes: 3 additions & 0 deletions app/components/form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -157,12 +157,14 @@ export const Select = ({
options,
onChange,
sortOptions = true,
controls,
}: {
id: string;
options: Option[];
label?: ReactNode;
disabled?: boolean;
sortOptions?: boolean;
controls?: React.ReactNode;
} & SelectProps) => {
const locale = useLocale();
const sortedOptions = useMemo(() => {
Expand All @@ -181,6 +183,7 @@ export const Select = ({
{label && (
<Label htmlFor={id} disabled={disabled} smaller>
{label}
{controls}
</Label>
)}
<TUISelect
Expand Down
159 changes: 151 additions & 8 deletions app/configurator/components/chart-configurator.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { Trans } from "@lingui/macro";
import * as React from "react";
import { ChartConfig, ConfiguratorStateConfiguringChart } from "..";
import {
ChartConfig,
ConfiguratorStateConfiguringChart,
useConfiguratorState,
} from "..";
import { chartConfigOptionsUISpec } from "../../charts/chart-config-ui-options";
import { getFieldComponentIris } from "../../charts";
import { useDataCubeMetadataWithComponentValuesQuery } from "../../graphql/query-hooks";
Expand All @@ -18,21 +22,53 @@ import {
} from "./field";
import { Loading } from "../../components/hint";
import { Box } from "theme-ui";
import produce from "immer";
import { useCallback } from "react";
import { mapValues, sortBy } from "lodash";

const DataFilterSelectGeneric = ({
dimension,
index,
onMove,
}: {
dimension: DataCubeMetadata["dimensions"][number];
isOptional?: boolean;
index: number;
onMove: (n: number) => void;
}) => {
const controls = (
<>
<button
style={{
background: "transparent",
cursor: "pointer",
color: "#666",
border: "transparent",
}}
onClick={() => onMove(-1)}
>
</button>
<button
style={{
background: "transparent",
cursor: "pointer",
color: "#666",
border: "transparent",
}}
onClick={() => onMove(1)}
>
</button>
</>
);
return (
<Box sx={{ px: 2, mb: 2 }}>
{dimension.__typename === "TemporalDimension" ? (
<DataFilterSelectTime
dimensionIri={dimension.iri}
label={`${index + 1}. ${dimension.label}`}
controls={controls}
from={dimension.values[0].value}
to={dimension.values[1].value}
timeUnit={dimension.timeUnit}
Expand All @@ -45,6 +81,7 @@ const DataFilterSelectGeneric = ({
<DataFilterSelect
dimensionIri={dimension.iri}
label={`${index + 1}. ${dimension.label}`}
controls={controls}
options={dimension.values}
disabled={false}
id={`select-single-filter-${index}`}
Expand All @@ -55,26 +92,130 @@ const DataFilterSelectGeneric = ({
);
};

const moveFilterField = produce(
(chartConfig: ChartConfig, { dimensionIri, delta }) => {
const keys = Object.keys(chartConfig.filters);
const fieldIndex = Object.keys(chartConfig.filters).indexOf(dimensionIri);
if (fieldIndex == 0 && delta === -1) {
return;
}
if (fieldIndex === keys.length - 1 && delta === 1) {
return;
}
const replaced = keys[fieldIndex + delta];
keys[fieldIndex + delta] = dimensionIri;
keys[fieldIndex] = replaced;
chartConfig.filters = Object.fromEntries(
keys.map((k) => [k, chartConfig.filters[k]])
);
}
);

const ensureFilterValuesCorrect = produce(
(
chartConfig: ChartConfig,
{ dimensions }: { dimensions: DataCubeMetadata["dimensions"] }
) => {
let dirty = false;
const newFilters = mapValues(chartConfig.filters, (f, dimensionIri) => {
if (f.type !== "single") {
return f;
}
const values = dimensions.find((dim) => dim.iri === dimensionIri)?.values;
if (!values || values.length === 0) {
return f;
}
if (values.find((v) => v.value === f.value)) {
return f;
}
dirty = true;
f.value = values[0].value;
return f;
});
if (dirty) {
chartConfig.filters = newFilters;
}
}
);

export const ChartConfigurator = ({
state,
}: {
state: ConfiguratorStateConfiguringChart;
}) => {
const locale = useLocale();
const [{ data }] = useDataCubeMetadataWithComponentValuesQuery({
variables: {
const [, dispatch] = useConfiguratorState();
const variables = React.useMemo(
() => ({
date: new Date(),
iri: state.dataSet,
locale,
filters: state.chartConfig.filters,
}),
[state, locale]
);
const [{ data, fetching }, executeQuery] =
useDataCubeMetadataWithComponentValuesQuery({
variables: variables,
});
const metaData = data?.dataCubeByIri;

const handleMove = useCallback(
(dimensionIri: string, delta: number) => {
if (!metaData) {
return;
}

const chartConfig = moveFilterField(state.chartConfig, {
dimensionIri,
delta,
});

dispatch({
type: "CHART_CONFIG_REPLACED",
value: {
chartConfig,
dataSetMetadata: metaData,
},
});
},
});
[dispatch, metaData, state.chartConfig]
);

React.useEffect(() => {
executeQuery({
requestPolicy: "network-only",
variables,
});
}, [variables, executeQuery]);

React.useEffect(() => {
if (!metaData || !data || !data.dataCubeByIri) {
return;
}
// Make sure that the filters are in line with the values
const chartConfig = ensureFilterValuesCorrect(state.chartConfig, {
dimensions: data.dataCubeByIri.dimensions,
});

dispatch({
type: "CHART_CONFIG_REPLACED",
value: {
chartConfig,
dataSetMetadata: metaData,
},
});
}, [data, dispatch, metaData, state.chartConfig]);

if (data?.dataCubeByIri) {
const mappedIris = getFieldComponentIris(state.chartConfig.fields);
const filterDimensions = data?.dataCubeByIri.dimensions
.filter((dim) => !mappedIris.has(dim.iri))
.sort((a, b) => (a.isKeyDimension ? 0 : 1));

const keysOrder = Object.fromEntries(
Object.keys(state.chartConfig.filters).map((k, i) => [k, i])
);
const filterDimensions = sortBy(
data?.dataCubeByIri.dimensions.filter((dim) => !mappedIris.has(dim.iri)),
[(x) => keysOrder[x.iri] ?? Infinity]
);
return (
<>
<ControlSection>
Expand All @@ -96,13 +237,15 @@ export const ChartConfigurator = ({
<ControlSection>
<SectionTitle titleId="controls-data">
<Trans id="controls.section.data.filters">Filters</Trans>
{fetching ? "..." : ""}
</SectionTitle>
<ControlSectionContent side="left" aria-labelledby="controls-data">
{filterDimensions.map((dimension, i) => (
<DataFilterSelectGeneric
key={dimension.iri}
dimension={dimension}
index={i}
onMove={(n) => handleMove(dimension.iri, n)}
/>
))}
</ControlSectionContent>
Expand Down
6 changes: 6 additions & 0 deletions app/configurator/components/field.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,15 @@ export const DataFilterSelect = ({
id,
disabled,
isOptional,
controls,
}: {
dimensionIri: string;
label: string;
options: Option[];
id: string;
disabled?: boolean;
isOptional?: boolean;
controls: React.ReactNode;
}) => {
const fieldProps = useSingleFilterSelect({ dimensionIri });

Expand Down Expand Up @@ -106,6 +108,7 @@ export const DataFilterSelect = ({
label={isOptional ? `${label} (${optionalLabel})` : label}
disabled={disabled}
options={allOptions}
controls={controls}
{...fieldProps}
></Select>
);
Expand All @@ -121,6 +124,7 @@ export const DataFilterSelectTime = ({
id,
disabled,
isOptional,
controls,
}: {
dimensionIri: string;
label: string;
Expand All @@ -131,6 +135,7 @@ export const DataFilterSelectTime = ({
id: string;
disabled?: boolean;
isOptional?: boolean;
controls?: React.ReactNode;
}) => {
const fieldProps = useSingleFilterSelect({ dimensionIri });
const formatLocale = useTimeFormatLocale();
Expand Down Expand Up @@ -184,6 +189,7 @@ export const DataFilterSelectTime = ({
disabled={disabled}
options={allOptions}
sortOptions={false}
controls={controls}
{...fieldProps}
></Select>
);
Expand Down
1 change: 0 additions & 1 deletion app/pages/api/graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import configureCors from "cors";
import DataLoader from "dataloader";
import "global-agent/bootstrap";
import { NextApiRequest, NextApiResponse } from "next";
import { Filters } from "../../configurator";
import { resolvers } from "../../graphql/resolvers";
import typeDefs from "../../graphql/schema.graphql";
import { runMiddleware } from "../../lib/run-middleware";
Expand Down
8 changes: 5 additions & 3 deletions app/rdf/query-dimension-values.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,12 +81,14 @@ export async function loadDimensionValues(
): Promise<Array<Literal | NamedNode>> {
const dimensionIri = dimension.path;

let filterList = filters ? Object.entries(filters) : [];
filterList = filterList.slice(
let allFiltersList = filters ? Object.entries(filters) : [];
const filterList = allFiltersList.slice(
0,
filterList.findIndex(([iri]) => iri == dimensionIri?.value) + 1
allFiltersList.findIndex(([iri]) => iri == dimensionIri?.value)
);

console.log("filters for ", dimensionIri?.value, allFiltersList, filterList);

let query = SELECT.DISTINCT`?value`.WHERE`
${datasetIri} ${cubeNs.observationSet} ?observationSet .
?observationSet ${cubeNs.observation} ?observation .
Expand Down

0 comments on commit 6a0afa2

Please sign in to comment.