diff --git a/app/charts/area/chart-area.tsx b/app/charts/area/chart-area.tsx index dcc9f56c7..9a54dbd51 100644 --- a/app/charts/area/chart-area.tsx +++ b/app/charts/area/chart-area.tsx @@ -2,9 +2,11 @@ import { memo } from "react"; import { Areas } from "@/charts/area/areas"; import { AreaChart } from "@/charts/area/areas-state"; +import { ChartLoadingWrapper } from "@/charts/chart-loading-wrapper"; import { AxisHeightLinear } from "@/charts/shared/axis-height-linear"; import { AxisTime, AxisTimeDomain } from "@/charts/shared/axis-width-time"; import { BrushTime } from "@/charts/shared/brush"; +import { getChartConfigComponentIris } from "@/charts/shared/chart-helpers"; import { ChartContainer, ChartSvg } from "@/charts/shared/containers"; import { Ruler } from "@/charts/shared/interaction/ruler"; import { Tooltip } from "@/charts/shared/interaction/tooltip"; @@ -24,20 +26,23 @@ import { } from "@/graphql/query-hooks"; import { useLocale } from "@/locales/use-locale"; -import { ChartLoadingWrapper } from "../chart-loading-wrapper"; - export const ChartAreasVisualization = ({ dataSetIri, dataSource, chartConfig, queryFilters, + published, }: { dataSetIri: string; dataSource: DataSource; chartConfig: AreaConfig; queryFilters: QueryFilters; + published: boolean; }) => { const locale = useLocale(); + const componentIrisToFilterBy = published + ? getChartConfigComponentIris(chartConfig) + : undefined; const [metadataQuery] = useDataCubeMetadataQuery({ variables: { iri: dataSetIri, @@ -51,6 +56,7 @@ export const ChartAreasVisualization = ({ iri: dataSetIri, sourceType: dataSource.type, sourceUrl: dataSource.url, + componentIris: componentIrisToFilterBy, locale, }, }); @@ -60,7 +66,7 @@ export const ChartAreasVisualization = ({ sourceType: dataSource.type, sourceUrl: dataSource.url, locale, - dimensions: null, + componentIris: componentIrisToFilterBy, filters: queryFilters, }, }); diff --git a/app/charts/column/chart-column.tsx b/app/charts/column/chart-column.tsx index 0c928447b..d338b6f19 100644 --- a/app/charts/column/chart-column.tsx +++ b/app/charts/column/chart-column.tsx @@ -1,5 +1,6 @@ import { memo } from "react"; +import { ChartLoadingWrapper } from "@/charts/chart-loading-wrapper"; import { ColumnsGrouped, ErrorWhiskers as ErrorWhiskersGrouped, @@ -16,6 +17,7 @@ import { AxisWidthBandDomain, } from "@/charts/shared/axis-width-band"; import { BrushTime } from "@/charts/shared/brush"; +import { getChartConfigComponentIris } from "@/charts/shared/chart-helpers"; import { ChartContainer, ChartSvg } from "@/charts/shared/containers"; import { Tooltip } from "@/charts/shared/interaction/tooltip"; import { LegendColor } from "@/charts/shared/legend-color"; @@ -34,20 +36,23 @@ import { } from "@/graphql/query-hooks"; import { useLocale } from "@/locales/use-locale"; -import { ChartLoadingWrapper } from "../chart-loading-wrapper"; - export const ChartColumnsVisualization = ({ dataSetIri, dataSource, chartConfig, queryFilters, + published, }: { dataSetIri: string; dataSource: DataSource; chartConfig: ColumnConfig; queryFilters: QueryFilters; + published: boolean; }) => { const locale = useLocale(); + const componentIrisToFilterBy = published + ? getChartConfigComponentIris(chartConfig) + : undefined; const [metadataQuery] = useDataCubeMetadataQuery({ variables: { iri: dataSetIri, @@ -62,6 +67,7 @@ export const ChartColumnsVisualization = ({ sourceType: dataSource.type, sourceUrl: dataSource.url, locale, + componentIris: componentIrisToFilterBy, }, }); const [observationsQuery] = useDataCubeObservationsQuery({ @@ -70,7 +76,7 @@ export const ChartColumnsVisualization = ({ sourceType: dataSource.type, sourceUrl: dataSource.url, locale, - dimensions: null, + componentIris: componentIrisToFilterBy, filters: queryFilters, }, }); diff --git a/app/charts/line/chart-lines.tsx b/app/charts/line/chart-lines.tsx index 5d0d44e7e..e760eb4ae 100644 --- a/app/charts/line/chart-lines.tsx +++ b/app/charts/line/chart-lines.tsx @@ -1,10 +1,12 @@ import { memo } from "react"; +import { ChartLoadingWrapper } from "@/charts/chart-loading-wrapper"; import { Lines } from "@/charts/line/lines"; import { LineChart } from "@/charts/line/lines-state"; import { AxisHeightLinear } from "@/charts/shared/axis-height-linear"; import { AxisTime, AxisTimeDomain } from "@/charts/shared/axis-width-time"; import { BrushTime } from "@/charts/shared/brush"; +import { getChartConfigComponentIris } from "@/charts/shared/chart-helpers"; import { ChartContainer, ChartSvg } from "@/charts/shared/containers"; import { HoverDotMultiple } from "@/charts/shared/interaction/hover-dots-multiple"; import { Ruler } from "@/charts/shared/interaction/ruler"; @@ -25,20 +27,23 @@ import { } from "@/graphql/query-hooks"; import { useLocale } from "@/locales/use-locale"; -import { ChartLoadingWrapper } from "../chart-loading-wrapper"; - export const ChartLinesVisualization = ({ dataSetIri, dataSource, chartConfig, queryFilters, + published, }: { dataSetIri: string; dataSource: DataSource; chartConfig: LineConfig; queryFilters: QueryFilters; + published: boolean; }) => { const locale = useLocale(); + const componentIrisToFilterBy = published + ? getChartConfigComponentIris(chartConfig) + : undefined; const [metadataQuery] = useDataCubeMetadataQuery({ variables: { iri: dataSetIri, @@ -53,6 +58,7 @@ export const ChartLinesVisualization = ({ sourceType: dataSource.type, sourceUrl: dataSource.url, locale, + componentIris: componentIrisToFilterBy, }, }); const [observationsQuery] = useDataCubeObservationsQuery({ @@ -61,7 +67,7 @@ export const ChartLinesVisualization = ({ sourceType: dataSource.type, sourceUrl: dataSource.url, locale, - dimensions: null, + componentIris: componentIrisToFilterBy, filters: queryFilters, }, }); diff --git a/app/charts/map/chart-map.tsx b/app/charts/map/chart-map.tsx index 62b8ea8f1..f64e6aa4f 100644 --- a/app/charts/map/chart-map.tsx +++ b/app/charts/map/chart-map.tsx @@ -3,10 +3,13 @@ import keyBy from "lodash/keyBy"; import { useMemo } from "react"; import { mesh as topojsonMesh } from "topojson-client"; +import { ChartLoadingWrapper } from "@/charts/chart-loading-wrapper"; +import { prepareTopojson } from "@/charts/map/helpers"; import { MapComponent } from "@/charts/map/map"; import { MapLegend } from "@/charts/map/map-legend"; import { MapChart } from "@/charts/map/map-state"; import { MapTooltip } from "@/charts/map/map-tooltip"; +import { getChartConfigComponentIris } from "@/charts/shared/chart-helpers"; import { ChartContainer } from "@/charts/shared/containers"; import { BaseLayer, @@ -34,24 +37,25 @@ import { } from "@/graphql/query-hooks"; import { useLocale } from "@/locales/use-locale"; -import { ChartLoadingWrapper } from "../chart-loading-wrapper"; - -import { prepareTopojson } from "./helpers"; - export const ChartMapVisualization = ({ dataSetIri, dataSource, chartConfig, queryFilters, + published, }: { dataSetIri: string; dataSource: DataSource; chartConfig: MapConfig; queryFilters: QueryFilters; + published: boolean; }) => { const locale = useLocale(); const areaDimensionIri = chartConfig.fields.areaLayer?.componentIri || ""; const symbolDimensionIri = chartConfig.fields.symbolLayer?.componentIri || ""; + const componentIrisToFilterBy = published + ? getChartConfigComponentIris(chartConfig) + : undefined; const [metadataQuery] = useDataCubeMetadataQuery({ variables: { iri: dataSetIri, @@ -66,6 +70,7 @@ export const ChartMapVisualization = ({ sourceType: dataSource.type, sourceUrl: dataSource.url, locale, + componentIris: componentIrisToFilterBy, }, }); const [observationsQuery] = useDataCubeObservationsQuery({ @@ -74,7 +79,7 @@ export const ChartMapVisualization = ({ sourceType: dataSource.type, sourceUrl: dataSource.url, locale, - dimensions: null, // FIXME: Try to load less dimensions + componentIris: componentIrisToFilterBy, filters: queryFilters, }, }); diff --git a/app/charts/map/map.tsx b/app/charts/map/map.tsx index b61b6d1cd..040b70531 100644 --- a/app/charts/map/map.tsx +++ b/app/charts/map/map.tsx @@ -369,7 +369,7 @@ export const MapComponent = () => { setMap(null); }} onLoad={(e) => { - setMap(e.target); + setMap(e.target as mapboxgl.Map); currentBBox.current = e.target.getBounds().toArray() as BBox; /** diff --git a/app/charts/map/ref.ts b/app/charts/map/ref.ts index 6d51d6364..dac81234c 100644 --- a/app/charts/map/ref.ts +++ b/app/charts/map/ref.ts @@ -1,16 +1,14 @@ -import { Map } from "mapbox-gl"; - /** * We need to access the map from the map controls. Until we have a good solution * for sibling components, we use a global non-observable ref. */ -let map: Map | null = null; +let map: mapboxgl.Map | null = null; const getMap = () => { return map; }; -const setMap = (d: Map | null) => { +const setMap = (d: mapboxgl.Map | null) => { map = d; }; diff --git a/app/charts/pie/chart-pie.tsx b/app/charts/pie/chart-pie.tsx index 571c35938..52e3ee395 100644 --- a/app/charts/pie/chart-pie.tsx +++ b/app/charts/pie/chart-pie.tsx @@ -3,6 +3,7 @@ import { memo } from "react"; import { ChartLoadingWrapper } from "@/charts/chart-loading-wrapper"; import { Pie } from "@/charts/pie/pie"; import { PieChart } from "@/charts/pie/pie-state"; +import { getChartConfigComponentIris } from "@/charts/shared/chart-helpers"; import { ChartContainer, ChartSvg } from "@/charts/shared/containers"; import { Tooltip } from "@/charts/shared/interaction/tooltip"; import { LegendColor } from "@/charts/shared/legend-color"; @@ -27,13 +28,18 @@ export const ChartPieVisualization = ({ dataSource, chartConfig, queryFilters, + published, }: { dataSetIri: string; dataSource: DataSource; chartConfig: PieConfig; queryFilters: QueryFilters; + published: boolean; }) => { const locale = useLocale(); + const componentIrisToFilterBy = published + ? getChartConfigComponentIris(chartConfig) + : undefined; const [metadataQuery] = useDataCubeMetadataQuery({ variables: { iri: dataSetIri, @@ -48,6 +54,7 @@ export const ChartPieVisualization = ({ sourceType: dataSource.type, sourceUrl: dataSource.url, locale, + componentIris: componentIrisToFilterBy, }, }); const [observationsQuery] = useDataCubeObservationsQuery({ @@ -56,7 +63,7 @@ export const ChartPieVisualization = ({ sourceType: dataSource.type, sourceUrl: dataSource.url, locale, - dimensions: null, + componentIris: componentIrisToFilterBy, filters: queryFilters, }, }); diff --git a/app/charts/scatterplot/chart-scatterplot.tsx b/app/charts/scatterplot/chart-scatterplot.tsx index 4f5ac0771..f0d2af8f7 100644 --- a/app/charts/scatterplot/chart-scatterplot.tsx +++ b/app/charts/scatterplot/chart-scatterplot.tsx @@ -1,5 +1,6 @@ import { memo } from "react"; +import { ChartLoadingWrapper } from "@/charts/chart-loading-wrapper"; import { Scatterplot } from "@/charts/scatterplot/scatterplot-simple"; import { ScatterplotChart } from "@/charts/scatterplot/scatterplot-state"; import { @@ -10,6 +11,7 @@ import { AxisWidthLinear, AxisWidthLinearDomain, } from "@/charts/shared/axis-width-linear"; +import { getChartConfigComponentIris } from "@/charts/shared/chart-helpers"; import { ChartContainer, ChartSvg } from "@/charts/shared/containers"; import { Tooltip } from "@/charts/shared/interaction/tooltip"; import { LegendColor } from "@/charts/shared/legend-color"; @@ -29,20 +31,23 @@ import { } from "@/graphql/query-hooks"; import { useLocale } from "@/locales/use-locale"; -import { ChartLoadingWrapper } from "../chart-loading-wrapper"; - export const ChartScatterplotVisualization = ({ dataSetIri, dataSource, chartConfig, queryFilters, + published, }: { dataSetIri: string; dataSource: DataSource; chartConfig: ScatterPlotConfig; queryFilters: QueryFilters; + published: boolean; }) => { const locale = useLocale(); + const componentIrisToFilterBy = published + ? getChartConfigComponentIris(chartConfig) + : undefined; const [metadataQuery] = useDataCubeMetadataQuery({ variables: { iri: dataSetIri, @@ -57,6 +62,7 @@ export const ChartScatterplotVisualization = ({ sourceType: dataSource.type, sourceUrl: dataSource.url, locale, + componentIris: componentIrisToFilterBy, }, }); const [observationsQuery] = useDataCubeObservationsQuery({ @@ -65,7 +71,7 @@ export const ChartScatterplotVisualization = ({ sourceType: dataSource.type, sourceUrl: dataSource.url, locale, - dimensions: null, + componentIris: componentIrisToFilterBy, filters: queryFilters, }, }); diff --git a/app/charts/shared/chart-helpers.spec.tsx b/app/charts/shared/chart-helpers.spec.tsx index 9ffe383bf..086021094 100644 --- a/app/charts/shared/chart-helpers.spec.tsx +++ b/app/charts/shared/chart-helpers.spec.tsx @@ -5,12 +5,20 @@ import { getWideData, prepareQueryFilters, getMaybeTemporalDimensionValues, + getChartConfigComponentIris, } from "@/charts/shared/chart-helpers"; import { InteractiveFiltersState } from "@/charts/shared/use-interactive-filters"; -import { ChartType, Filters, InteractiveFiltersConfig } from "@/configurator"; +import { + ChartType, + Filters, + InteractiveFiltersConfig, + LineConfig, + MapConfig, +} from "@/configurator"; import { FIELD_VALUE_NONE } from "@/configurator/constants"; import { Observation } from "@/domain/data"; import { DimensionMetadataFragment } from "@/graphql/query-hooks"; +import map1Fixture from "@/test/__fixtures/config/int/map-waldflasche.json"; import line1Fixture from "@/test/__fixtures/config/prod/line-1.json"; const makeCubeNsGetters = (cubeIri: string) => ({ @@ -182,3 +190,33 @@ describe("getMaybeTemporalDimensionValues", () => { expect(result).toEqual(dimension.values); }); }); + +describe("getChartConfigComponentIris", () => { + const lineConfig = line1Fixture.data.chartConfig as unknown as LineConfig; + const mapConfig = map1Fixture.data.chartConfig as unknown as MapConfig; + + it("should return correct componentIris for line chart", () => { + const componentsIris = getChartConfigComponentIris(lineConfig); + expect(componentsIris).toEqual([ + "http://environment.ld.admin.ch/foen/px/0703010000_105/dimension/0", + "http://environment.ld.admin.ch/foen/px/0703010000_105/measure/0", + "http://environment.ld.admin.ch/foen/px/0703010000_105/dimension/1", + "http://environment.ld.admin.ch/foen/px/0703010000_105/dimension/2", + "http://environment.ld.admin.ch/foen/px/0703010000_105/dimension/3", + "http://environment.ld.admin.ch/foen/px/0703010000_105/dimension/4", + ]); + }); + + it("should return correct componentIris for map chart", () => { + const componentsIris = getChartConfigComponentIris(mapConfig); + console.log(componentsIris); + expect(componentsIris).toEqual([ + "https://environment.ld.admin.ch/foen/nfi/unitOfReference", + "https://environment.ld.admin.ch/foen/nfi/forestArea", + "https://environment.ld.admin.ch/foen/nfi/inventory", + "https://environment.ld.admin.ch/foen/nfi/struk", + "https://environment.ld.admin.ch/foen/nfi/grid", + "https://environment.ld.admin.ch/foen/nfi/unitOfEvaluation", + ]); + }); +}); diff --git a/app/charts/shared/chart-helpers.tsx b/app/charts/shared/chart-helpers.tsx index 39cb8284c..717987e2b 100644 --- a/app/charts/shared/chart-helpers.tsx +++ b/app/charts/shared/chart-helpers.tsx @@ -12,6 +12,11 @@ import { InteractiveFiltersState, useInteractiveFilters, } from "@/charts/shared/use-interactive-filters"; +import { + CategoricalColorField, + InteractiveFiltersTimeSlider, + NumericalColorField, +} from "@/configurator"; import { parseDate } from "@/configurator/components/ui-helpers"; import { ChartConfig, @@ -19,8 +24,12 @@ import { Filters, ImputationType, InteractiveFiltersConfig, + InteractiveFiltersDataConfig, + InteractiveFiltersLegend, + InteractiveFiltersTimeRange, isAreaConfig, QueryFilters, + MapConfig, } from "@/configurator/config-types"; import { FIELD_VALUE_NONE } from "@/configurator/constants"; import { isTemporalDimension, Observation } from "@/domain/data"; @@ -76,7 +85,77 @@ export const useQueryFilters = ({ ]); }; -type ValuePredicate = (v: any) => boolean; +type IFKey = keyof NonNullable; + +export const getChartConfigFilterComponentIris = ({ filters }: ChartConfig) => { + return Object.keys(filters); +}; + +const getMapChartConfigAdditionalFields = ({ fields }: MapConfig) => { + const { areaLayer, symbolLayer } = fields; + const additionalFields = []; + + if (areaLayer) { + additionalFields.push(areaLayer.color.componentIri); + } + + if (symbolLayer) { + if (symbolLayer.measureIri !== FIELD_VALUE_NONE) { + additionalFields.push(symbolLayer.measureIri); + } + + if (["categorical", "numerical"].includes(symbolLayer.color.type)) { + additionalFields.push( + (symbolLayer.color as CategoricalColorField | NumericalColorField) + .componentIri + ); + } + } + + return additionalFields; +}; + +export const getChartConfigComponentIris = (chartConfig: ChartConfig) => { + const { fields, interactiveFiltersConfig: IFConfig } = chartConfig; + const fieldIris = Object.values(fields).map((d) => d.componentIri); + const additionalFieldIris = + chartConfig.chartType === "map" + ? getMapChartConfigAdditionalFields(chartConfig) + : []; + const filterIris = getChartConfigFilterComponentIris(chartConfig); + const IFKeys = IFConfig ? (Object.keys(IFConfig) as IFKey[]) : []; + const IFIris: string[] = []; + + if (IFConfig) { + IFKeys.forEach((k) => { + const v = IFConfig[k]; + + switch (k) { + case "timeSlider": + IFIris.push((v as InteractiveFiltersTimeSlider).componentIri); + break; + case "legend": + IFIris.push((v as InteractiveFiltersLegend).componentIri); + break; + case "timeRange": + IFIris.push((v as InteractiveFiltersTimeRange).componentIri); + break; + case "dataFilters": + IFIris.push(...(v as InteractiveFiltersDataConfig).componentIris); + break; + default: + const _exhaustiveCheck: never = k; + return _exhaustiveCheck; + } + }); + } + + return uniq( + [...fieldIris, ...additionalFieldIris, ...filterIris, ...IFIris].filter( + Boolean + ) + ); +}; export const usePlottableData = ({ data, @@ -102,6 +181,8 @@ export const usePlottableData = ({ return useMemo(() => data.filter(isPlottable), [data, isPlottable]); }; +type ValuePredicate = (v: any) => boolean; + /** Prepares the data to be used in charts, taking interactive filters into account. */ export const useDataAfterInteractiveFilters = ({ sortedData, diff --git a/app/charts/table/chart-table.tsx b/app/charts/table/chart-table.tsx index b43933181..51302e9d2 100644 --- a/app/charts/table/chart-table.tsx +++ b/app/charts/table/chart-table.tsx @@ -1,6 +1,7 @@ import { memo } from "react"; import { ChartLoadingWrapper } from "@/charts/chart-loading-wrapper"; +import { getChartConfigComponentIris } from "@/charts/shared/chart-helpers"; import { Table } from "@/charts/table/table"; import { TableChart } from "@/charts/table/table-state"; import { DataSource, TableConfig } from "@/configurator"; @@ -17,12 +18,17 @@ export const ChartTableVisualization = ({ dataSetIri, dataSource, chartConfig, + published, }: { dataSetIri: string; dataSource: DataSource; chartConfig: TableConfig; + published: boolean; }) => { const locale = useLocale(); + const componentIrisToFilterBy = published + ? getChartConfigComponentIris(chartConfig) + : undefined; const [metadataQuery] = useDataCubeMetadataQuery({ variables: { iri: dataSetIri, @@ -37,6 +43,7 @@ export const ChartTableVisualization = ({ sourceType: dataSource.type, sourceUrl: dataSource.url, locale, + componentIris: componentIrisToFilterBy, }, }); const [observationsQuery] = useDataCubeObservationsQuery({ @@ -45,7 +52,7 @@ export const ChartTableVisualization = ({ sourceType: dataSource.type, sourceUrl: dataSource.url, locale, - dimensions: null, + componentIris: componentIrisToFilterBy, filters: chartConfig.filters, }, }); diff --git a/app/components/chart-filters-list.tsx b/app/components/chart-filters-list.tsx index 383a19471..6ec6eb014 100644 --- a/app/components/chart-filters-list.tsx +++ b/app/components/chart-filters-list.tsx @@ -2,6 +2,7 @@ import { Box, Typography } from "@mui/material"; import { Fragment } from "react"; import { useQueryFilters } from "@/charts/shared/chart-helpers"; +import { getChartConfigFilterComponentIris } from "@/charts/shared/chart-helpers"; import { OpenMetadataPanelWrapper } from "@/components/metadata-panel"; import { ChartConfig, DataSource } from "@/configurator"; import { isTemporalDimension } from "@/domain/data"; @@ -27,6 +28,7 @@ export const ChartFiltersList = ({ sourceType: dataSource.type, sourceUrl: dataSource.url, locale, + componentIris: getChartConfigFilterComponentIris(chartConfig), }, }); diff --git a/app/components/chart-footnotes.tsx b/app/components/chart-footnotes.tsx index f659fdd8f..58266089a 100644 --- a/app/components/chart-footnotes.tsx +++ b/app/components/chart-footnotes.tsx @@ -3,6 +3,10 @@ import { Box, Button, Link, Theme, Typography } from "@mui/material"; import { makeStyles } from "@mui/styles"; import { PropsWithChildren, useEffect, useMemo, useState } from "react"; +import { + getChartConfigComponentIris, + useQueryFilters, +} from "@/charts/shared/chart-helpers"; import { useChartTablePreview } from "@/components/chart-table-preview"; import { DataDownloadMenu, RunSparqlQuery } from "@/components/data-download"; import { ChartConfig, DataSource } from "@/configurator"; @@ -16,8 +20,6 @@ import { useLocale } from "@/locales/use-locale"; import { useEmbedOptions } from "@/utils/embed"; import { makeOpenDataLink } from "@/utils/opendata"; -import { useQueryFilters } from "../charts/shared/chart-helpers"; - export const useFootnotesStyles = makeStyles( (theme) => ({ actions: { @@ -92,13 +94,14 @@ export const ChartFootnotes = ({ // Data for data download const filters = useQueryFilters({ chartConfig }); + const componentIrisToFilterBy = getChartConfigComponentIris(chartConfig); const [{ data: visibleData }] = useDataCubeObservationsQuery({ variables: { iri: dataSetIri, sourceType: dataSource.type, sourceUrl: dataSource.url, locale, - dimensions: null, + componentIris: componentIrisToFilterBy, filters, }, }); @@ -189,6 +192,7 @@ export const ChartFootnotes = ({ dataSetIri={dataSetIri} dataSource={dataSource} title={dataCubeByIri.title} + componentIris={componentIrisToFilterBy} filters={filters} /> ) : null} diff --git a/app/components/chart-preview.tsx b/app/components/chart-preview.tsx index 350ff062b..744699d97 100644 --- a/app/components/chart-preview.tsx +++ b/app/components/chart-preview.tsx @@ -216,6 +216,7 @@ export const ChartPreviewInner = ({ dataSet={dataSetIri} dataSource={dataSource} chartConfig={state.chartConfig} + published={false} /> )} @@ -242,10 +243,12 @@ const ChartWithInteractiveFilters = React.forwardRef( dataSet, dataSource, chartConfig, + published, }: { dataSet: string; dataSource: DataSource; chartConfig: ChartConfig; + published: boolean; }, ref ) => { @@ -281,6 +284,7 @@ const ChartWithInteractiveFilters = React.forwardRef( dataSet={dataSet} dataSource={dataSource} chartConfig={chartConfig} + published={published} /> ); @@ -291,21 +295,20 @@ const Chart = ({ dataSet, dataSource, chartConfig, + published, }: { dataSet: string; dataSource: DataSource; chartConfig: ChartConfig; + published: boolean; }) => { - // Combine filters from config + interactive filters - const queryFilters = useQueryFilters({ - chartConfig, - }); - + const queryFilters = useQueryFilters({ chartConfig }); const props = { dataSet, dataSource, - chartConfig: chartConfig, - queryFilters: queryFilters, + chartConfig, + queryFilters, + published, }; return ; diff --git a/app/components/chart-published.tsx b/app/components/chart-published.tsx index 8a4685c53..1daebbdd6 100644 --- a/app/components/chart-published.tsx +++ b/app/components/chart-published.tsx @@ -6,6 +6,7 @@ import { useEffect, useMemo, useRef } from "react"; import { useStore } from "zustand"; import { ChartDataFilters } from "@/charts/shared/chart-data-filters"; +import { getChartConfigComponentIris } from "@/charts/shared/chart-helpers"; import { isUsingImputation } from "@/charts/shared/imputation"; import { InteractiveFiltersProvider, @@ -153,6 +154,7 @@ export const ChartPublishedInner = ({ sourceType: dataSource.type, sourceUrl: dataSource.url, locale, + componentIris: getChartConfigComponentIris(chartConfig), }, }); @@ -356,6 +358,7 @@ const ChartWithInteractiveFilters = React.forwardRef( dataSet={dataSet} dataSource={dataSource} chartConfig={chartConfig} + published /> ); diff --git a/app/components/common-chart.tsx b/app/components/common-chart.tsx index 2d23383c6..8c3168e34 100644 --- a/app/components/common-chart.tsx +++ b/app/components/common-chart.tsx @@ -50,21 +50,19 @@ const GenericChart = ({ dataSet, dataSource, chartConfig, + published, }: { dataSet: string; dataSource: DataSource; chartConfig: ChartConfig; + published: boolean; }) => { - // Combine filters from config + interactive filters - const queryFilters = useQueryFilters({ - chartConfig, - }); - + const queryFilters = useQueryFilters({ chartConfig }); const props = { dataSetIri: dataSet, dataSource, - chartConfig, queryFilters, + published, }; switch (chartConfig.chartType) { diff --git a/app/components/data-download.tsx b/app/components/data-download.tsx index 952f267f6..bc18f941d 100644 --- a/app/components/data-download.tsx +++ b/app/components/data-download.tsx @@ -132,11 +132,13 @@ export const DataDownloadMenu = memo( dataSetIri, dataSource, filters, + componentIris, title, }: { dataSetIri: string; dataSource: DataSource; filters?: QueryFilters; + componentIris?: string[]; title: string; }) => { return ( @@ -145,6 +147,7 @@ export const DataDownloadMenu = memo( dataSetIri={dataSetIri} dataSource={dataSource} fileName={title} + componentIris={componentIris} filters={filters} /> @@ -156,11 +159,13 @@ const DataDownloadInnerMenu = ({ dataSetIri, dataSource, fileName, + componentIris, filters, }: { dataSetIri: string; dataSource: DataSource; fileName: string; + componentIris?: string[]; filters?: QueryFilters; }) => { const [state] = useDataDownloadState(); @@ -207,6 +212,7 @@ const DataDownloadInnerMenu = ({ Chart dataset } fileName={fileName} + componentIris={componentIris} filters={filters} /> )} @@ -215,6 +221,7 @@ const DataDownloadInnerMenu = ({ dataSource={dataSource} subheader={Full dataset} fileName={fileName} + componentIris={componentIris} /> {state.error && ( @@ -233,12 +240,14 @@ const DataDownloadMenuSection = ({ dataSource, subheader, fileName, + componentIris, filters, }: { dataSetIri: string; dataSource: DataSource; subheader: ReactNode; fileName: string; + componentIris?: string[]; filters?: QueryFilters; }) => { return ( @@ -255,6 +264,7 @@ const DataDownloadMenuSection = ({ dataSource={dataSource} fileName={fileName} fileFormat={fileFormat} + componentIris={componentIris} filters={filters} /> ))} @@ -269,12 +279,14 @@ const DownloadMenuItem = ({ dataSource, fileName, fileFormat, + componentIris, filters, }: { dataSetIri: string; dataSource: DataSource; fileName: string; fileFormat: FileFormat; + componentIris?: string[]; filters?: QueryFilters; }) => { const locale = useLocale(); @@ -333,6 +345,7 @@ const DownloadMenuItem = ({ sourceType: dataSource.type, sourceUrl: dataSource.url, locale, + componentIris, } ) .toPromise(); @@ -346,7 +359,7 @@ const DownloadMenuItem = ({ sourceType: dataSource.type, sourceUrl: dataSource.url, locale, - dimensions: null, + componentIris, filters, }) .toPromise(); diff --git a/app/configurator/components/datatable.tsx b/app/configurator/components/datatable.tsx index 4add2e51b..52acff909 100644 --- a/app/configurator/components/datatable.tsx +++ b/app/configurator/components/datatable.tsx @@ -14,7 +14,10 @@ import { import { ascending, descending } from "d3"; import { useMemo, useRef, useState } from "react"; -import { useQueryFilters } from "@/charts/shared/chart-helpers"; +import { + getChartConfigComponentIris, + useQueryFilters, +} from "@/charts/shared/chart-helpers"; import { Loading } from "@/components/hint"; import { OpenMetadataPanelWrapper } from "@/components/metadata-panel"; import { ChartConfig, DataSource } from "@/configurator/config-types"; @@ -242,6 +245,7 @@ export const DataSetTable = ({ sx?: SxProps; }) => { const locale = useLocale(); + const componentIrisToFilterBy = getChartConfigComponentIris(chartConfig); const filters = useQueryFilters({ chartConfig }); const [{ data: metadataData }] = useDataCubeMetadataQuery({ variables: { @@ -257,6 +261,7 @@ export const DataSetTable = ({ sourceType: dataSource.type, sourceUrl: dataSource.url, locale, + componentIris: componentIrisToFilterBy, }, }); const [{ data: observationsData }] = useDataCubeObservationsQuery({ @@ -265,7 +270,7 @@ export const DataSetTable = ({ sourceType: dataSource.type, sourceUrl: dataSource.url, locale, - dimensions: null, + componentIris: componentIrisToFilterBy, filters, }, }); diff --git a/app/configurator/config-types.ts b/app/configurator/config-types.ts index 9705cd380..e29fed2d5 100644 --- a/app/configurator/config-types.ts +++ b/app/configurator/config-types.ts @@ -528,13 +528,13 @@ export type MapFields = t.TypeOf; export type MapConfig = t.TypeOf; export type ChartFields = + | AreaFields | ColumnFields | LineFields - | AreaFields - | ScatterPlotFields + | MapFields | PieFields - | TableFields - | MapFields; + | ScatterPlotFields + | TableFields; export type ChartSegmentField = | AreaSegmentField @@ -547,12 +547,11 @@ const ChartConfig = t.union([ AreaConfig, ColumnConfig, LineConfig, - ScatterPlotConfig, + MapConfig, PieConfig, + ScatterPlotConfig, TableConfig, - MapConfig, ]); -// t.record(t.string, t.any) export type ChartConfig = t.TypeOf; export const decodeChartConfig = ( diff --git a/app/graphql/queries/data-cubes.graphql b/app/graphql/queries/data-cubes.graphql index f13d5de19..0137551f0 100644 --- a/app/graphql/queries/data-cubes.graphql +++ b/app/graphql/queries/data-cubes.graphql @@ -190,6 +190,7 @@ query Components( $locale: String! $latest: Boolean $filters: Filters + $componentIris: [String!] ) { dataCubeByIri( iri: $iri @@ -198,10 +199,18 @@ query Components( locale: $locale latest: $latest ) { - dimensions(sourceType: $sourceType, sourceUrl: $sourceUrl) { + dimensions( + sourceType: $sourceType + sourceUrl: $sourceUrl + componentIris: $componentIris + ) { ...dimensionMetadata } - measures(sourceType: $sourceType, sourceUrl: $sourceUrl) { + measures( + sourceType: $sourceType + sourceUrl: $sourceUrl + componentIris: $componentIris + ) { ...dimensionMetadata } } @@ -214,6 +223,7 @@ query ComponentsWithHierarchies( $locale: String! $latest: Boolean $filters: Filters + $componentIris: [String!] ) { dataCubeByIri( iri: $iri @@ -222,10 +232,18 @@ query ComponentsWithHierarchies( locale: $locale latest: $latest ) { - dimensions(sourceType: $sourceType, sourceUrl: $sourceUrl) { + dimensions( + sourceType: $sourceType + sourceUrl: $sourceUrl + componentIris: $componentIris + ) { ...dimensionMetadataWithHierarchies } - measures(sourceType: $sourceType, sourceUrl: $sourceUrl) { + measures( + sourceType: $sourceType + sourceUrl: $sourceUrl + componentIris: $componentIris + ) { ...dimensionMetadataWithHierarchies } } @@ -351,7 +369,7 @@ query DataCubeObservations( $sourceType: String! $sourceUrl: String! $locale: String! - $dimensions: [String!] + $componentIris: [String!] $filters: Filters $latest: Boolean $limit: Int @@ -366,7 +384,7 @@ query DataCubeObservations( observations( sourceType: $sourceType sourceUrl: $sourceUrl - dimensions: $dimensions + componentIris: $componentIris filters: $filters limit: $limit ) { diff --git a/app/graphql/query-hooks.ts b/app/graphql/query-hooks.ts index d1c060361..99cb0e6a6 100644 --- a/app/graphql/query-hooks.ts +++ b/app/graphql/query-hooks.ts @@ -55,7 +55,7 @@ export type DataCubeObservationsArgs = { sourceType: Scalars['String']; sourceUrl: Scalars['String']; limit?: Maybe; - dimensions?: Maybe>; + componentIris?: Maybe>; filters?: Maybe; }; @@ -63,6 +63,7 @@ export type DataCubeObservationsArgs = { export type DataCubeDimensionsArgs = { sourceType: Scalars['String']; sourceUrl: Scalars['String']; + componentIris?: Maybe>; }; @@ -76,6 +77,7 @@ export type DataCubeDimensionByIriArgs = { export type DataCubeMeasuresArgs = { sourceType: Scalars['String']; sourceUrl: Scalars['String']; + componentIris?: Maybe>; }; export type DataCubeOrganization = { @@ -394,6 +396,7 @@ export type QueryDataCubeByIriArgs = { iri: Scalars['String']; latest?: Maybe; filters?: Maybe; + componentIris?: Maybe>; }; @@ -762,6 +765,7 @@ export type ComponentsQueryVariables = Exact<{ locale: Scalars['String']; latest?: Maybe; filters?: Maybe; + componentIris?: Maybe | Scalars['String']>; }>; @@ -801,6 +805,7 @@ export type ComponentsWithHierarchiesQueryVariables = Exact<{ locale: Scalars['String']; latest?: Maybe; filters?: Maybe; + componentIris?: Maybe | Scalars['String']>; }>; @@ -912,7 +917,7 @@ export type DataCubeObservationsQueryVariables = Exact<{ sourceType: Scalars['String']; sourceUrl: Scalars['String']; locale: Scalars['String']; - dimensions?: Maybe | Scalars['String']>; + componentIris?: Maybe | Scalars['String']>; filters?: Maybe; latest?: Maybe; limit?: Maybe; @@ -1200,7 +1205,7 @@ export function useDataCubeMetadataQuery(options: Omit({ query: DataCubeMetadataDocument, ...options }); }; export const ComponentsDocument = gql` - query Components($iri: String!, $sourceType: String!, $sourceUrl: String!, $locale: String!, $latest: Boolean, $filters: Filters) { + query Components($iri: String!, $sourceType: String!, $sourceUrl: String!, $locale: String!, $latest: Boolean, $filters: Filters, $componentIris: [String!]) { dataCubeByIri( iri: $iri sourceType: $sourceType @@ -1208,10 +1213,18 @@ export const ComponentsDocument = gql` locale: $locale latest: $latest ) { - dimensions(sourceType: $sourceType, sourceUrl: $sourceUrl) { + dimensions( + sourceType: $sourceType + sourceUrl: $sourceUrl + componentIris: $componentIris + ) { ...dimensionMetadata } - measures(sourceType: $sourceType, sourceUrl: $sourceUrl) { + measures( + sourceType: $sourceType + sourceUrl: $sourceUrl + componentIris: $componentIris + ) { ...dimensionMetadata } } @@ -1222,7 +1235,7 @@ export function useComponentsQuery(options: Omit({ query: ComponentsDocument, ...options }); }; export const ComponentsWithHierarchiesDocument = gql` - query ComponentsWithHierarchies($iri: String!, $sourceType: String!, $sourceUrl: String!, $locale: String!, $latest: Boolean, $filters: Filters) { + query ComponentsWithHierarchies($iri: String!, $sourceType: String!, $sourceUrl: String!, $locale: String!, $latest: Boolean, $filters: Filters, $componentIris: [String!]) { dataCubeByIri( iri: $iri sourceType: $sourceType @@ -1230,10 +1243,18 @@ export const ComponentsWithHierarchiesDocument = gql` locale: $locale latest: $latest ) { - dimensions(sourceType: $sourceType, sourceUrl: $sourceUrl) { + dimensions( + sourceType: $sourceType + sourceUrl: $sourceUrl + componentIris: $componentIris + ) { ...dimensionMetadataWithHierarchies } - measures(sourceType: $sourceType, sourceUrl: $sourceUrl) { + measures( + sourceType: $sourceType + sourceUrl: $sourceUrl + componentIris: $componentIris + ) { ...dimensionMetadataWithHierarchies } } @@ -1349,7 +1370,7 @@ export function useTemporalDimensionValuesQuery(options: Omit({ query: TemporalDimensionValuesDocument, ...options }); }; export const DataCubeObservationsDocument = gql` - query DataCubeObservations($iri: String!, $sourceType: String!, $sourceUrl: String!, $locale: String!, $dimensions: [String!], $filters: Filters, $latest: Boolean, $limit: Int) { + query DataCubeObservations($iri: String!, $sourceType: String!, $sourceUrl: String!, $locale: String!, $componentIris: [String!], $filters: Filters, $latest: Boolean, $limit: Int) { dataCubeByIri( iri: $iri sourceType: $sourceType @@ -1360,7 +1381,7 @@ export const DataCubeObservationsDocument = gql` observations( sourceType: $sourceType sourceUrl: $sourceUrl - dimensions: $dimensions + componentIris: $componentIris filters: $filters limit: $limit ) { diff --git a/app/graphql/resolver-types.ts b/app/graphql/resolver-types.ts index 5d63c4ed6..20c83809e 100644 --- a/app/graphql/resolver-types.ts +++ b/app/graphql/resolver-types.ts @@ -57,7 +57,7 @@ export type DataCubeObservationsArgs = { sourceType: Scalars['String']; sourceUrl: Scalars['String']; limit?: Maybe; - dimensions?: Maybe>; + componentIris?: Maybe>; filters?: Maybe; }; @@ -65,6 +65,7 @@ export type DataCubeObservationsArgs = { export type DataCubeDimensionsArgs = { sourceType: Scalars['String']; sourceUrl: Scalars['String']; + componentIris?: Maybe>; }; @@ -78,6 +79,7 @@ export type DataCubeDimensionByIriArgs = { export type DataCubeMeasuresArgs = { sourceType: Scalars['String']; sourceUrl: Scalars['String']; + componentIris?: Maybe>; }; export type DataCubeOrganization = { @@ -396,6 +398,7 @@ export type QueryDataCubeByIriArgs = { iri: Scalars['String']; latest?: Maybe; filters?: Maybe; + componentIris?: Maybe>; }; diff --git a/app/graphql/resolvers/rdf.ts b/app/graphql/resolvers/rdf.ts index 06ef4f7b1..e03a17c30 100644 --- a/app/graphql/resolvers/rdf.ts +++ b/app/graphql/resolvers/rdf.ts @@ -120,7 +120,6 @@ export const possibleFilters: NonNullable = filters: queryFilters, limit: 1, raw: true, - dimensions: null, cache, }); @@ -200,26 +199,30 @@ export const datasetcount: NonNullable = async ( }; export const dataCubeDimensions: NonNullable = - async ({ cube, locale }, _, { setup }, info) => { + async ({ cube, locale }, { componentIris }, { setup }, info) => { const { sparqlClient, cache } = await setup(info); const dimensions = await getCubeDimensions({ cube, locale, sparqlClient, + componentIris, cache, }); + return dimensions.filter((d) => !d.data.isMeasureDimension); }; export const dataCubeMeasures: NonNullable = - async ({ cube, locale }, _, { setup }, info) => { + async ({ cube, locale }, { componentIris }, { setup }, info) => { const { sparqlClient, cache } = await setup(info); const dimensions = await getCubeDimensions({ cube, locale, sparqlClient, + componentIris, cache, }); + return dimensions.filter((d) => d.data.isMeasureDimension); }; @@ -231,7 +234,7 @@ export const dataCubeDimensionByIri: NonNullable< cube, locale, sparqlClient, - dimensionIris: [iri], + componentIris: [iri], cache, }); const dimension = dimensions.find((d) => iri === d.data.iri); @@ -250,7 +253,7 @@ export const dataCubeObservations: NonNullable< DataCubeResolvers["observations"] > = async ( { cube, locale }, - { limit, filters, dimensions }, + { limit, filters, componentIris }, { setup }, info ) => { @@ -261,7 +264,7 @@ export const dataCubeObservations: NonNullable< sparqlClient, filters: filters ?? undefined, limit: limit ?? undefined, - dimensions, + componentIris, cache, }); diff --git a/app/graphql/schema.graphql b/app/graphql/schema.graphql index c572595c7..ea96ef124 100644 --- a/app/graphql/schema.graphql +++ b/app/graphql/schema.graphql @@ -43,16 +43,24 @@ type DataCube { sourceType: String! sourceUrl: String! limit: Int - dimensions: [String!] + componentIris: [String!] filters: Filters ): ObservationsQuery! - dimensions(sourceType: String!, sourceUrl: String!): [Dimension!]! + dimensions( + sourceType: String! + sourceUrl: String! + componentIris: [String!] + ): [Dimension!]! dimensionByIri( iri: String! sourceType: String! sourceUrl: String! ): Dimension - measures(sourceType: String!, sourceUrl: String!): [Measure!]! + measures( + sourceType: String! + sourceUrl: String! + componentIris: [String!] + ): [Measure!]! themes: [DataCubeTheme!]! } @@ -301,6 +309,7 @@ type Query { iri: String! latest: Boolean = true filters: Filters + componentIris: [String!] ): DataCube possibleFilters( iri: String! diff --git a/app/rdf/queries.ts b/app/rdf/queries.ts index d63a81e93..09b7cae97 100644 --- a/app/rdf/queries.ts +++ b/app/rdf/queries.ts @@ -129,20 +129,20 @@ export const getCubeDimensions = async ({ cube, locale, sparqlClient, - dimensionIris, + componentIris, cache, }: { cube: Cube; locale: string; sparqlClient: ParsingClient; - dimensionIris?: string[]; + componentIris?: Maybe; cache: LRUCache | undefined; }): Promise => { try { const dimensions = cube.dimensions .filter(isObservationDimension) .filter((x) => - dimensionIris ? dimensionIris.includes(x.path.value) : true + componentIris ? componentIris.includes(x.path.value) : true ); const dimensionUnits = dimensions.flatMap(getDimensionUnits); @@ -466,7 +466,7 @@ export const getCubeObservations = async ({ filters, limit, raw, - dimensions, + componentIris, cache, }: { cube: Cube; @@ -478,7 +478,7 @@ export const getCubeObservations = async ({ limit?: number; /** Returns IRIs instead of labels for NamedNodes */ raw?: boolean; - dimensions: Maybe | undefined; + componentIris?: Maybe; cache: LRUCache | undefined; }): Promise<{ query: string; @@ -495,7 +495,7 @@ export const getCubeObservations = async ({ }); const cubeDimensions = allResolvedDimensions.filter((d) => - dimensions ? dimensions.includes(d.data.iri) : true + componentIris ? componentIris.includes(d.data.iri) : true ); const serverFilters: typeof filters = {}; @@ -521,10 +521,9 @@ export const getCubeObservations = async ({ ? buildFilters({ cube, view: cubeView, filters: dbFilters, locale }) : []; - // Only choose dimensions that we really want const observationDimensions = buildDimensions({ cubeView, - dimensions, + dimensions: componentIris, cubeDimensions, cube, locale,