From 4f85eb4f8ab9833a2ef2ba8a68705217946adcb0 Mon Sep 17 00:00:00 2001 From: Patrick Browne Date: Tue, 15 Feb 2022 16:54:21 +0100 Subject: [PATCH 1/9] refactor: Make the parsing happening in variable retrievers more obvious --- app/charts/shared/chart-helpers.tsx | 56 +++++++++-------------------- 1 file changed, 16 insertions(+), 40 deletions(-) diff --git a/app/charts/shared/chart-helpers.tsx b/app/charts/shared/chart-helpers.tsx index b0b03114d..18a471419 100644 --- a/app/charts/shared/chart-helpers.tsx +++ b/app/charts/shared/chart-helpers.tsx @@ -118,47 +118,23 @@ export const usePreparedData = ({ return preparedData; }; -// retrieving variables -export const useNumericVariable = ( - key: string -): ((d: Observation) => number) => { - const getVariable = useCallback((d: Observation) => Number(d[key]), [key]); - - return getVariable; -}; - -export const useOptionalNumericVariable = ( - key: string -): ((d: Observation) => number | null) => { - const getVariable = useCallback( - (d: Observation) => (d[key] !== null ? Number(d[key]) : null), - [key] - ); - - return getVariable; -}; - -export const useStringVariable = ( - key: string -): ((d: Observation) => string) => { - const getVariable = useCallback( - (d: Observation) => (d[key] !== null ? `${d[key]}` : ""), - [key] - ); - - return getVariable; -}; +export const makeUseParsedVariable = + (parser: (d: Observation[string]) => T) => + (key: string) => { + return useCallback((d: Observation) => parser(d[key]), [key]); + }; -export const useTemporalVariable = ( - key: string -): ((d: Observation) => Date) => { - const getVariable = useCallback( - (d: Observation) => parseDate(`${d[key]}`), - [key] - ); - - return getVariable; -}; +// retrieving variables +export const useNumericVariable = makeUseParsedVariable((x) => Number(x)); +export const useOptionalNumericVariable = makeUseParsedVariable((x) => + x !== null ? Number(x) : null +); +export const useStringVariable = makeUseParsedVariable((x) => + x !== null ? `${x}` : "" +); +export const useTemporalVariable = makeUseParsedVariable((x) => + parseDate(`${x}`) +); const getSegment = (segmentKey: string | undefined) => From c6ef67a3d1324d592dc87ae25f41096216456d47 Mon Sep 17 00:00:00 2001 From: Patrick Browne Date: Tue, 15 Feb 2022 16:59:17 +0100 Subject: [PATCH 2/9] feat: First implementation of error whiskers --- app/charts/column/columns-simple.tsx | 75 ++++ app/charts/column/columns-state.tsx | 17 + app/docs/columns.docs.tsx | 555 +++++++++++++++++++++++++++ app/pages/docs.tsx | 11 +- 4 files changed, 655 insertions(+), 3 deletions(-) create mode 100644 app/docs/columns.docs.tsx diff --git a/app/charts/column/columns-simple.tsx b/app/charts/column/columns-simple.tsx index 989e692fc..4f725975a 100644 --- a/app/charts/column/columns-simple.tsx +++ b/app/charts/column/columns-simple.tsx @@ -1,8 +1,83 @@ +import { memo } from "react"; import { useTheme } from "../../themes"; import { useChartState } from "../shared/use-chart-state"; import { ColumnsState } from "./columns-state"; import { Column } from "./rendering-utils"; +export const VerticalWhisker = memo( + ({ + x, + y1, + y2, + width, + }: { + x: number; + y1: number; + y2: number; + width: number; + color?: string; + }) => { + return ( + <> + + + + + ); + } +); + +export const ErrorWhiskers = () => { + const { preparedData, bounds, getX, xScale, getY, getYError, yScale } = + useChartState() as ColumnsState; + const { margins } = bounds; + + if (!getYError) { + return null; + } + + return ( + + {preparedData.map((d, i) => { + const x0 = xScale(getX(d)) as number; + const bandwidth = xScale.bandwidth(); + const barwidth = bandwidth / 4; + const [y1, y2] = getYError(d); + return ( + + ); + })} + + ); +}; + export const Columns = () => { const { preparedData, bounds, getX, xScale, getY, yScale } = useChartState() as ColumnsState; diff --git a/app/charts/column/columns-state.tsx b/app/charts/column/columns-state.tsx index d0a5609c4..a7d7d18b2 100644 --- a/app/charts/column/columns-state.tsx +++ b/app/charts/column/columns-state.tsx @@ -59,6 +59,7 @@ export interface ColumnsState { xEntireScale: ScaleTime; xScaleInteraction: ScaleBand; getY: (d: Observation) => number | null; + getYError: null | ((d: Observation) => [number, number]); yScale: ScaleLinear; getSegment: (d: Observation) => string; segments: string[]; @@ -102,7 +103,22 @@ const useColumnsState = ({ const getX = useStringVariable(fields.x.componentIri); const getXAsDate = useTemporalVariable(fields.x.componentIri); const getY = useOptionalNumericVariable(fields.y.componentIri); + const errorIri = useMemo(() => { + const yMeasure = measures.find((m) => m.iri === fields.y.componentIri); + return yMeasure?.related?.errorIri; + }, [fields.y.componentIri, measures]); const getSegment = useSegment(fields.segment?.componentIri); + const getYError = errorIri + ? (d: Observation) => { + const y = getY(d) as number; + const error = + d[errorIri] !== null ? parseFloat(d[errorIri] as string) : null; + return (error === null ? [y, y] : [y - error, y + error]) as [ + number, + number + ]; + } + : null; const sortingType = fields.x.sorting?.sortingType; const sortingOrder = fields.x.sorting?.sortingOrder; @@ -271,6 +287,7 @@ const useColumnsState = ({ timeUnit, xScaleInteraction, getY, + getYError, yScale, getSegment, yAxisLabel, diff --git a/app/docs/columns.docs.tsx b/app/docs/columns.docs.tsx new file mode 100644 index 000000000..089e710e2 --- /dev/null +++ b/app/docs/columns.docs.tsx @@ -0,0 +1,555 @@ +import { markdown, ReactSpecimen } from "catalog"; +import * as React from "react"; +import { Columns, ErrorWhiskers } from "../charts/column/columns-simple"; +import { ColumnChart } from "../charts/column/columns-state"; +import { AxisHeightLinear } from "../charts/shared/axis-height-linear"; +import { + AxisWidthBand, + AxisWidthBandDomain, +} from "../charts/shared/axis-width-band"; +import { ChartContainer, ChartSvg } from "../charts/shared/containers"; +import { Tooltip } from "../charts/shared/interaction/tooltip"; +import { InteractiveFiltersProvider } from "../charts/shared/use-interactive-filters"; +import { DimensionMetaDataFragment } from "../graphql/query-hooks"; + +export const Docs = () => markdown` + +## Columns + +${( + + + + + + + + + + + + + + {/* {barFields.segment && } */} + + + +)} +`; +export default Docs; + +const columnFields = { + x: { + componentIri: + "http://environment.ld.admin.ch/foen/px/0703010000_103/measure/0", + }, + y: { + componentIri: + "http://environment.ld.admin.ch/foen/px/0703010000_103/measure/1", + }, + // segment: { + // componentIri: + // "http://environment.ld.admin.ch/foen/px/0703010000_103/dimension/1", + // palette: "category10", + // colorMapping: { + "http://environment.ld.admin.ch/foen/px/0703010000_103/dimension/1/0": + "#1f77b4", + "http://environment.ld.admin.ch/foen/px/0703010000_103/dimension/1/1": + "#ff7f0e", + "http://environment.ld.admin.ch/foen/px/0703010000_103/dimension/1/2": + "#2ca02c", + "http://environment.ld.admin.ch/foen/px/0703010000_103/dimension/1/3": + "#d62728", + "http://environment.ld.admin.ch/foen/px/0703010000_103/dimension/1/4": + "#9467bd", + "http://environment.ld.admin.ch/foen/px/0703010000_103/dimension/1/5": + "#8c564b", + // }, + // interactiveFilterPresets: { + // "http://environment.ld.admin.ch/foen/px/0703010000_103/dimension/1/0": true, + // "http://environment.ld.admin.ch/foen/px/0703010000_103/dimension/1/1": true, + // "http://environment.ld.admin.ch/foen/px/0703010000_103/dimension/1/2": true, + // "http://environment.ld.admin.ch/foen/px/0703010000_103/dimension/1/3": true, + // "http://environment.ld.admin.ch/foen/px/0703010000_103/dimension/1/4": true, + // "http://environment.ld.admin.ch/foen/px/0703010000_103/dimension/1/5": true, + // }, + // }, +}; +const columnMeasures = [ + { + iri: "http://environment.ld.admin.ch/foen/px/0703010000_103/measure/0", + label: "Anzahl Betriebe", + __typename: "Measure", + }, + { + iri: "http://environment.ld.admin.ch/foen/px/0703010000_103/measure/1", + label: "Anzahl Waldeigentümer", + related: { + errorIri: + "http://environment.ld.admin.ch/foen/px/0703010000_103/measure/16", + }, + __typename: "Measure", + }, + { + iri: "http://environment.ld.admin.ch/foen/px/0703010000_103/measure/2", + label: "Gesamte Waldflächen in ha", + __typename: "Measure", + }, + { + iri: "http://environment.ld.admin.ch/foen/px/0703010000_103/measure/3", + label: "Produktive Waldflächen in ha", + __typename: "Measure", + }, + { + iri: "http://environment.ld.admin.ch/foen/px/0703010000_103/measure/4", + label: "Zertifizierte Waldflächen in ha", + __typename: "Measure", + }, + { + iri: "http://environment.ld.admin.ch/foen/px/0703010000_103/measure/5", + label: "Bundeswälder in ha", + __typename: "Measure", + }, + { + iri: "http://environment.ld.admin.ch/foen/px/0703010000_103/measure/6", + label: "Staatswälder in ha", + __typename: "Measure", + }, + { + iri: "http://environment.ld.admin.ch/foen/px/0703010000_103/measure/7", + label: "Wälder der politischen Gemeinden in ha", + __typename: "Measure", + }, + { + iri: "http://environment.ld.admin.ch/foen/px/0703010000_103/measure/8", + label: "Bürgerwälder in ha", + __typename: "Measure", + }, + { + iri: "http://environment.ld.admin.ch/foen/px/0703010000_103/measure/9", + label: "Korporationswälder in ha", + __typename: "Measure", + }, + { + iri: "http://environment.ld.admin.ch/foen/px/0703010000_103/measure/10", + label: "Übrige Wälder in ha", + __typename: "Measure", + }, + { + iri: "http://environment.ld.admin.ch/foen/px/0703010000_103/measure/11", + label: "Holzproduktion Total in m3", + __typename: "Measure", + }, + { + iri: "http://environment.ld.admin.ch/foen/px/0703010000_103/measure/12", + label: "Stammholz in m3", + __typename: "Measure", + }, + { + iri: "http://environment.ld.admin.ch/foen/px/0703010000_103/measure/13", + label: "Industrieholz in m3", + __typename: "Measure", + }, + { + iri: "http://environment.ld.admin.ch/foen/px/0703010000_103/measure/14", + label: "Energieholz in m3", + __typename: "Measure", + }, + { + iri: "http://environment.ld.admin.ch/foen/px/0703010000_103/measure/15", + label: "Übrige Sortimente in m3", + __typename: "Measure", + }, +] as DimensionMetaDataFragment[]; + +const columnDimensions = [ + { + iri: "http://environment.ld.admin.ch/foen/px/0703010000_103/measure/0", + label: "Jahr", + values: [], + __typename: "Measure", + }, + { + iri: "http://environment.ld.admin.ch/foen/px/0703010000_103/dimension/0", + label: "Jahr", + values: [ + { value: "2004", label: "2004", __typename: "DimensionValue" }, + { value: "2005", label: "2005", __typename: "DimensionValue" }, + { value: "2006", label: "2006", __typename: "DimensionValue" }, + { value: "2007", label: "2007", __typename: "DimensionValue" }, + { value: "2008", label: "2008", __typename: "DimensionValue" }, + { value: "2009", label: "2009", __typename: "DimensionValue" }, + { value: "2010", label: "2010", __typename: "DimensionValue" }, + { value: "2011", label: "2011", __typename: "DimensionValue" }, + { value: "2012", label: "2012", __typename: "DimensionValue" }, + { value: "2013", label: "2013", __typename: "DimensionValue" }, + { value: "2014", label: "2014", __typename: "DimensionValue" }, + ], + __typename: "TemporalDimension", + }, + { + iri: "http://environment.ld.admin.ch/foen/px/0703010000_103/dimension/1", + label: "Forstzone", + values: [ + { + value: + "http://environment.ld.admin.ch/foen/px/0703010000_103/dimension/1/0", + label: "Schweiz", + __typename: "DimensionValue", + }, + { + value: + "http://environment.ld.admin.ch/foen/px/0703010000_103/dimension/1/1", + label: "Jura", + __typename: "DimensionValue", + }, + { + value: + "http://environment.ld.admin.ch/foen/px/0703010000_103/dimension/1/2", + label: "Mittelland", + __typename: "DimensionValue", + }, + { + value: + "http://environment.ld.admin.ch/foen/px/0703010000_103/dimension/1/3", + label: "Voralpen", + __typename: "DimensionValue", + }, + { + value: + "http://environment.ld.admin.ch/foen/px/0703010000_103/dimension/1/4", + label: "Alpen", + __typename: "DimensionValue", + }, + { + value: + "http://environment.ld.admin.ch/foen/px/0703010000_103/dimension/1/5", + label: "Alpen-Südseite", + __typename: "DimensionValue", + }, + ], + __typename: "NominalDimension", + }, + { + iri: "http://environment.ld.admin.ch/foen/px/0703010000_103/dimension/2", + label: "Kanton", + values: [ + { + value: + "http://environment.ld.admin.ch/foen/px/0703010000_103/dimension/2/0", + label: "Schweiz", + __typename: "DimensionValue", + }, + { + value: + "http://environment.ld.admin.ch/foen/px/0703010000_103/dimension/2/1", + label: "Zürich", + __typename: "DimensionValue", + }, + { + value: + "http://environment.ld.admin.ch/foen/px/0703010000_103/dimension/2/2", + label: "Bern / Berne", + __typename: "DimensionValue", + }, + { + value: + "http://environment.ld.admin.ch/foen/px/0703010000_103/dimension/2/3", + label: "Luzern", + __typename: "DimensionValue", + }, + { + value: + "http://environment.ld.admin.ch/foen/px/0703010000_103/dimension/2/4", + label: "Uri", + __typename: "DimensionValue", + }, + { + value: + "http://environment.ld.admin.ch/foen/px/0703010000_103/dimension/2/5", + label: "Schwyz", + __typename: "DimensionValue", + }, + { + value: + "http://environment.ld.admin.ch/foen/px/0703010000_103/dimension/2/6", + label: "Obwalden", + __typename: "DimensionValue", + }, + { + value: + "http://environment.ld.admin.ch/foen/px/0703010000_103/dimension/2/7", + label: "Nidwalden", + __typename: "DimensionValue", + }, + { + value: + "http://environment.ld.admin.ch/foen/px/0703010000_103/dimension/2/8", + label: "Glarus", + __typename: "DimensionValue", + }, + { + value: + "http://environment.ld.admin.ch/foen/px/0703010000_103/dimension/2/9", + label: "Zug", + __typename: "DimensionValue", + }, + { + value: + "http://environment.ld.admin.ch/foen/px/0703010000_103/dimension/2/10", + label: "Fribourg / Freiburg", + __typename: "DimensionValue", + }, + { + value: + "http://environment.ld.admin.ch/foen/px/0703010000_103/dimension/2/11", + label: "Solothurn", + __typename: "DimensionValue", + }, + { + value: + "http://environment.ld.admin.ch/foen/px/0703010000_103/dimension/2/12", + label: "Basel-Stadt", + __typename: "DimensionValue", + }, + { + value: + "http://environment.ld.admin.ch/foen/px/0703010000_103/dimension/2/13", + label: "Basel-Landschaft", + __typename: "DimensionValue", + }, + { + value: + "http://environment.ld.admin.ch/foen/px/0703010000_103/dimension/2/14", + label: "Schaffhausen", + __typename: "DimensionValue", + }, + { + value: + "http://environment.ld.admin.ch/foen/px/0703010000_103/dimension/2/15", + label: "Appenzell Ausserrhoden", + __typename: "DimensionValue", + }, + { + value: + "http://environment.ld.admin.ch/foen/px/0703010000_103/dimension/2/16", + label: "Appenzell Innerrhoden", + __typename: "DimensionValue", + }, + { + value: + "http://environment.ld.admin.ch/foen/px/0703010000_103/dimension/2/17", + label: "St. Gallen", + __typename: "DimensionValue", + }, + { + value: + "http://environment.ld.admin.ch/foen/px/0703010000_103/dimension/2/18", + label: "Graubünden / Grigioni / Grischun", + __typename: "DimensionValue", + }, + { + value: + "http://environment.ld.admin.ch/foen/px/0703010000_103/dimension/2/19", + label: "Aargau", + __typename: "DimensionValue", + }, + { + value: + "http://environment.ld.admin.ch/foen/px/0703010000_103/dimension/2/20", + label: "Thurgau", + __typename: "DimensionValue", + }, + { + value: + "http://environment.ld.admin.ch/foen/px/0703010000_103/dimension/2/21", + label: "Ticino", + __typename: "DimensionValue", + }, + { + value: + "http://environment.ld.admin.ch/foen/px/0703010000_103/dimension/2/22", + label: "Vaud", + __typename: "DimensionValue", + }, + { + value: + "http://environment.ld.admin.ch/foen/px/0703010000_103/dimension/2/23", + label: "Valais / Wallis", + __typename: "DimensionValue", + }, + { + value: + "http://environment.ld.admin.ch/foen/px/0703010000_103/dimension/2/24", + label: "Neuchâtel", + __typename: "DimensionValue", + }, + { + value: + "http://environment.ld.admin.ch/foen/px/0703010000_103/dimension/2/25", + label: "Genève", + __typename: "DimensionValue", + }, + { + value: + "http://environment.ld.admin.ch/foen/px/0703010000_103/dimension/2/26", + label: "Jura", + __typename: "DimensionValue", + }, + ], + __typename: "NominalDimension", + }, + { + iri: "http://environment.ld.admin.ch/foen/px/0703010000_103/dimension/3", + label: "Grössenklasse", + values: [ + { + value: + "http://environment.ld.admin.ch/foen/px/0703010000_103/dimension/3/0", + label: "Grössenklasse - Total", + __typename: "DimensionValue", + }, + { + value: + "http://environment.ld.admin.ch/foen/px/0703010000_103/dimension/3/1", + label: "< 50 ha", + __typename: "DimensionValue", + }, + { + value: + "http://environment.ld.admin.ch/foen/px/0703010000_103/dimension/3/2", + label: "50 - 100 ha", + __typename: "DimensionValue", + }, + { + value: + "http://environment.ld.admin.ch/foen/px/0703010000_103/dimension/3/3", + label: "101 - 200 ha", + __typename: "DimensionValue", + }, + { + value: + "http://environment.ld.admin.ch/foen/px/0703010000_103/dimension/3/4", + label: "201 - 500 ha", + __typename: "DimensionValue", + }, + { + value: + "http://environment.ld.admin.ch/foen/px/0703010000_103/dimension/3/5", + label: "501 - 1000 ha", + __typename: "DimensionValue", + }, + { + value: + "http://environment.ld.admin.ch/foen/px/0703010000_103/dimension/3/6", + label: "1001 - 5000 ha", + __typename: "DimensionValue", + }, + { + value: + "http://environment.ld.admin.ch/foen/px/0703010000_103/dimension/3/7", + label: "> 5000 ha", + __typename: "DimensionValue", + }, + ], + __typename: "NominalDimension", + }, +] as unknown as DimensionMetaDataFragment[]; +const columnObservations = [ + { + "http://environment.ld.admin.ch/foen/px/0703010000_103/dimension/1": + "Alpen", + "http://environment.ld.admin.ch/foen/px/0703010000_103/dimension/3": + "Grössenklasse - Total", + "http://environment.ld.admin.ch/foen/px/0703010000_103/dimension/2": + "Schweiz", + "http://environment.ld.admin.ch/foen/px/0703010000_103/measure/1": 1086, + "http://environment.ld.admin.ch/foen/px/0703010000_103/measure/0": 390, + "http://environment.ld.admin.ch/foen/px/0703010000_103/measure/16": 39, + "http://environment.ld.admin.ch/foen/px/0703010000_103/dimension/0": "2004", + }, + { + "http://environment.ld.admin.ch/foen/px/0703010000_103/dimension/1": + "Alpen-Südseite", + "http://environment.ld.admin.ch/foen/px/0703010000_103/dimension/3": + "Grössenklasse - Total", + "http://environment.ld.admin.ch/foen/px/0703010000_103/dimension/2": + "Schweiz", + "http://environment.ld.admin.ch/foen/px/0703010000_103/measure/1": 379, + "http://environment.ld.admin.ch/foen/px/0703010000_103/measure/0": 366, + "http://environment.ld.admin.ch/foen/px/0703010000_103/measure/16": 36, + "http://environment.ld.admin.ch/foen/px/0703010000_103/dimension/0": "2004", + }, + { + "http://environment.ld.admin.ch/foen/px/0703010000_103/dimension/1": "Jura", + "http://environment.ld.admin.ch/foen/px/0703010000_103/dimension/3": + "Grössenklasse - Total", + "http://environment.ld.admin.ch/foen/px/0703010000_103/dimension/2": + "Schweiz", + "http://environment.ld.admin.ch/foen/px/0703010000_103/measure/1": 988, + "http://environment.ld.admin.ch/foen/px/0703010000_103/measure/0": 409, + "http://environment.ld.admin.ch/foen/px/0703010000_103/measure/16": 80, + "http://environment.ld.admin.ch/foen/px/0703010000_103/dimension/0": "2004", + }, + { + "http://environment.ld.admin.ch/foen/px/0703010000_103/dimension/1": + "Mittelland", + "http://environment.ld.admin.ch/foen/px/0703010000_103/dimension/3": + "Grössenklasse - Total", + "http://environment.ld.admin.ch/foen/px/0703010000_103/dimension/2": + "Schweiz", + "http://environment.ld.admin.ch/foen/px/0703010000_103/measure/1": 1507, + "http://environment.ld.admin.ch/foen/px/0703010000_103/measure/0": 1266, + "http://environment.ld.admin.ch/foen/px/0703010000_103/measure/16": 126, + "http://environment.ld.admin.ch/foen/px/0703010000_103/dimension/0": "2004", + }, + { + "http://environment.ld.admin.ch/foen/px/0703010000_103/dimension/1": + "Schweiz", + "http://environment.ld.admin.ch/foen/px/0703010000_103/dimension/3": + "Grössenklasse - Total", + "http://environment.ld.admin.ch/foen/px/0703010000_103/dimension/2": + "Schweiz", + "http://environment.ld.admin.ch/foen/px/0703010000_103/measure/1": 4663, + "http://environment.ld.admin.ch/foen/px/0703010000_103/measure/0": 3040, + "http://environment.ld.admin.ch/foen/px/0703010000_103/measure/16": 304, + "http://environment.ld.admin.ch/foen/px/0703010000_103/dimension/0": "2004", + }, + { + "http://environment.ld.admin.ch/foen/px/0703010000_103/dimension/1": + "Voralpen", + "http://environment.ld.admin.ch/foen/px/0703010000_103/dimension/3": + "Grössenklasse - Total", + "http://environment.ld.admin.ch/foen/px/0703010000_103/dimension/2": + "Schweiz", + "http://environment.ld.admin.ch/foen/px/0703010000_103/measure/1": 703, + "http://environment.ld.admin.ch/foen/px/0703010000_103/measure/0": 609, + "http://environment.ld.admin.ch/foen/px/0703010000_103/measure/16": 60, + "http://environment.ld.admin.ch/foen/px/0703010000_103/dimension/0": "2004", + }, +]; diff --git a/app/pages/docs.tsx b/app/pages/docs.tsx index 949027250..abb0bdf10 100644 --- a/app/pages/docs.tsx +++ b/app/pages/docs.tsx @@ -90,9 +90,9 @@ const pages: ConfigPageOrGroup[] = [ content: require("../docs/annotations.docs"), }, { - path: "/charts/data-table", - title: "Data Table", - content: require("../docs/data-table.docs"), + path: "/charts/columns-chart", + title: "Columns", + content: require("../docs/columns.docs"), }, { path: "/charts/line-chart", @@ -104,6 +104,11 @@ const pages: ConfigPageOrGroup[] = [ title: "Scatterplot", content: require("../docs/scatterplot.docs"), }, + { + path: "/charts/data-table", + title: "Table", + content: require("../docs/data-table.docs"), + }, { path: "/charts/cube-update", title: "Cube update", From c8755ec0c13afe130ac8550132f799330139be21 Mon Sep 17 00:00:00 2001 From: Patrick Browne Date: Wed, 16 Feb 2022 10:42:13 +0100 Subject: [PATCH 3/9] feat: Parse related dimension from data --- app/graphql/queries/data-cubes.graphql | 4 +++ app/graphql/query-hooks.ts | 29 +++++++++++++++++----- app/graphql/resolver-types.ts | 29 ++++++++++++++++++++++ app/graphql/resolvers.ts | 1 + app/graphql/schema.graphql | 12 +++++++++ app/graphql/shared-types.ts | 2 ++ app/rdf/parse.ts | 34 ++++++++++++++++++++++++++ 7 files changed, 105 insertions(+), 6 deletions(-) diff --git a/app/graphql/queries/data-cubes.graphql b/app/graphql/queries/data-cubes.graphql index 12ab04ae9..79d38e2f4 100644 --- a/app/graphql/queries/data-cubes.graphql +++ b/app/graphql/queries/data-cubes.graphql @@ -38,6 +38,10 @@ fragment dimensionMetaData on Dimension { isKeyDimension values(filters: $filters) unit + related { + iri + type + } ... on TemporalDimension { timeUnit timeFormat diff --git a/app/graphql/query-hooks.ts b/app/graphql/query-hooks.ts index 8e11a2abb..931215cc1 100644 --- a/app/graphql/query-hooks.ts +++ b/app/graphql/query-hooks.ts @@ -102,6 +102,7 @@ export type Dimension = { scaleType?: Maybe; isKeyDimension: Scalars['Boolean']; values: Array; + related?: Maybe>; }; @@ -129,6 +130,7 @@ export type GeoCoordinatesDimension = Dimension & { isKeyDimension: Scalars['Boolean']; values: Array; geoCoordinates?: Maybe>; + related?: Maybe>; }; @@ -146,6 +148,7 @@ export type GeoShapesDimension = Dimension & { isKeyDimension: Scalars['Boolean']; values: Array; geoShapes?: Maybe; + related?: Maybe>; }; @@ -161,6 +164,7 @@ export type Measure = Dimension & { scaleType?: Maybe; isKeyDimension: Scalars['Boolean']; values: Array; + related?: Maybe>; }; @@ -176,6 +180,7 @@ export type NominalDimension = Dimension & { scaleType?: Maybe; isKeyDimension: Scalars['Boolean']; values: Array; + related?: Maybe>; }; @@ -211,6 +216,7 @@ export type OrdinalDimension = Dimension & { scaleType?: Maybe; isKeyDimension: Scalars['Boolean']; values: Array; + related?: Maybe>; }; @@ -276,6 +282,12 @@ export type QueryDatasetcountArgs = { }; +export type RelatedDimension = { + __typename: 'RelatedDimension'; + type: Scalars['String']; + iri: Scalars['String']; +}; + export type TemporalDimension = Dimension & { __typename: 'TemporalDimension'; iri: Scalars['String']; @@ -286,6 +298,7 @@ export type TemporalDimension = Dimension & { scaleType?: Maybe; isKeyDimension: Scalars['Boolean']; values: Array; + related?: Maybe>; }; @@ -314,17 +327,17 @@ export type DataCubesQueryVariables = Exact<{ export type DataCubesQuery = { __typename: 'Query', dataCubes: Array<{ __typename: 'DataCubeResult', highlightedTitle?: Maybe, highlightedDescription?: Maybe, dataCube: { __typename: 'DataCube', iri: string, title: string, description?: Maybe, publicationStatus: DataCubePublicationStatus, datePublished?: Maybe, creator?: Maybe<{ __typename: 'DataCubeOrganization', iri: string, label?: Maybe }>, themes: Array<{ __typename: 'DataCubeTheme', iri: string, label?: Maybe }> } }> }; -type DimensionMetaData_GeoCoordinatesDimension_Fragment = { __typename: 'GeoCoordinatesDimension', iri: string, label: string, isKeyDimension: boolean, values: Array, unit?: Maybe }; +type DimensionMetaData_GeoCoordinatesDimension_Fragment = { __typename: 'GeoCoordinatesDimension', iri: string, label: string, isKeyDimension: boolean, values: Array, unit?: Maybe, related?: Maybe> }; -type DimensionMetaData_GeoShapesDimension_Fragment = { __typename: 'GeoShapesDimension', iri: string, label: string, isKeyDimension: boolean, values: Array, unit?: Maybe }; +type DimensionMetaData_GeoShapesDimension_Fragment = { __typename: 'GeoShapesDimension', iri: string, label: string, isKeyDimension: boolean, values: Array, unit?: Maybe, related?: Maybe> }; -type DimensionMetaData_Measure_Fragment = { __typename: 'Measure', iri: string, label: string, isKeyDimension: boolean, values: Array, unit?: Maybe }; +type DimensionMetaData_Measure_Fragment = { __typename: 'Measure', iri: string, label: string, isKeyDimension: boolean, values: Array, unit?: Maybe, related?: Maybe> }; -type DimensionMetaData_NominalDimension_Fragment = { __typename: 'NominalDimension', iri: string, label: string, isKeyDimension: boolean, values: Array, unit?: Maybe }; +type DimensionMetaData_NominalDimension_Fragment = { __typename: 'NominalDimension', iri: string, label: string, isKeyDimension: boolean, values: Array, unit?: Maybe, related?: Maybe> }; -type DimensionMetaData_OrdinalDimension_Fragment = { __typename: 'OrdinalDimension', iri: string, label: string, isKeyDimension: boolean, values: Array, unit?: Maybe }; +type DimensionMetaData_OrdinalDimension_Fragment = { __typename: 'OrdinalDimension', iri: string, label: string, isKeyDimension: boolean, values: Array, unit?: Maybe, related?: Maybe> }; -type DimensionMetaData_TemporalDimension_Fragment = { __typename: 'TemporalDimension', timeUnit: TimeUnit, timeFormat: string, iri: string, label: string, isKeyDimension: boolean, values: Array, unit?: Maybe }; +type DimensionMetaData_TemporalDimension_Fragment = { __typename: 'TemporalDimension', timeUnit: TimeUnit, timeFormat: string, iri: string, label: string, isKeyDimension: boolean, values: Array, unit?: Maybe, related?: Maybe> }; export type DimensionMetaDataFragment = DimensionMetaData_GeoCoordinatesDimension_Fragment | DimensionMetaData_GeoShapesDimension_Fragment | DimensionMetaData_Measure_Fragment | DimensionMetaData_NominalDimension_Fragment | DimensionMetaData_OrdinalDimension_Fragment | DimensionMetaData_TemporalDimension_Fragment; @@ -550,6 +563,10 @@ export const DimensionMetaDataFragmentDoc = gql` isKeyDimension values(filters: $filters) unit + related { + iri + type + } ... on TemporalDimension { timeUnit timeFormat diff --git a/app/graphql/resolver-types.ts b/app/graphql/resolver-types.ts index e530f5025..20837c2fc 100644 --- a/app/graphql/resolver-types.ts +++ b/app/graphql/resolver-types.ts @@ -107,6 +107,7 @@ export type Dimension = { scaleType?: Maybe; isKeyDimension: Scalars['Boolean']; values: Array; + related?: Maybe>; }; @@ -134,6 +135,7 @@ export type GeoCoordinatesDimension = Dimension & { isKeyDimension: Scalars['Boolean']; values: Array; geoCoordinates?: Maybe>; + related?: Maybe>; }; @@ -151,6 +153,7 @@ export type GeoShapesDimension = Dimension & { isKeyDimension: Scalars['Boolean']; values: Array; geoShapes?: Maybe; + related?: Maybe>; }; @@ -166,6 +169,7 @@ export type Measure = Dimension & { scaleType?: Maybe; isKeyDimension: Scalars['Boolean']; values: Array; + related?: Maybe>; }; @@ -181,6 +185,7 @@ export type NominalDimension = Dimension & { scaleType?: Maybe; isKeyDimension: Scalars['Boolean']; values: Array; + related?: Maybe>; }; @@ -216,6 +221,7 @@ export type OrdinalDimension = Dimension & { scaleType?: Maybe; isKeyDimension: Scalars['Boolean']; values: Array; + related?: Maybe>; }; @@ -281,6 +287,12 @@ export type QueryDatasetcountArgs = { }; +export type RelatedDimension = { + __typename?: 'RelatedDimension'; + type: Scalars['String']; + iri: Scalars['String']; +}; + export type TemporalDimension = Dimension & { __typename?: 'TemporalDimension'; iri: Scalars['String']; @@ -291,6 +303,7 @@ export type TemporalDimension = Dimension & { scaleType?: Maybe; isKeyDimension: Scalars['Boolean']; values: Array; + related?: Maybe>; }; @@ -402,6 +415,7 @@ export type ResolversTypes = ResolversObject<{ OrdinalDimension: ResolverTypeWrapper; Query: ResolverTypeWrapper<{}>; RawObservation: ResolverTypeWrapper; + RelatedDimension: ResolverTypeWrapper; TemporalDimension: ResolverTypeWrapper; TimeUnit: TimeUnit; }>; @@ -434,6 +448,7 @@ export type ResolversParentTypes = ResolversObject<{ OrdinalDimension: ResolvedDimension; Query: {}; RawObservation: Scalars['RawObservation']; + RelatedDimension: RelatedDimension; TemporalDimension: ResolvedDimension; }>; @@ -492,6 +507,7 @@ export type DimensionResolvers, ParentType, ContextType>; isKeyDimension?: Resolver; values?: Resolver, ParentType, ContextType, RequireFields>; + related?: Resolver>, ParentType, ContextType>; }>; export interface DimensionValueScalarConfig extends GraphQLScalarTypeConfig { @@ -522,6 +538,7 @@ export type GeoCoordinatesDimensionResolvers; values?: Resolver, ParentType, ContextType, RequireFields>; geoCoordinates?: Resolver>, ParentType, ContextType>; + related?: Resolver>, ParentType, ContextType>; __isTypeOf?: IsTypeOfResolverFn; }>; @@ -537,6 +554,7 @@ export type GeoShapesDimensionResolvers; values?: Resolver, ParentType, ContextType, RequireFields>; geoShapes?: Resolver, ParentType, ContextType>; + related?: Resolver>, ParentType, ContextType>; __isTypeOf?: IsTypeOfResolverFn; }>; @@ -547,6 +565,7 @@ export type MeasureResolvers, ParentType, ContextType>; isKeyDimension?: Resolver; values?: Resolver, ParentType, ContextType, RequireFields>; + related?: Resolver>, ParentType, ContextType>; __isTypeOf?: IsTypeOfResolverFn; }>; @@ -557,6 +576,7 @@ export type NominalDimensionResolvers, ParentType, ContextType>; isKeyDimension?: Resolver; values?: Resolver, ParentType, ContextType, RequireFields>; + related?: Resolver>, ParentType, ContextType>; __isTypeOf?: IsTypeOfResolverFn; }>; @@ -586,6 +606,7 @@ export type OrdinalDimensionResolvers, ParentType, ContextType>; isKeyDimension?: Resolver; values?: Resolver, ParentType, ContextType, RequireFields>; + related?: Resolver>, ParentType, ContextType>; __isTypeOf?: IsTypeOfResolverFn; }>; @@ -603,6 +624,12 @@ export interface RawObservationScalarConfig extends GraphQLScalarTypeConfig = ResolversObject<{ + type?: Resolver; + iri?: Resolver; + __isTypeOf?: IsTypeOfResolverFn; +}>; + export type TemporalDimensionResolvers = ResolversObject<{ iri?: Resolver; label?: Resolver; @@ -612,6 +639,7 @@ export type TemporalDimensionResolvers, ParentType, ContextType>; isKeyDimension?: Resolver; values?: Resolver, ParentType, ContextType, RequireFields>; + related?: Resolver>, ParentType, ContextType>; __isTypeOf?: IsTypeOfResolverFn; }>; @@ -637,6 +665,7 @@ export type Resolvers = ResolversObject<{ OrdinalDimension?: OrdinalDimensionResolvers; Query?: QueryResolvers; RawObservation?: GraphQLScalarType; + RelatedDimension?: RelatedDimensionResolvers; TemporalDimension?: TemporalDimensionResolvers; }>; diff --git a/app/graphql/resolvers.ts b/app/graphql/resolvers.ts index 86ca20634..003f0943f 100644 --- a/app/graphql/resolvers.ts +++ b/app/graphql/resolvers.ts @@ -311,6 +311,7 @@ const getDimensionValuesLoader = ( const mkDimensionResolvers = (debugName: string) => ({ iri: ({ data: { iri } }: ResolvedDimension) => iri, label: ({ data: { name } }: ResolvedDimension) => name, + related: ({ data: { related } }: ResolvedDimension) => related, isKeyDimension: ({ data: { isKeyDimension } }: ResolvedDimension) => isKeyDimension, unit: ({ data: { unit } }: ResolvedDimension) => unit ?? null, diff --git a/app/graphql/schema.graphql b/app/graphql/schema.graphql index ca9f4b1e7..714de0113 100644 --- a/app/graphql/schema.graphql +++ b/app/graphql/schema.graphql @@ -45,6 +45,11 @@ type DataCube { themes: [DataCubeTheme!]! } +type RelatedDimension { + type: String! + iri: String! +} + interface Dimension { iri: String! label: String! @@ -52,6 +57,7 @@ interface Dimension { scaleType: String isKeyDimension: Boolean! values(filters: Filters): [DimensionValue!]! + related: [RelatedDimension!] } type GeoCoordinates { @@ -69,6 +75,7 @@ type GeoCoordinatesDimension implements Dimension { isKeyDimension: Boolean! values(filters: Filters): [DimensionValue!]! geoCoordinates: [GeoCoordinates!] + related: [RelatedDimension!] } type ObservationFilter { @@ -85,6 +92,7 @@ type GeoShapesDimension implements Dimension { isKeyDimension: Boolean! values(filters: Filters): [DimensionValue!]! geoShapes: GeoShapes + related: [RelatedDimension!] } type NominalDimension implements Dimension { @@ -94,6 +102,7 @@ type NominalDimension implements Dimension { scaleType: String isKeyDimension: Boolean! values(filters: Filters): [DimensionValue!]! + related: [RelatedDimension!] } type OrdinalDimension implements Dimension { @@ -103,6 +112,7 @@ type OrdinalDimension implements Dimension { scaleType: String isKeyDimension: Boolean! values(filters: Filters): [DimensionValue!]! + related: [RelatedDimension!] } enum TimeUnit { @@ -124,6 +134,7 @@ type TemporalDimension implements Dimension { scaleType: String isKeyDimension: Boolean! values(filters: Filters): [DimensionValue!]! + related: [RelatedDimension!] } type Measure implements Dimension { @@ -133,6 +144,7 @@ type Measure implements Dimension { scaleType: String isKeyDimension: Boolean! values(filters: Filters): [DimensionValue!]! + related: [RelatedDimension!] } type DataCubeResult { diff --git a/app/graphql/shared-types.ts b/app/graphql/shared-types.ts index fd64f29ec..5b4f07a81 100644 --- a/app/graphql/shared-types.ts +++ b/app/graphql/shared-types.ts @@ -1,6 +1,7 @@ import { Cube, CubeDimension } from "rdf-cube-view-query"; import { Literal, NamedNode } from "rdf-js"; import { Observation } from "../domain/data"; +import { RelatedDimension } from "./query-hooks"; import { DataCubeOrganization, DataCubePublicationStatus, @@ -50,6 +51,7 @@ export type ResolvedDimension = { dataType?: string; name: string; dataKind?: "Time" | "GeoCoordinates" | "GeoShape"; + related: Omit[]; timeUnit?: TimeUnit; timeFormat?: string; scaleType?: "Nominal" | "Ordinal" | "Ratio" | "Interval"; diff --git a/app/rdf/parse.ts b/app/rdf/parse.ts index 9a19428be..97ba0664d 100644 --- a/app/rdf/parse.ts +++ b/app/rdf/parse.ts @@ -10,6 +10,7 @@ import { timeWeek, timeYear, } from "d3"; +import { string } from "fp-ts"; import { Cube, CubeDimension } from "rdf-cube-view-query"; import { NamedNode, Term } from "rdf-js"; import { DataCubePublicationStatus, TimeUnit } from "../graphql/resolver-types"; @@ -144,6 +145,36 @@ export const parseDimensionDatatype = (dim: CubeDimension) => { return { dataType, hasUndefinedValues }; }; +type RelationType = "StandardError"; + +const sparqlRelationToVisualizeRelation = { + "https://cube.link/relation/StandardError": "StandardError", +} as Record; + +export const parseRelatedDimensions = (dim: CubeDimension) => { + const relatedDimensionNodes = dim.out(ns.cube`meta/dimensionRelation`); + + const res = relatedDimensionNodes + .map((n) => { + const rawType = n.out(ns.rdf("type")).value; + const type = rawType + ? sparqlRelationToVisualizeRelation[rawType] + : undefined; + const iri = n.out(ns.cube`meta/relatesTo`)?.value; + if (!iri || !type) { + return null; + } + + return { + type: type, + iri, + }; + }) + .filter(truthy); + + return res; +}; + export const parseCubeDimension = ({ dim, cube, @@ -158,6 +189,8 @@ export const parseCubeDimension = ({ const outOpts = { language: getQueryLocales(locale) }; const dataKindTerm = dim.out(ns.cube`meta/dataKind`).out(ns.rdf.type).term; + const related = parseRelatedDimensions(dim); + const timeUnitTerm = dim .out(ns.cube`meta/dataKind`) .out(ns.time.unitType).term; @@ -194,6 +227,7 @@ export const parseCubeDimension = ({ data: { iri: dim.path?.value!, + related, isLiteral, isNumerical, isKeyDimension, From b0fc4e9c70d93273e4876f1f3c2db6f9fd613e1b Mon Sep 17 00:00:00 2001 From: Patrick Browne Date: Wed, 16 Feb 2022 10:42:19 +0100 Subject: [PATCH 4/9] refactor: Remove unsued --- app/rdf/queries.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/rdf/queries.ts b/app/rdf/queries.ts index 43f715787..5f1e68d31 100644 --- a/app/rdf/queries.ts +++ b/app/rdf/queries.ts @@ -197,11 +197,9 @@ export const getCube = async ({ export const getCubeDimensions = async ({ cube, locale, - filters, }: { cube: Cube; locale: string; - filters?: Filters | null; }): Promise => { try { const dimensions = cube.dimensions.filter( From edce337606ca95c3901830de514ff748fa825e3b Mon Sep 17 00:00:00 2001 From: Patrick Browne Date: Wed, 16 Feb 2022 10:42:50 +0100 Subject: [PATCH 5/9] docs: Updated docs to reflect that related property goes from error column to normal column --- app/docs/columns.docs.tsx | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/app/docs/columns.docs.tsx b/app/docs/columns.docs.tsx index 089e710e2..877e9131c 100644 --- a/app/docs/columns.docs.tsx +++ b/app/docs/columns.docs.tsx @@ -109,10 +109,6 @@ const columnMeasures = [ { iri: "http://environment.ld.admin.ch/foen/px/0703010000_103/measure/1", label: "Anzahl Waldeigentümer", - related: { - errorIri: - "http://environment.ld.admin.ch/foen/px/0703010000_103/measure/16", - }, __typename: "Measure", }, { @@ -185,6 +181,18 @@ const columnMeasures = [ label: "Übrige Sortimente in m3", __typename: "Measure", }, + { + iri: "http://environment.ld.admin.ch/foen/px/0703010000_103/measure/16", + label: "Standard Error", + related: [ + { + __typename: "RelatedDimension", + type: "StandardError", + iri: "http://environment.ld.admin.ch/foen/px/0703010000_103/measure/1", + }, + ], + __typename: "Measure", + }, ] as DimensionMetaDataFragment[]; const columnDimensions = [ From 01e8644a5ac9aa2952564a5736b73ea62e4911d3 Mon Sep 17 00:00:00 2001 From: Patrick Browne Date: Wed, 16 Feb 2022 10:43:19 +0100 Subject: [PATCH 6/9] feat: Display error whiskers on chart column --- app/charts/column/chart-column.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/charts/column/chart-column.tsx b/app/charts/column/chart-column.tsx index 782e900ef..c7c401c88 100644 --- a/app/charts/column/chart-column.tsx +++ b/app/charts/column/chart-column.tsx @@ -29,7 +29,7 @@ import { Tooltip } from "../shared/interaction/tooltip"; import { InteractiveLegendColor, LegendColor } from "../shared/legend-color"; import { ColumnsGrouped } from "./columns-grouped"; import { GroupedColumnChart } from "./columns-grouped-state"; -import { Columns } from "./columns-simple"; +import { Columns, ErrorWhiskers } from "./columns-simple"; import { ColumnsStacked } from "./columns-stacked"; import { StackedColumnsChart } from "./columns-stacked-state"; import { ColumnChart } from "./columns-state"; @@ -171,6 +171,7 @@ export const ChartColumns = memo( + {interactiveFiltersConfig?.time.active && } From 1e347442d5e6664e5d646766f88c7f74cd465966 Mon Sep 17 00:00:00 2001 From: Patrick Browne Date: Wed, 16 Feb 2022 10:43:33 +0100 Subject: [PATCH 7/9] feat: Support percentage unit for standard error column --- app/charts/column/columns-state.tsx | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/app/charts/column/columns-state.tsx b/app/charts/column/columns-state.tsx index a7d7d18b2..c25a0944c 100644 --- a/app/charts/column/columns-state.tsx +++ b/app/charts/column/columns-state.tsx @@ -103,16 +103,23 @@ const useColumnsState = ({ const getX = useStringVariable(fields.x.componentIri); const getXAsDate = useTemporalVariable(fields.x.componentIri); const getY = useOptionalNumericVariable(fields.y.componentIri); - const errorIri = useMemo(() => { - const yMeasure = measures.find((m) => m.iri === fields.y.componentIri); - return yMeasure?.related?.errorIri; - }, [fields.y.componentIri, measures]); + const errorMeasure = useMemo(() => { + return [...measures, ...dimensions].find((m) => { + return m.related?.some( + (r) => r.type === "StandardError" && r.iri === fields.y.componentIri + ); + }); + }, [dimensions, fields.y.componentIri, measures]); const getSegment = useSegment(fields.segment?.componentIri); - const getYError = errorIri + const getYError = errorMeasure ? (d: Observation) => { const y = getY(d) as number; - const error = + const errorIri = errorMeasure.iri; + let error = d[errorIri] !== null ? parseFloat(d[errorIri] as string) : null; + if (errorMeasure.unit === "%" && error !== null) { + error = (error * y) / 100; + } return (error === null ? [y, y] : [y - error, y + error]) as [ number, number From c462d6c4fa26bad5c1b61c5cd8de961108dbd694 Mon Sep 17 00:00:00 2001 From: Patrick Browne Date: Wed, 16 Feb 2022 10:49:31 +0100 Subject: [PATCH 8/9] feat: Tweak error bar width and center the whisker central line --- app/charts/column/columns-simple.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/charts/column/columns-simple.tsx b/app/charts/column/columns-simple.tsx index 4f725975a..d2f772d0b 100644 --- a/app/charts/column/columns-simple.tsx +++ b/app/charts/column/columns-simple.tsx @@ -28,7 +28,7 @@ export const VerticalWhisker = memo( stroke="none" /> { {preparedData.map((d, i) => { const x0 = xScale(getX(d)) as number; const bandwidth = xScale.bandwidth(); - const barwidth = bandwidth / 4; + const barwidth = Math.min(bandwidth, 15); const [y1, y2] = getYError(d); return ( Date: Wed, 16 Feb 2022 11:01:26 +0100 Subject: [PATCH 9/9] refactor: Remove unused import --- app/rdf/parse.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/app/rdf/parse.ts b/app/rdf/parse.ts index 97ba0664d..f5ded8ef1 100644 --- a/app/rdf/parse.ts +++ b/app/rdf/parse.ts @@ -10,7 +10,6 @@ import { timeWeek, timeYear, } from "d3"; -import { string } from "fp-ts"; import { Cube, CubeDimension } from "rdf-cube-view-query"; import { NamedNode, Term } from "rdf-js"; import { DataCubePublicationStatus, TimeUnit } from "../graphql/resolver-types";