Skip to content

Commit

Permalink
Merge pull request #359 from visualize-admin/fix/right-filters
Browse files Browse the repository at this point in the history
  • Loading branch information
ptbrowne authored Feb 17, 2022
2 parents a87169b + 172c43a commit a3dcdaf
Show file tree
Hide file tree
Showing 7 changed files with 109 additions and 27 deletions.
5 changes: 1 addition & 4 deletions app/charts/shared/use-chart-state.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { createContext, useContext } from "react";
import { ChartFields, InteractiveFiltersConfig } from "../../configurator";
import { Observation } from "../../domain/data";
import { DimensionMetaDataFragment } from "../../graphql/query-hooks";
import { Has } from "../../lib/has";
import { AreasState } from "../area/areas-state";
import { GroupedBarsState } from "../bar/bars-grouped-state";
import { BarsState } from "../bar/bars-state";
Expand Down Expand Up @@ -36,10 +37,6 @@ export type ChartState =
| MapState
| undefined;

type Has<T extends unknown, R extends string> = T extends { [k in R]: any }
? T
: never;

export type ColorsChartState = Has<ChartState, "colors">;
export const ChartContext = createContext<ChartState>(undefined);

Expand Down
68 changes: 53 additions & 15 deletions app/configurator/components/chart-configurator.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { t, Trans } from "@lingui/macro";
import { Menu, MenuButton, MenuItem, MenuList } from "@reach/menu-button";
import { isEqual, sortBy } from "lodash";
import React, { useCallback, useEffect, useState } from "react";
import React, { useCallback, useEffect, useRef, useState } from "react";
import {
DragDropContext,
Draggable,
Expand All @@ -15,7 +15,6 @@ import {
ConfiguratorStateConfiguringChart,
ConfiguratorStateSelectingChartType,
isMapConfig,
useConfiguratorState,
} from "..";
import { getFieldComponentIris } from "../../charts";
import { chartConfigOptionsUISpec } from "../../charts/chart-config-ui-options";
Expand All @@ -29,7 +28,11 @@ import {
import { DataCubeMetadata } from "../../graphql/types";
import { Icon } from "../../icons";
import { useLocale } from "../../locales/use-locale";
import { moveFilterField } from "../configurator-state";
import {
getFiltersByMappingStatus,
moveFilterField,
useConfiguratorState,
} from "../configurator-state";
import { FIELD_VALUE_NONE } from "../constants";
import {
ControlSection,
Expand Down Expand Up @@ -119,6 +122,13 @@ const DataFilterSelectGeneric = ({
);
};

const orderedIsEqual = (
obj1: Record<string, unknown>,
obj2: Record<string, unknown>
) => {
return isEqual(Object.keys(obj1), Object.keys(obj2)) && isEqual(obj1, obj2);
};

/**
* This runs every time the state changes and it ensures that the selected filters
* return at least 1 observation. Otherwise filters are reloaded.
Expand All @@ -133,19 +143,32 @@ export const useEnsurePossibleFilters = ({
const [, dispatch] = useConfiguratorState();
const [fetching, setFetching] = useState(false);
const [error, setError] = useState<Error>();

const lastFilters = useRef<ChartConfig["filters"]>();
const client = useClient();

useEffect(() => {
const run = async () => {
const { mapped: mappedFilters, unmapped: unmappedFilters } =
getFiltersByMappingStatus(
state.chartConfig.fields,
state.chartConfig.filters
);
if (
lastFilters.current &&
orderedIsEqual(lastFilters.current, unmappedFilters)
) {
return;
}
lastFilters.current = unmappedFilters;

setFetching(true);
const {
data,
error,
}: { data?: PossibleFiltersQuery; error?: CombinedError } = await client
.query(PossibleFiltersDocument, {
iri: state.dataSet,
filters: state.chartConfig.filters,
filters: unmappedFilters,
})
.toPromise();
if (error || !data) {
Expand All @@ -156,12 +179,15 @@ export const useEnsurePossibleFilters = ({
setError(undefined);
setFetching(false);

const filters = Object.fromEntries(
data.possibleFilters.map((x) => [
x.iri,
{ type: x.type, value: x.value },
])
) as ChartConfig["filters"];
const filters = Object.assign(
Object.fromEntries(
data.possibleFilters.map((x) => [
x.iri,
{ type: x.type, value: x.value },
])
) as ChartConfig["filters"],
mappedFilters
);

if (!isEqual(filters, state.chartConfig.filters)) {
dispatch({
Expand All @@ -174,7 +200,14 @@ export const useEnsurePossibleFilters = ({
};

run();
}, [client, dispatch, state.chartConfig, state.dataSet]);
}, [
client,
dispatch,
state,
state.chartConfig.fields,
state.chartConfig.filters,
state.dataSet,
]);
return { error, fetching };
};

Expand All @@ -185,18 +218,23 @@ export const ChartConfigurator = ({
}) => {
const locale = useLocale();
const [, dispatch] = useConfiguratorState();
const { fields, filters } = state.chartConfig;
const unmappedFilters = React.useMemo(() => {
return getFiltersByMappingStatus(fields, filters).unmapped;
}, [fields, filters]);

const variables = React.useMemo(
() => ({
iri: state.dataSet,
locale,
filters: state.chartConfig.filters,
filters: unmappedFilters,
// This is important for urql not to think that filters
// are the same while the order of the keys has changed.
// If this is not present, we'll have outdated dimension
// values after we change the filter order
filterKeys: Object.keys(state.chartConfig.filters).join(", "),
filterKeys: Object.keys(unmappedFilters).join(", "),
}),
[state, locale]
[state.dataSet, locale, unmappedFilters]
);
const [{ data, fetching: dataFetching }, executeQuery] =
useDataCubeMetadataWithComponentValuesQuery({
Expand Down
15 changes: 14 additions & 1 deletion app/configurator/configurator-state.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import produce from "immer";
import { mapValues } from "lodash";
import { mapValues, pickBy } from "lodash";
import setWith from "lodash/setWith";
import { useRouter } from "next/router";
import {
Expand All @@ -12,6 +12,7 @@ import {
import { Client, useClient } from "urql";
import { Reducer, useImmerReducer } from "use-immer";
import {
ConfiguratorStateConfiguringChart,
ImputationType,
isAreaConfig,
isColumnConfig,
Expand Down Expand Up @@ -569,6 +570,18 @@ export const canTransitionToPreviousStep = (
return true;
};

export const getFiltersByMappingStatus = (
fields: ConfiguratorStateConfiguringChart["chartConfig"]["fields"],
filters: ConfiguratorStateConfiguringChart["chartConfig"]["filters"]
) => {
const mappedIris = new Set(
Object.values(fields).map((fieldValue) => fieldValue.componentIri)
);
const unmapped = pickBy(filters, (value, iri) => !mappedIris.has(iri));
const mapped = pickBy(filters, (value, iri) => mappedIris.has(iri));
return { unmapped, mapped };
};

const reducer: Reducer<ConfiguratorState, ConfiguratorStateAction> = (
draft,
action
Expand Down
4 changes: 2 additions & 2 deletions app/configurator/table/table-chart-options.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import {
import { useConfiguratorState } from "../configurator-state";
import { TableSortingOptions } from "./table-chart-sorting-options";
import { updateIsGroup, updateIsHidden } from "./table-config-state";
import { canDimensionBeMultiFiltered } from "../../domain/data";

const useTableColumnGroupHiddenField = ({
path,
Expand Down Expand Up @@ -294,8 +295,7 @@ export const TableColumnOptions = ({
</ControlSectionContent>
</ControlSection>
)}
{component.__typename === "NominalDimension" ||
component.__typename === "OrdinalDimension" ? (
{canDimensionBeMultiFiltered(component) ? (
<ControlSection>
<SectionTitle disabled={!component} iconName="filter">
<Trans id="controls.section.filter">Filter</Trans>
Expand Down
36 changes: 31 additions & 5 deletions app/domain/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import {
DimensionMetaDataFragment,
GeoCoordinatesDimension,
GeoShapesDimension,
NominalDimension,
OrdinalDimension,
} from "../graphql/query-hooks";

export type RawObservationValue = Literal | NamedNode;
Expand Down Expand Up @@ -128,16 +130,28 @@ export const parseObservationValue = ({
*/
export const getTimeDimensions = (dimensions: DimensionMetaDataFragment[]) =>
dimensions.filter((d) => d.__typename === "TemporalDimension");

export const isCategoricalDimension = (
d: DimensionMetaDataFragment
): d is NominalDimension | OrdinalDimension => {
return isNominalDimension(d) || isOrdinalDimension(d);
};

export const canDimensionBeMultiFiltered = (d: DimensionMetaDataFragment) => {
return (
isNominalDimension(d) ||
isOrdinalDimension(d) ||
isGeoCoordinatesDimension(d) ||
isGeoShapesDimension(d)
);
};

/**
* @fixme use metadata to filter categorical dimension!
*/
export const getCategoricalDimensions = (
dimensions: DimensionMetaDataFragment[]
) =>
dimensions.filter(
(d) =>
d.__typename === "NominalDimension" || d.__typename === "OrdinalDimension"
);
) => dimensions.filter(isCategoricalDimension);

export const getGeoCoordinatesDimensions = (
dimensions: DimensionMetaDataFragment[]
Expand Down Expand Up @@ -165,6 +179,18 @@ export const getDimensionsByDimensionType = ({
dimensionTypes.includes(component.__typename)
);

export const isNominalDimension = (
dimension?: DimensionMetaDataFragment
): dimension is GeoCoordinatesDimension => {
return dimension?.__typename === "GeoCoordinatesDimension";
};

export const isOrdinalDimension = (
dimension?: DimensionMetaDataFragment
): dimension is GeoCoordinatesDimension => {
return dimension?.__typename === "GeoCoordinatesDimension";
};

export const isGeoCoordinatesDimension = (
dimension?: DimensionMetaDataFragment
): dimension is GeoCoordinatesDimension => {
Expand Down
5 changes: 5 additions & 0 deletions app/lib/has.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export type Has<T extends unknown, R extends string> = T extends {
[k in R]: any;
}
? T
: never;
3 changes: 3 additions & 0 deletions app/utils/dimension-hierarchy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,9 @@ type TreeSorters = Record<

/** Recursively sorts tree children */
export const sortTree = (tree: HierarchyValue[], sorters?: TreeSorters) => {
if (!tree.length) {
return;
}
const dimensionIri = tree[0].dimensionIri;
const sorter = sorters?.[dimensionIri] || defaultSorter;
if (dimensionIri && sorter) {
Expand Down

0 comments on commit a3dcdaf

Please sign in to comment.