Skip to content

Commit

Permalink
Merge pull request #294 from visualize-admin/feat/new-design-for-colo…
Browse files Browse the repository at this point in the history
…r-picking-maps

feat: New design for color scale palette picking for maps
  • Loading branch information
bprusinowski authored Jan 27, 2022
2 parents 8b419fa + c60d46e commit 596fa48
Show file tree
Hide file tree
Showing 18 changed files with 829 additions and 428 deletions.
3 changes: 2 additions & 1 deletion app/charts/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -278,9 +278,10 @@ export const getInitialConfig = ({
componentIri: geoShapes[0]?.iri || "",
measureIri: measures[0].iri,
hierarchyLevel: 1,
colorScaleType: "continuous",
colorScaleInterpolationType: "linear",
palette: "oranges",
nbClass: 5,
paletteType: "continuous",
},
symbolLayer: {
show: geoShapes.length === 0,
Expand Down
52 changes: 14 additions & 38 deletions app/charts/map/map-legend.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import {
axisBottom,
interpolateOranges,
NumberValue,
range,
ScaleLinear,
Expand All @@ -14,6 +13,7 @@ import {
import * as React from "react";
import { useEffect, useMemo, useRef } from "react";
import { Box, Flex, Text } from "theme-ui";
import { ColorRamp } from "../../configurator/components/chart-controls/color-ramp";
import {
getColorInterpolator,
useFormatInteger,
Expand Down Expand Up @@ -92,10 +92,18 @@ export const MapLegend = () => {
{areaLayer.measureLabel}
</Text>
)}
{areaLayer.paletteType === "continuous" && <ContinuousColorLegend />}
{areaLayer.paletteType === "discrete" && <QuantizeColorLegend />}
{areaLayer.paletteType === "quantile" && <QuantileColorLegend />}
{areaLayer.paletteType === "jenks" && <JenksColorLegend />}
{areaLayer.colorScaleInterpolationType === "linear" && (
<ContinuousColorLegend />
)}
{areaLayer.colorScaleInterpolationType === "quantize" && (
<QuantizeColorLegend />
)}
{areaLayer.colorScaleInterpolationType === "quantile" && (
<QuantileColorLegend />
)}
{areaLayer.colorScaleInterpolationType === "jenks" && (
<JenksColorLegend />
)}
</Box>
)}

Expand Down Expand Up @@ -509,7 +517,7 @@ const ContinuousColorLegend = () => {
width={width - MARGIN.left - MARGIN.right}
height={COLOR_RAMP_HEIGHT}
colorInterpolator={getColorInterpolator(palette)}
nbClass={width}
nbClass={width - MARGIN.left - MARGIN.right}
/>
</foreignObject>
<g
Expand Down Expand Up @@ -558,35 +566,3 @@ const DataPointIndicator = ({
</>
);
};

const ColorRamp = ({
colorInterpolator = interpolateOranges,
nbClass,
width,
height,
}: {
colorInterpolator: (t: number) => string;
nbClass: number;
width: number;
height: number;
}) => {
const canvasRef = useRef<HTMLCanvasElement>(null);

useEffect(() => {
const canvas = canvasRef.current;
const context = canvas && canvas.getContext("2d");

if (canvas && context) {
context.clearRect(0, 0, width, height);
canvas.style.imageRendering = "-moz-crisp-edges";
canvas.style.imageRendering = "pixelated";

for (let i = 0; i < nbClass; ++i) {
context.fillStyle = colorInterpolator(i / (nbClass - 1));
context.fillRect(i, 0, 1, height);
}
}
});

return <canvas ref={canvasRef} width={width} height={height} />;
};
56 changes: 32 additions & 24 deletions app/charts/map/map-state.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,10 @@ import {
} from "../../configurator/components/ui-helpers";
import {
BaseLayer,
ColorScaleInterpolationType,
DivergingPaletteType,
MapFields,
PaletteType,
SequentialPaletteType,
} from "../../configurator/config-types";
import {
GeoData,
Expand Down Expand Up @@ -61,8 +63,8 @@ export interface MapState {
| ScaleQuantile<string>
| ScaleLinear<string, string>
| ScaleThreshold<number, string>;
paletteType: PaletteType;
palette: string;
colorScaleInterpolationType: ColorScaleInterpolationType;
palette: DivergingPaletteType | SequentialPaletteType;
nbClass: number;
dataDomain: [number, number];
};
Expand All @@ -80,36 +82,38 @@ export interface MapState {
}

const getColorScale = ({
paletteType,
scaleInterpolationType,
palette,
getValue,
data,
dataDomain,
nbClass,
}: {
paletteType: PaletteType;
palette: string;
scaleInterpolationType: ColorScaleInterpolationType;
palette: DivergingPaletteType | SequentialPaletteType;
getValue: (x: Observation) => number | null;
data: Observation[];
dataDomain: [number, number];
nbClass: number;
}) => {
const paletteDomain = getSingleHueSequentialPalette({
palette,
nbClass: 9,
});
const interpolator = getColorInterpolator(palette);
const getDiscreteRange = () => {
return Array.from({ length: nbClass }, (_, i) =>
interpolator(i / (nbClass - 1))
);
};

switch (paletteType) {
case "continuous":
return scaleSequential(getColorInterpolator(palette)).domain(dataDomain);
case "discrete":
switch (scaleInterpolationType) {
case "linear":
return scaleSequential(interpolator).domain(dataDomain);
case "quantize":
return scaleQuantize<string>()
.domain(dataDomain)
.range(getSingleHueSequentialPalette({ palette, nbClass }));
.range(getDiscreteRange());
case "quantile":
return scaleQuantile<string>()
.domain(data.map((d) => getValue(d)))
.range(getSingleHueSequentialPalette({ palette, nbClass }));
.range(getDiscreteRange());
case "jenks":
const ckMeansThresholds = ckmeans(
data.map((d) => getValue(d) ?? NaN),
Expand All @@ -118,8 +122,13 @@ const getColorScale = ({

return scaleThreshold<number, string>()
.domain(ckMeansThresholds)
.range(getSingleHueSequentialPalette({ palette, nbClass }));
.range(getDiscreteRange());
default:
const paletteDomain = getSingleHueSequentialPalette({
palette,
nbClass: 9,
});

return scaleLinear<string>()
.domain(dataDomain)
.range([paletteDomain[0], paletteDomain[paletteDomain.length - 1]]);
Expand All @@ -140,7 +149,6 @@ const useMapState = ({
}): MapState => {
const width = useWidth();
const { areaLayer, symbolLayer } = fields;
const { palette, nbClass, paletteType } = areaLayer;

const getAreaLabel = useStringVariable(areaLayer.componentIri);
const getSymbolLabel = useStringVariable(symbolLayer.componentIri);
Expand Down Expand Up @@ -227,12 +235,12 @@ const useMapState = ({
]) as [number, number];

const areaColorScale = getColorScale({
paletteType,
palette,
scaleInterpolationType: areaLayer.colorScaleInterpolationType,
palette: areaLayer.palette,
getValue: getAreaValue,
data: areaData,
dataDomain: areaDataDomain,
nbClass,
nbClass: areaLayer.nbClass,
});

const getAreaColor = (v: number | null) => {
Expand Down Expand Up @@ -279,9 +287,9 @@ const useMapState = ({
getValue: getAreaValue,
getColor: getAreaColor,
colorScale: areaColorScale,
paletteType,
palette,
nbClass: nbClass,
colorScaleInterpolationType: areaLayer.colorScaleInterpolationType,
palette: areaLayer.palette,
nbClass: areaLayer.nbClass,
dataDomain: areaDataDomain,
},
symbolLayer: {
Expand Down
36 changes: 8 additions & 28 deletions app/configurator/components/chart-controls/color-palette.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import { Trans } from "@lingui/macro";
import { Box, Button, Flex, Text } from "theme-ui";
import { useSelect } from "downshift";
import get from "lodash/get";
import { useCallback } from "react";
import { Box, Button, Flex, Text } from "theme-ui";
import { ConfiguratorStateConfiguringChart, useConfiguratorState } from "../..";
import { Label } from "../../../components/form";
import { DimensionMetaDataFragment } from "../../../graphql/query-hooks";
import { Icon } from "../../../icons";
import {
categoricalPalettes,
divergingSteppedPalettes,
getPalette,
mapColorsToComponentValuesIris,
sequentialPalettes,
} from "../ui-helpers";
import { DimensionMetaDataFragment } from "../../../graphql/query-hooks";
import { Icon } from "../../../icons";

type Props = {
field: string;
Expand All @@ -31,7 +31,7 @@ export const ColorPalette = ({

const palettes =
component?.__typename === "Measure"
? sequentialPalettes
? divergingSteppedPalettes
: categoricalPalettes;

const currentPaletteName = get(
Expand Down Expand Up @@ -73,34 +73,14 @@ export const ColorPalette = ({
});

return (
<Box mt={2} sx={{ pointerEvents: disabled ? "none" : "unset" }}>
<Box mt={2} sx={{ pointerEvents: disabled ? "none" : "auto" }}>
<Label disabled={disabled} smaller {...getLabelProps()}>
<Trans id="controls.color.palette">Color Palette</Trans>
</Label>
<Button
variant="selectColorPicker"
{...getToggleButtonProps()}
sx={{
color: "monochrome700",
display: "flex",
justifyContent: "space-between",
alignItems: "center",
bg: "monochrome100",
p: 1,
height: "40px",
borderWidth: "1px",
borderStyle: "solid",
borderColor: "monochrome500",
":hover": {
bg: "monochrome100",
},
":active": {
bg: "monochrome100",
},
":disabled": {
cursor: "initial",
bg: "muted",
},
}}
sx={{ cursor: "pointer" }}
>
{state.state === "CONFIGURING_CHART" && (
<Flex>
Expand Down
Loading

0 comments on commit 596fa48

Please sign in to comment.