Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Categorical color mapping #338

Merged
merged 35 commits into from
May 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
a859524
introduces new user color bar prop "categorical"
forman May 10, 2024
552adfb
Merge branch 'refs/heads/main' into forman-categorical_color_bars
forman May 10, 2024
f60900b
changed color bar models to support categories incl. labels
forman May 10, 2024
1eb7345
fixed tests
forman May 10, 2024
66fd33c
fix: only if categorical
forman May 10, 2024
34e9eb2
remove unneeded components
forman May 10, 2024
cd10ad7
avoid closing color bar selector if making color bar categorical
forman May 10, 2024
7482e29
works
forman May 10, 2024
219be74
now also rendering categorical color bars
forman May 10, 2024
17f2838
Merge branch 'refs/heads/main' into forman-categorical_color_bars
forman May 11, 2024
8153220
better color item border colors
forman May 12, 2024
32090f0
introduced property Variable.colorBarNorm and set tile query param "n…
forman May 12, 2024
9e46a99
UserColorBar.categorical --> UserColorBar.discrete
forman May 12, 2024
ba1a3c8
Can now switch color bar norm
forman May 12, 2024
4038a04
Update
forman May 13, 2024
414650a
introduced ColorMapType
forman May 13, 2024
661da49
Disable normalisation "cat" if we do not have categories
forman May 14, 2024
1ee72f1
Normalisation "cat" now works
forman May 14, 2024
e2cc2a7
Adjust min/max range for log normalisation
forman May 14, 2024
e376810
Fixed test
forman May 14, 2024
f071bdf
renamed user color bar type "index" into "key"
forman May 14, 2024
883c036
Merge branch 'refs/heads/main' into forman-categorical_color_bars
forman May 14, 2024
432c92c
labels now recognise log-scaling
forman May 15, 2024
fc2a5c1
Merge branch 'refs/heads/main' into forman-categorical_color_bars
forman May 15, 2024
4abd725
Merge branch 'refs/heads/main' into forman-categorical_color_bars
forman May 15, 2024
509229e
Merge branch 'refs/heads/main' into forman-categorical_color_bars
forman May 15, 2024
f107c40
Merge branch 'refs/heads/main' into forman-categorical_color_bars
forman May 15, 2024
2e7b93b
log-scaled range slider
forman May 15, 2024
4cf8f0b
Merge branch 'refs/heads/main' into forman-categorical_color_bars
forman May 15, 2024
0585898
Merge branch 'refs/heads/forman-categorical_color_bars' into forman-l…
forman May 15, 2024
aa5160c
Seems to work now, but value formatting is not optimal
forman May 15, 2024
94aff87
It works now
forman May 15, 2024
f729a66
refactorings
forman May 16, 2024
9eaa3bf
Changing range in text boxes now updates slider correctly
forman May 16, 2024
5b21335
increased dev version
forman May 16, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@
definition. Existing custom color bars can be edited and deleted.
This feature requires xcube server >= 1.6. (#334)

* Users can now select the data normalisation function to be applied before
the actual color mapping takes place. Three functions are available:
- `Lin`: linear mapping of data values between `min` and `max` to colors.
- `Log`: logarithmic mapping of data values between `vmin` and `vmax` to colors.
- `Cat`: direct mapping of categorical data values into colors.

* Users can now zoom into arbitrary regions of a time-series chart
by pressing the `CTRL` key of the keyboard. (#285)

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "xcube-viewer",
"version": "1.2.0-dev.0",
"version": "1.2.0-dev.1",
"private": true,
"type": "module",
"scripts": {
Expand Down
28 changes: 23 additions & 5 deletions src/actions/dataActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ import {
import { VolumeRenderMode } from "@/states/controlState";
import { MessageLogAction, postMessage } from "./messageLogActions";
import { renameUserPlaceInLayer } from "./mapActions";
import { ColorBarNorm } from "@/model/variable";

////////////////////////////////////////////////////////////////////////////////

Expand Down Expand Up @@ -682,14 +683,16 @@ export interface UpdateVariableColorBar {
type: typeof UPDATE_VARIABLE_COLOR_BAR;
datasetId: string;
variableName: string;
colorBarMinMax: [number, number];
colorBarName: string;
colorBarMinMax: [number, number];
colorBarNorm: ColorBarNorm;
opacity: number;
}

export function updateVariableColorBar(
colorBarMinMax: [number, number],
colorBarName: string,
colorBarMinMax: [number, number],
colorBarNorm: ColorBarNorm,
opacity: number,
) {
return (
Expand All @@ -699,12 +702,25 @@ export function updateVariableColorBar(
const selectedDatasetId = getState().controlState.selectedDatasetId;
const selectedVariableName = getState().controlState.selectedVariableName;
if (selectedDatasetId && selectedVariableName) {
if (colorBarNorm === "log") {
// Adjust range in case of log norm: Make sure xcube server can use
// matplotlib.colors.LogNorm(vmin, vmax) without errors
let [vMin, vMax] = colorBarMinMax;
if (vMin <= 0) {
vMin = 1e-3;
}
if (vMax <= vMin) {
vMax = 1;
}
colorBarMinMax = [vMin, vMax];
}
dispatch(
_updateVariableColorBar(
selectedDatasetId,
selectedVariableName,
colorBarMinMax,
colorBarName,
colorBarMinMax,
colorBarNorm,
opacity,
),
);
Expand All @@ -715,16 +731,18 @@ export function updateVariableColorBar(
export function _updateVariableColorBar(
datasetId: string,
variableName: string,
colorBarMinMax: [number, number],
colorBarName: string,
colorBarMinMax: [number, number],
colorBarNorm: ColorBarNorm,
opacity: number,
): UpdateVariableColorBar {
return {
type: UPDATE_VARIABLE_COLOR_BAR,
datasetId,
variableName,
colorBarMinMax,
colorBarName,
colorBarMinMax,
colorBarNorm,
opacity,
};
}
Expand Down
4 changes: 2 additions & 2 deletions src/components/ColorBarLegend/ColorBarCanvas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ import {
interface ColorBarCanvasProps {
colorBar: ColorBar;
opacity: number;
width: number | string | undefined;
height: number | string | undefined;
width?: number | string | undefined;
height?: number | string | undefined;
onClick: (event: MouseEvent<HTMLCanvasElement>) => void;
}

Expand Down
7 changes: 5 additions & 2 deletions src/components/ColorBarLegend/ColorBarColorEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import { UserColorBar } from "@/model/userColorBar";
import ColorBarStyleEditor from "./ColorBarStyleEditor";
import ColorBarSelect from "./ColorBarSelect";
import { COLOR_BAR_ITEM_GAP, COLOR_BAR_BOX_MARGIN } from "./constants";
import { ColorBarNorm } from "@/model/variable";

const useStyles = makeStyles((theme: Theme) => ({
colorBarBox: {
Expand All @@ -42,13 +43,15 @@ const useStyles = makeStyles((theme: Theme) => ({
}));

interface ColorBarColorEditorProps {
variableColorBarMinMax: [number, number];
variableColorBarName: string;
variableColorBarMinMax: [number, number];
variableColorBarNorm: ColorBarNorm;
variableColorBar: ColorBar;
variableOpacity: number;
updateVariableColorBar: (
colorBarMinMax: [number, number],
colorBarName: string,
colorBarMinMax: [number, number],
colorBarNorm: ColorBarNorm,
opacity: number,
) => void;
colorBars: ColorBars;
Expand Down
3 changes: 2 additions & 1 deletion src/components/ColorBarLegend/ColorBarGroupHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ import { COLOR_BAR_ITEM_GAP } from "./constants";
const useStyles = makeStyles((theme: Theme) => ({
colorBarGroupTitle: {
marginTop: theme.spacing(2 * COLOR_BAR_ITEM_GAP),
color: theme.palette.grey[400],
fontSize: "small",
color: theme.palette.text.secondary,
},
}));

Expand Down
2 changes: 1 addition & 1 deletion src/components/ColorBarLegend/ColorBarItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ const colorBarItemStyle = (theme: Theme) => ({
const useItemStyles = makeStyles((theme: Theme) => ({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

makeStyles is also imported from @mui/styles which is now legacy, so may be we can get rid of makeStlyes and adapt styled or sx

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, of course. I will adapt all the components. I have not yet started this.

colorBarItem: {
...colorBarItemStyle(theme),
borderColor: theme.palette.mode === "dark" ? "white" : "black",
borderColor: theme.palette.mode === "dark" ? "lightgray" : "darkgray",
},
colorBarItemSelected: {
...colorBarItemStyle(theme),
Expand Down
12 changes: 9 additions & 3 deletions src/components/ColorBarLegend/ColorBarLabels.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@
* SOFTWARE.
*/

import React from "react";
import React, { useMemo } from "react";
import makeStyles from "@mui/styles/makeStyles";

import { getLabelsFromRange } from "@/util/label";
import { getLabelsForRange } from "@/util/label";

const useStyles = makeStyles(() => ({
label: {
Expand All @@ -42,19 +42,25 @@ interface ColorBarLabelsProps {
minValue: number;
maxValue: number;
numTicks: number;
logScaled?: boolean;
onClick: (event: React.MouseEvent<HTMLDivElement>) => void;
}

export default function ColorBarLabels({
minValue,
maxValue,
numTicks,
logScaled,
onClick,
}: ColorBarLabelsProps) {
const classes = useStyles();
const labels = useMemo(
() => getLabelsForRange(minValue, maxValue, numTicks, logScaled),
[minValue, maxValue, numTicks, logScaled],
);
return (
<div className={classes.label} onClick={onClick}>
{getLabelsFromRange(minValue, maxValue, numTicks).map((label, i) => (
{labels.map((label, i) => (
<span key={i}>{label}</span>
))}
</div>
Expand Down
151 changes: 54 additions & 97 deletions src/components/ColorBarLegend/ColorBarLegend.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,21 @@
* SOFTWARE.
*/

import React, { useState } from "react";
import makeStyles from "@mui/styles/makeStyles";
import { Theme } from "@mui/material";
import { MouseEvent, useRef, useState } from "react";
import Popover from "@mui/material/Popover";

import ColorBarLegendCategorical from "./ColorBarLegendCategorical";
import ColorBarLegendContinuous from "./ColorBarLegendContinuous";
import ColorBarColorEditor from "./ColorBarColorEditor";
import { ColorBar, ColorBars } from "@/model/colorBar";
import { UserColorBar } from "@/model/userColorBar";
import ColorBarCanvas from "./ColorBarCanvas";
import ColorBarColorEditor from "./ColorBarColorEditor";
import ColorBarRangeEditor from "./ColorBarRangeEditor";
import ColorBarLabels from "./ColorBarLabels";
import makeStyles from "@mui/styles/makeStyles";
import { Theme } from "@mui/material";
import { ColorBarNorm } from "@/model/variable";

const useStyles = makeStyles((theme: Theme) => ({
title: {
fontSize: "x-small",
fontSize: "small",
fontWeight: "bold",
width: "100%",
display: "flex",
Expand All @@ -56,13 +56,15 @@ const useStyles = makeStyles((theme: Theme) => ({
interface ColorBarLegendProps {
variableName: string | null;
variableUnits: string;
variableColorBarMinMax: [number, number];
variableColorBarName: string;
variableColorBarMinMax: [number, number];
variableColorBarNorm: ColorBarNorm;
variableColorBar: ColorBar;
variableOpacity: number;
updateVariableColorBar: (
colorBarMinMax: [number, number],
colorBarName: string,
colorBarMinMax: [number, number],
colorBarNorm: ColorBarNorm,
opacity: number,
) => void;
colorBars: ColorBars;
Expand All @@ -71,115 +73,70 @@ interface ColorBarLegendProps {
removeUserColorBar: (userColorBarId: string) => void;
updateUserColorBar: (userColorBar: UserColorBar) => void;
updateUserColorBars: (userColorBars: UserColorBar[]) => void;
width?: number | string;
height?: number | string;
numTicks?: number;
onOpenColorBarEditor: (event: MouseEvent<HTMLCanvasElement>) => void;
}

export default function ColorBarLegend({
variableName,
variableUnits,
variableColorBarMinMax,
variableColorBarName,
variableColorBar,
variableOpacity,
updateVariableColorBar,
colorBars,
userColorBars,
addUserColorBar,
removeUserColorBar,
updateUserColorBar,
updateUserColorBars,
width,
height,
numTicks,
}: ColorBarLegendProps) {
export default function ColorBarLegend(
props: Omit<ColorBarLegendProps, "onOpenColorBarEditor">,
) {
const classes = useStyles();

const [colorBarRangeEditorAnchor, setColorBarRangeEditorAnchor] =
useState<HTMLDivElement | null>(null);
const [colorBarSelectAnchor, setColorBarSelectAnchor] =
useState<HTMLCanvasElement | null>(null);

if (!variableName) {
return null;
}
const {
variableName,
variableUnits,
variableColorBar,
variableColorBarNorm,
} = props;

const handleOpenColorBarRangeEditor = (
event: React.MouseEvent<HTMLDivElement>,
) => {
setColorBarRangeEditorAnchor(event.currentTarget);
};

const handleCloseColorBarRangeEditor = () => {
setColorBarRangeEditorAnchor(null);
};
const colorBarSelectAnchorRef = useRef<HTMLDivElement | null>(null);
const [colorBarSelectAnchorEl, setColorBarSelectAnchorEl] =
useState<HTMLDivElement | null>(null);

const handleOpenColorBarSelect = (
event: React.MouseEvent<HTMLCanvasElement>,
) => {
setColorBarSelectAnchor(event.currentTarget);
const handleOpenColorBarSelect = () => {
setColorBarSelectAnchorEl(colorBarSelectAnchorRef.current);
};

const handleCloseColorBarSelect = () => {
setColorBarSelectAnchor(null);
setColorBarSelectAnchorEl(null);
};

const variableTitle = `${variableName} (${variableUnits || "-"})`;
if (!variableName) {
return null;
}

const variableTitle = variableColorBar.categories
? variableName
: `${variableName} (${variableUnits || "-"})`;

return (
<div className={"ol-control " + classes.container}>
<div
className={"ol-control " + classes.container}
ref={colorBarSelectAnchorRef}
>
<div className={classes.title}>
<span>{variableTitle}</span>
</div>
<ColorBarCanvas
colorBar={variableColorBar}
opacity={variableOpacity}
width={width}
height={height}
onClick={handleOpenColorBarSelect}
/>
<ColorBarLabels
minValue={variableColorBarMinMax[0]}
maxValue={variableColorBarMinMax[1]}
numTicks={numTicks || 5}
onClick={handleOpenColorBarRangeEditor}
/>
<Popover
anchorEl={colorBarRangeEditorAnchor}
open={Boolean(colorBarRangeEditorAnchor)}
onClose={handleCloseColorBarRangeEditor}
anchorOrigin={{ vertical: "bottom", horizontal: "left" }}
transformOrigin={{ vertical: "top", horizontal: "center" }}
>
<ColorBarRangeEditor
variableTitle={variableTitle}
variableColorBarMinMax={variableColorBarMinMax}
variableColorBarName={variableColorBarName}
variableOpacity={variableOpacity}
updateVariableColorBar={updateVariableColorBar}
{variableColorBarNorm === "cat" && variableColorBar.categories ? (
<ColorBarLegendCategorical
variableColorBarCategories={variableColorBar.categories}
onOpenColorBarEditor={handleOpenColorBarSelect}
{...props}
/>
</Popover>
) : (
<ColorBarLegendContinuous
variableTitle={variableName}
onOpenColorBarEditor={handleOpenColorBarSelect}
{...props}
/>
)}
<Popover
anchorEl={colorBarSelectAnchor}
open={Boolean(colorBarSelectAnchor)}
anchorEl={colorBarSelectAnchorEl}
open={Boolean(colorBarSelectAnchorEl)}
onClose={handleCloseColorBarSelect}
anchorOrigin={{ vertical: "bottom", horizontal: "left" }}
transformOrigin={{ vertical: "top", horizontal: "left" }}
>
<ColorBarColorEditor
variableColorBarMinMax={variableColorBarMinMax}
variableColorBarName={variableColorBarName}
variableColorBar={variableColorBar}
variableOpacity={variableOpacity}
updateVariableColorBar={updateVariableColorBar}
colorBars={colorBars}
userColorBars={userColorBars}
addUserColorBar={addUserColorBar}
removeUserColorBar={removeUserColorBar}
updateUserColorBar={updateUserColorBar}
updateUserColorBars={updateUserColorBars}
/>
<ColorBarColorEditor {...props} />
</Popover>
</div>
);
Expand Down
Loading
Loading