Skip to content

Commit

Permalink
Merge pull request #393 from xcube-dev/forman-216-change_user_shape_c…
Browse files Browse the repository at this point in the history
…olor

Change color and opacity of user places
  • Loading branch information
forman authored Jul 19, 2024
2 parents c89c132 + aec6ecd commit e875fe7
Show file tree
Hide file tree
Showing 17 changed files with 511 additions and 116 deletions.
5 changes: 5 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@

* Made the right sidebar panel's tab bar position sticky. (#373)

* It is now possible to change the color and opacity of user places
and hence associated timeseries and statistic charts. (#216, #97)

* Improved visual style of selected places in the map.

### Fixes

* Fixed a problem that caused categorical map legends to list the categories
Expand Down
68 changes: 38 additions & 30 deletions src/actions/dataActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,12 @@ import i18n from "@/i18n";
import { ApiServerConfig, ApiServerInfo } from "@/model/apiServer";
import { ColorBar, ColorBars } from "@/model/colorBar";
import { Dataset, getDatasetUserVariables } from "@/model/dataset";
import { findPlaceInPlaceGroups, Place, PlaceGroup } from "@/model/place";
import {
findPlaceInPlaceGroups,
Place,
PlaceGroup,
PlaceStyle,
} from "@/model/place";
import { getUserPlacesFromCsv } from "@/model/user-place/csv";
import { getUserPlacesFromGeoJson } from "@/model/user-place/geojson";
import { getUserPlacesFromWkt } from "@/model/user-place/wkt";
Expand Down Expand Up @@ -81,7 +86,7 @@ import {
} from "./controlActions";
import { VolumeRenderMode } from "@/states/controlState";
import { MessageLogAction, postMessage } from "./messageLogActions";
import { renameUserPlaceInLayer } from "./mapActions";
import { renameUserPlaceInLayer, restyleUserPlaceInLayer } from "./mapActions";
import { ColorBarNorm } from "@/model/variable";
import { getStatistics } from "@/api/getStatistics";
import { StatisticsRecord } from "@/model/statistics";
Expand Down Expand Up @@ -447,6 +452,36 @@ export function _renameUserPlace(

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

export const RESTYLE_USER_PLACE = "RESTYLE_USER_PLACE";

export interface RestyleUserPlace {
type: typeof RESTYLE_USER_PLACE;
placeGroupId: string;
placeId: string;
placeStyle: PlaceStyle;
}

export function restyleUserPlace(
placeGroupId: string,
placeId: string,
placeStyle: PlaceStyle,
) {
return (dispatch: Dispatch<RestyleUserPlace>) => {
dispatch(_restyleUserPlace(placeGroupId, placeId, placeStyle));
restyleUserPlaceInLayer(placeGroupId, placeId, placeStyle);
};
}

export function _restyleUserPlace(
placeGroupId: string,
placeId: string,
placeStyle: PlaceStyle,
): RestyleUserPlace {
return { type: RESTYLE_USER_PLACE, placeGroupId, placeId, placeStyle };
}

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

export const REMOVE_USER_PLACE = "REMOVE_USER_PLACE";

export interface RemoveUserPlace {
Expand Down Expand Up @@ -530,34 +565,6 @@ export function addStatistics() {
};
}

// const addStatisticsFromMock = () => {
// const i = statisticsRecords.length + 1;
// setStatisticsRecords([
// ...statisticsRecords,
// {
// source: {
// datasetId: `ds${i}`,
// datasetTitle: `Dataset ${i}`,
// variableName: "CHL",
// placeId: "p029840456",
// geometry: null,
// },
// minimum: i,
// maximum: i + 1,
// mean: i + 0.5,
// standardDev: 0.1 * i,
// histogram: {
// bins: Array.from({ length: 100 }, (_, index) => ({
// x1: index,
// xc: index + 0.5,
// x2: index + 1,
// count: 1000 * Math.random(),
// })),
// },
// },
// ]);
// };

export const ADD_STATISTICS = "ADD_STATISTICS";

export interface AddStatistics {
Expand Down Expand Up @@ -1206,6 +1213,7 @@ export type DataAction =
| AddImportedUserPlaces
| RenameUserPlaceGroup
| RenameUserPlace
| RestyleUserPlace
| RemoveUserPlace
| RemoveUserPlaceGroup
| AddStatistics
Expand Down
22 changes: 22 additions & 0 deletions src/actions/mapActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,17 @@

import { default as OlMap } from "ol/Map";
import { Geometry as OlGeometry } from "ol/geom";
import { Vector as OlVectorLayer } from "ol/layer";
import { Vector as OlVectorSource } from "ol/source";
import { fromExtent } from "ol/geom/Polygon";
import { Extent as OlExtent } from "ol/extent";
import { getCenter } from "ol/extent";
import { default as OlSimpleGeometry } from "ol/geom/SimpleGeometry";

import { GEOGRAPHIC_CRS } from "@/model/proj";
import { MAP_OBJECTS } from "@/states/controlState";
import { PlaceStyle } from "@/model/place";
import { setFeatureStyle } from "@/components/ol/style";

// noinspection JSUnusedLocalSymbols
export function renameUserPlaceInLayer(
Expand All @@ -47,6 +51,24 @@ export function renameUserPlaceInLayer(
}
}

export function restyleUserPlaceInLayer(
placeGroupId: string,
placeId: string,
placeStyle: PlaceStyle,
) {
if (MAP_OBJECTS[placeGroupId]) {
const userLayer = MAP_OBJECTS[
placeGroupId
] as OlVectorLayer<OlVectorSource>;
const source = userLayer.getSource();
const feature = source?.getFeatureById(placeId);
if (feature) {
// console.log("selected feature:", feature, placeStyle);
setFeatureStyle(feature, placeStyle.color, placeStyle.opacity);
}
}
}

export function locateInMap(
mapId: string,
location: OlGeometry | OlExtent,
Expand Down
82 changes: 60 additions & 22 deletions src/components/PlaceSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,23 @@
* SOFTWARE.
*/

import * as React from "react";
import { useState, MouseEvent } from "react";
import { SxProps } from "@mui/system";
import Input from "@mui/material/Input";
import MenuItem from "@mui/material/MenuItem";
import Select, { SelectChangeEvent } from "@mui/material/Select";
import { SxProps } from "@mui/system";
import EditIcon from "@mui/icons-material/Edit";
import FormatColorFillIcon from "@mui/icons-material/FormatColorFill";
import RemoveCircleOutlineIcon from "@mui/icons-material/RemoveCircleOutline";
import TravelExploreIcon from "@mui/icons-material/TravelExplore";

import i18n from "@/i18n";
import { Dataset } from "@/model/dataset";
import { Place, USER_ID_PREFIX } from "@/model/place";
import { Place, PlaceInfo, PlaceStyle, USER_ID_PREFIX } from "@/model/place";
import { WithLocale } from "@/util/lang";
import EditableSelect from "./EditableSelect";
import ToolButton from "./ToolButton";
import PlaceStyleEditor from "@/components/PlaceStyleEditor";

// noinspection JSUnusedLocalSymbols
const styles: Record<string, SxProps> = {
Expand All @@ -49,6 +51,7 @@ interface PlaceSelectProps extends WithLocale {
datasets: Dataset[];
selectedPlaceGroupIds: string[] | null;
selectedPlaceId: string | null;
selectedPlaceInfo: PlaceInfo | null;
places: Place[];
placeLabels: string[];
selectPlace: (
Expand All @@ -61,6 +64,11 @@ interface PlaceSelectProps extends WithLocale {
placeId: string,
placeName: string,
) => void;
restyleUserPlace: (
placeGroupId: string,
placeId: string,
placeStyle: PlaceStyle,
) => void;
removeUserPlace: (
placeGroupId: string,
placeId: string,
Expand All @@ -75,12 +83,15 @@ export default function PlaceSelect({
placeLabels,
selectedPlaceId,
selectedPlaceGroupIds,
selectedPlaceInfo,
renameUserPlace,
restyleUserPlace,
removeUserPlace,
places,
locateSelectedPlace,
}: PlaceSelectProps) {
const [editMode, setEditMode] = React.useState(false);
const [editMode, setEditMode] = useState(false);
const [styleAnchorEl, setStyleAnchorEl] = useState<HTMLElement | null>(null);

places = places || [];
placeLabels = placeLabels || [];
Expand All @@ -97,6 +108,10 @@ export default function PlaceSelect({
renameUserPlace(selectedPlaceGroupId!, selectedPlaceId!, placeName);
};

const updatePlaceStyle = (placeStyle: PlaceStyle) => {
restyleUserPlace(selectedPlaceGroupId!, selectedPlaceId!, placeStyle);
};

const handlePlaceChange = (event: SelectChangeEvent) => {
selectPlace(event.target.value || null, places, true);
};
Expand Down Expand Up @@ -129,12 +144,24 @@ export default function PlaceSelect({
selectedPlaceGroupId.startsWith(USER_ID_PREFIX) &&
selectedPlaceId !== "";

let actions;
let actions = [
<ToolButton
key="locatePlace"
onClick={locateSelectedPlace}
tooltipText={i18n.get("Locate place in map")}
icon={<TravelExploreIcon />}
/>,
];

if (!editMode && isEditableUserPlace) {
const handleEditButtonClick = () => {
setEditMode(true);
};

const handleStyleButtonClick = (event: MouseEvent<HTMLElement>) => {
setStyleAnchorEl(event.currentTarget);
};

const handleRemoveButtonClick = () => {
removeUserPlace(selectedPlaceGroupId!, selectedPlaceId!, places);
};
Expand All @@ -146,31 +173,42 @@ export default function PlaceSelect({
tooltipText={i18n.get("Rename place")}
icon={<EditIcon />}
/>,
<ToolButton
key="styleButton"
onClick={handleStyleButtonClick}
tooltipText={i18n.get("Style place")}
icon={<FormatColorFillIcon />}
/>,
<ToolButton
key="removeButton"
onClick={handleRemoveButtonClick}
tooltipText={i18n.get("Remove place")}
icon={<RemoveCircleOutlineIcon />}
/>,
<ToolButton
key="locatePlace"
onClick={locateSelectedPlace}
tooltipText={i18n.get("Locate place in map")}
icon={<TravelExploreIcon />}
/>,
];
].concat(actions);
}

return (
<EditableSelect
itemValue={placeName}
setItemValue={setPlaceName}
validateItemValue={(v) => v.trim().length > 0}
editMode={editMode}
setEditMode={setEditMode}
labelText={i18n.get("Place")}
select={select}
actions={actions}
/>
<>
<EditableSelect
itemValue={placeName}
setItemValue={setPlaceName}
validateItemValue={(v) => v.trim().length > 0}
editMode={editMode}
setEditMode={setEditMode}
labelText={i18n.get("Place")}
select={select}
actions={actions}
/>
{selectedPlaceInfo && (
<PlaceStyleEditor
anchorEl={styleAnchorEl}
setAnchorEl={setStyleAnchorEl}
isPoint={selectedPlaceInfo.place.geometry.type === "Point"}
placeStyle={selectedPlaceInfo}
updatePlaceStyle={updatePlaceStyle}
/>
)}
</>
);
}
Loading

0 comments on commit e875fe7

Please sign in to comment.