diff --git a/app/charts/index.ts b/app/charts/index.ts index 154dda04b..23518ef1c 100644 --- a/app/charts/index.ts +++ b/app/charts/index.ts @@ -27,9 +27,9 @@ import { TableColumn, TableFields, } from "@/configurator/config-types"; -import { DEFAULT_PALETTE } from "@/configurator/configurator-state"; import { FIELD_VALUE_NONE } from "@/configurator/constants"; import { HierarchyValue } from "@/graphql/resolver-types"; +import { getDefaultCategoricalPaletteName } from "@/palettes"; import { visitHierarchy } from "@/rdf/tree-utils"; import { CHART_CONFIG_VERSION } from "@/utils/chart-config/versioning"; @@ -160,6 +160,8 @@ export const getInitialAreaLayer = ({ // color measure: NumericalMeasure | OrdinalMeasure; }): MapAreaLayer => { + const palette = getDefaultCategoricalPaletteName(measure); + return { componentIri: component.iri, color: isNumericalMeasure(measure) @@ -173,9 +175,9 @@ export const getInitialAreaLayer = ({ : { type: "categorical", componentIri: measure.iri, - palette: "oranges", + palette, colorMapping: mapValueIrisToColor({ - palette: DEFAULT_PALETTE, + palette, dimensionValues: measure.values, }), }, @@ -271,6 +273,9 @@ export const getInitialConfig = ({ const scatterplotSegmentComponent = getCategoricalDimensions(dimensions)[0] || getGeoDimensions(dimensions)[0]; + const scatterplotPalette = getDefaultCategoricalPaletteName( + scatterplotSegmentComponent + ); return { version: CHART_CONFIG_VERSION, @@ -288,9 +293,9 @@ export const getInitialConfig = ({ ...(scatterplotSegmentComponent ? { componentIri: scatterplotSegmentComponent.iri, - palette: DEFAULT_PALETTE, + palette: scatterplotPalette, colorMapping: mapValueIrisToColor({ - palette: DEFAULT_PALETTE, + palette: scatterplotPalette, dimensionValues: scatterplotSegmentComponent.values, }), } @@ -301,6 +306,7 @@ export const getInitialConfig = ({ const pieSegmentComponent = getCategoricalDimensions(dimensions)[0] || getGeoDimensions(dimensions)[0]; + const piePalette = getDefaultCategoricalPaletteName(pieSegmentComponent); return { version: CHART_CONFIG_VERSION, @@ -311,10 +317,10 @@ export const getInitialConfig = ({ y: { componentIri: numericalMeasures[0].iri }, segment: { componentIri: pieSegmentComponent.iri, - palette: DEFAULT_PALETTE, + palette: piePalette, sorting: { sortingType: "byMeasure", sortingOrder: "asc" }, colorMapping: mapValueIrisToColor({ - palette: DEFAULT_PALETTE, + palette: piePalette, dimensionValues: pieSegmentComponent.values, }), }, @@ -602,7 +608,13 @@ const chartConfigsAdjusters: ChartConfigsAdjusters = { }); }, }, - segment: ({ oldValue, oldChartConfig, newChartConfig, dimensions }) => { + segment: ({ + oldValue, + oldChartConfig, + newChartConfig, + dimensions, + measures, + }) => { let newSegment: ColumnSegmentField | undefined; // When switching from a table chart, a whole fields object is passed as oldValue. @@ -610,6 +622,7 @@ const chartConfigsAdjusters: ChartConfigsAdjusters = { const tableSegment = convertTableFieldsToSegmentField({ fields: oldValue as TableFields, dimensions, + measures, }); if (tableSegment) { @@ -673,13 +686,20 @@ const chartConfigsAdjusters: ChartConfigsAdjusters = { }); }, }, - segment: ({ oldValue, oldChartConfig, newChartConfig, dimensions }) => { + segment: ({ + oldValue, + oldChartConfig, + newChartConfig, + dimensions, + measures, + }) => { let newSegment: LineSegmentField | undefined; if (oldChartConfig.chartType === "table") { const tableSegment = convertTableFieldsToSegmentField({ fields: oldValue as TableFields, dimensions, + measures, }); if (tableSegment) { @@ -738,13 +758,20 @@ const chartConfigsAdjusters: ChartConfigsAdjusters = { }); }, }, - segment: ({ oldValue, oldChartConfig, newChartConfig, dimensions }) => { + segment: ({ + oldValue, + oldChartConfig, + newChartConfig, + dimensions, + measures, + }) => { let newSegment: AreaSegmentField | undefined; if (oldChartConfig.chartType === "table") { const tableSegment = convertTableFieldsToSegmentField({ fields: oldValue as TableFields, dimensions, + measures, }); if (tableSegment) { @@ -800,13 +827,20 @@ const chartConfigsAdjusters: ChartConfigsAdjusters = { return newChartConfig; }, }, - segment: ({ oldValue, oldChartConfig, newChartConfig, dimensions }) => { + segment: ({ + oldValue, + oldChartConfig, + newChartConfig, + dimensions, + measures, + }) => { let newSegment: ScatterPlotSegmentField | undefined; if (oldChartConfig.chartType === "table") { const tableSegment = convertTableFieldsToSegmentField({ fields: oldValue as TableFields, dimensions, + measures, }); if (tableSegment) { @@ -844,13 +878,20 @@ const chartConfigsAdjusters: ChartConfigsAdjusters = { }); }, }, - segment: ({ oldValue, oldChartConfig, newChartConfig, dimensions }) => { + segment: ({ + oldValue, + oldChartConfig, + newChartConfig, + dimensions, + measures, + }) => { let newSegment: PieSegmentField | undefined; if (oldChartConfig.chartType === "table") { const tableSegment = convertTableFieldsToSegmentField({ fields: oldValue as TableFields, dimensions, + measures, }); if (tableSegment) { @@ -1136,9 +1177,11 @@ export const getFieldComponentIri = (fields: GenericFields, field: string) => { const convertTableFieldsToSegmentField = ({ fields, dimensions, + measures, }: { fields: TableFields; dimensions: DataCubeMetadata["dimensions"]; + measures: DataCubeMetadata["measures"]; }): GenericSegmentField | undefined => { const groupedColumns = group(Object.values(fields), (d) => d.isGroup) .get(true) @@ -1153,17 +1196,17 @@ const convertTableFieldsToSegmentField = ({ if (component) { const { componentIri } = component; + const actualComponent = [...dimensions, ...measures].find( + (d) => d.iri === componentIri + ) as DimensionMetadataFragment; + const palette = getDefaultCategoricalPaletteName(actualComponent); return { componentIri, - palette: DEFAULT_PALETTE, + palette, colorMapping: mapValueIrisToColor({ - palette: DEFAULT_PALETTE, - dimensionValues: ( - dimensions.find( - (d) => d.iri === componentIri - ) as DimensionMetadataFragment - )?.values, + palette, + dimensionValues: actualComponent.values, }), }; } diff --git a/app/charts/shared/colors.tsx b/app/charts/shared/colors.tsx index 3018df0d8..c040ef2b5 100644 --- a/app/charts/shared/colors.tsx +++ b/app/charts/shared/colors.tsx @@ -1,5 +1,8 @@ import { color, RGBColor } from "d3"; +import { DimensionValue } from "@/domain/data"; +import { DimensionMetadataFragment } from "@/graphql/query-hooks"; + export const colorToRgbArray = (_color: string, opacity?: number): number[] => { const { r, g, b } = color(_color) as RGBColor; @@ -18,3 +21,11 @@ export const rgbArrayToHex = (rgbArray: number[]): string => { ); } }; + +export const hasDimensionColors = ( + d?: DimensionMetadataFragment +): d is DimensionMetadataFragment => { + return !!(d?.values as DimensionValue[] | undefined)?.some( + (d) => d.color !== undefined + ); +}; diff --git a/app/charts/table/table-state.tsx b/app/charts/table/table-state.tsx index ada16d469..8dca0b29b 100644 --- a/app/charts/table/table-state.tsx +++ b/app/charts/table/table-state.tsx @@ -34,7 +34,6 @@ import { } from "@/configurator/components/ui-helpers"; import { Observation } from "@/domain/data"; import { useFormatNumber, useDimensionFormatters } from "@/formatters"; -import { DimensionMetadataFragment } from "@/graphql/query-hooks"; import { getColorInterpolator } from "@/palettes"; import { useTheme } from "@/themes"; import { estimateTextWidth } from "@/utils/estimate-text-width"; @@ -281,9 +280,7 @@ const useTableState = ({ return common as TextColumnMeta; } else if (columnStyleType === "category") { const { colorMapping } = columnStyle as ColumnStyleCategory; - const dimension = dimensions.find( - (d) => d.iri === iri - ) as DimensionMetadataFragment; + const dimension = allColumnsByIri[iri]; // Color scale (always from colorMappings) const colorScale = scaleOrdinal(); diff --git a/app/configurator/components/chart-controls/color-palette.tsx b/app/configurator/components/chart-controls/color-palette.tsx index bd16cc2b9..1e0a62600 100644 --- a/app/configurator/components/chart-controls/color-palette.tsx +++ b/app/configurator/components/chart-controls/color-palette.tsx @@ -12,19 +12,21 @@ import { makeStyles } from "@mui/styles"; import get from "lodash/get"; import { useCallback } from "react"; +import { hasDimensionColors } from "@/charts/shared/colors"; import Flex from "@/components/flex"; import { Label } from "@/components/form"; import { ConfiguratorStateConfiguringChart, useConfiguratorState, - DEFAULT_PALETTE, } from "@/configurator"; import { mapValueIrisToColor } from "@/configurator/components/ui-helpers"; -import { isNumericalMeasure } from "@/domain/data"; +import { DimensionValue, isNumericalMeasure } from "@/domain/data"; import { DimensionMetadataFragment } from "@/graphql/query-hooks"; import { categoricalPalettes, + DEFAULT_CATEGORICAL_PALETTE_NAME, divergingSteppedPalettes, + getDefaultCategoricalPalette, getPalette, } from "@/palettes"; import useEvent from "@/utils/use-event"; @@ -58,9 +60,20 @@ export const ColorPalette = ({ }: Props) => { const [state, dispatch] = useConfiguratorState(); const classes = useStyles(); + const hasColors = hasDimensionColors(component); + const defaultPalette = + hasColors && component + ? getDefaultCategoricalPalette( + (component.values as DimensionValue[]) + .map((d) => d.color) + .filter(Boolean) as string[] + ) + : null; const palettes = isNumericalMeasure(component) ? divergingSteppedPalettes + : defaultPalette + ? [defaultPalette, ...categoricalPalettes] : categoricalPalettes; const currentPaletteName = get( @@ -78,6 +91,7 @@ export const ColorPalette = ({ if (!component || !palette) { return; } + dispatch({ type: "CHART_PALETTE_CHANGED", value: { @@ -200,7 +214,7 @@ const ColorPaletteReset = ({ `chartConfig.fields["${field}"].${ colorConfigPath ? `${colorConfigPath}.` : "" }palette`, - DEFAULT_PALETTE + DEFAULT_CATEGORICAL_PALETTE_NAME ) as string; const colorMapping = get( @@ -233,9 +247,9 @@ const ColorPaletteReset = ({ const nbMatchedColors = colorMappingColors.length; const matchedColorsInPalette = currentPalette.slice(0, nbMatchedColors); - const same = matchedColorsInPalette.every( - (pc, i) => pc === colorMappingColors[i] - ); + const same = + matchedColorsInPalette.every((d, i) => d === colorMappingColors[i]) || + palette === "dimension"; return (