Skip to content

Commit

Permalink
Merge pull request #132 from RENCI/group-by-runs
Browse files Browse the repository at this point in the history
Group by model runs on the layer tray
  • Loading branch information
lstillwe authored Jul 30, 2024
2 parents 0542d48 + eff16ef commit 93a73e0
Show file tree
Hide file tree
Showing 5 changed files with 186 additions and 15 deletions.
27 changes: 27 additions & 0 deletions src/components/trays/layers/delete-layer-button.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,30 @@ export const DeleteLayerButton = ({ layerId }) => {
DeleteLayerButton.propTypes = {
layerId: PropTypes.string.isRequired
};

export const DeleteModelRunButton = ({ groupId }) => {
const { removeModelRun } = useLayers();

return (
<ActionButton
color="danger"
variant="outlined"
onClick={ () => removeModelRun(groupId) }
sx={{
alignContent: 'right',
m: 1,
'filter': 'opacity(0.3)',
transition: 'filter 250ms',
'&:hover': {
'filter': 'opacity(1.0)',
},
}}
>
<RemoveIcon />
</ActionButton>
);
};

DeleteModelRunButton.propTypes = {
groupId: PropTypes.string.isRequired
};
2 changes: 1 addition & 1 deletion src/components/trays/layers/form.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react';
import { ModelSelection } from '../model-selection/model-selection';
import { ModelSelection } from '@model-selection/model-selection';

export const AddLayerForm = () => {
return (
Expand Down
158 changes: 148 additions & 10 deletions src/components/trays/layers/list.js
Original file line number Diff line number Diff line change
@@ -1,27 +1,165 @@
import React from 'react';
import {
AccordionGroup,
Divider,
AccordionGroup, Box,
Divider, Accordion, AccordionSummary, AccordionDetails
} from '@mui/joy';
import { useLayers } from '@context';
import { LayerCard } from './layer-card';
import { DeleteModelRunButton } from "@components/trays/layers/delete-layer-button";

/**
* gets the header data property name index
* This takes into account the two types of runs (tropical, synoptic)
*
* @param layerProps
* @param type
* @returns {string}
*/
const getPropertyName = (layerProps, type) => {
// init the return
let ret_val = undefined;

// capture the name of the element for tropical storms and advisory numbers
if (layerProps['met_class'] === 'tropical') {
switch (type) {
case 'stormOrModelEle':
ret_val = layerProps['storm_name'];
break;
case 'numberName':
ret_val = ' Adv: ';
break;
case 'numberEle':
ret_val = layerProps['advisory_number'];
break;
}
}
// capture the name of the synoptic ADCIRC models and cycle numbers
else {
switch (type) {
case 'stormOrModelEle':
ret_val = layerProps['model'];
break;
case 'numberName':
ret_val = ' Cycle: ';
break;
case 'numberEle':
ret_val = layerProps['cycle'];
break;
}
}

// return to the caller
return ret_val;
};


/**
* gets the summary header text for the layer groups.
* This takes into account the two types of runs (tropical, synoptic)
*
* @param layerProps
* @returns {string}
*/
const getHeaderSummary = (layerProps) => {
// get the full accordian summary text
return layerProps['run_date'] + ': ' +
((getPropertyName(layerProps, 'stormOrModelEle') === undefined) ? 'Data error' : getPropertyName(layerProps, 'stormOrModelEle').toUpperCase()) +
', ' + getPropertyName(layerProps, 'numberName') + getPropertyName(layerProps, 'numberEle') +
', Type: ' + layerProps['event_type'] +
', Grid: ' + layerProps['grid_type'] +
((layerProps['meteorological_model'] === 'None') ? '' : ', ' + layerProps['meteorological_model']);
};

/**
* renders the layer cards for a model run
*
* @param layers
* @param group
* @returns {*[]}
*/
const renderLayerCards = (layers, group) => {
// init the return
const layerCards = [];

// filter/map the layers to create/return the layer card list
layers
// capture the layers for this group
.filter(layer => ( layer['group'] === group) )
// at this point we have the distinct runs
.map((layer, idx) => {
layerCards.push(<LayerCard key={`layer-${ idx }`} layer={ layer } index={ idx }> </LayerCard>);
});

// return to the caller
return layerCards;
};

/**
* collect the list of unique layer groups
*
* @param layers
* @returns {*[]}
*/
const getGroupList = (layers) => {
// init the group list
const groupList = [];

// loop through the layers and get the unique groups
layers
// filter by the group name
.filter((groups, idx, self) =>
( idx === self.findIndex((t)=> ( t['group'] === groups['group']) )))
// .sort((a, b) =>
// a['run_date'] < b['run_date'] ? 1 : -1)
// at this point we have the distinct runs
.map((layer) => {
groupList.push(layer);
});

// return the list of groups
return groupList;
};

/**
* render the layers for the selected run groups
*
* @returns {JSX.Element}
* @constructor
*/
export const LayersList = () => {
// get a handle to the layer state
const { defaultModelLayers } = useLayers();

// get the default layers
const layers = [...defaultModelLayers];

// get the unique groups in the selected run groups
const groupList = getGroupList(layers);

// loop through the layers and put them away
return (
<AccordionGroup variant="soft">
{
layers
.sort((a, b) => a.state.order - b.state.order)
.map((layer, index) => {
// loop through the layer groups and put them away
groupList
// filter by the group name
.filter((groups, idx, self) =>
( idx === self.findIndex((t)=> ( t['group'] === groups['group']) )))
// at this point we have the distinct runs
.map((groups, idx) => {
return (
<LayerCard
key={ `layer-${ layer.id }` }
layer={ layer }
index={ index }
/>
<Accordion key={idx}>
<Box sx={{ display: "flex" }}>
<DeleteModelRunButton groupId={ groups['group'] } />
<Box>
<AccordionSummary sx={{ fontSize: 12 }}>{ getHeaderSummary(groups['properties']) } </AccordionSummary>
</Box>
</Box>

<AccordionDetails>
{ renderLayerCards( layers, groups['group'] ) }
</AccordionDetails>
</Accordion>
);
})
}
Expand Down
4 changes: 0 additions & 4 deletions src/components/trays/model-selection/catalogItems.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ export default function CatalogItems(data) {
const [accordianDateIndex, setAccordianDateIndex] = useState(-1);

// variables for the display of checkbox labels
let stormOrModelName = null;
let stormOrModelEle = null;
let numberName = null;
let numberEle = null;
Expand Down Expand Up @@ -137,14 +136,12 @@ export default function CatalogItems(data) {
else {
// save the name of the element for tropical storms and advisory numbers
if (data.isTropical) {
stormOrModelName = '';
stormOrModelEle = 'storm_name';
numberName = ' Adv: ';
numberEle = 'advisory_number';
}
// save the name of the synoptic ADCIRC models and cycle numbers
else if (!data.isTropical) {
stormOrModelName = '';
stormOrModelEle = 'model';
numberName = ' Cycle: ';
numberEle = 'cycle';
Expand Down Expand Up @@ -185,7 +182,6 @@ export default function CatalogItems(data) {
key={ mbrIdx }
checked={ getCheckedState(mbr.group) }
label={
stormOrModelName +
((mbr['properties'][stormOrModelEle] === undefined) ? 'Data error' : mbr['properties'][stormOrModelEle].toUpperCase()) + ', ' +
numberName + mbr['properties'][numberEle] +
', Type: ' + mbr['properties']['event_type'] +
Expand Down
10 changes: 10 additions & 0 deletions src/context/map-context.js
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,15 @@ export const LayersProvider = ({ children }) => {
setDefaultModelLayers(newLayers);
};

const removeModelRun = groupId => {
const index = defaultModelLayers.findIndex(l => l.group === groupId);
if (index === -1) {
return;
}
const newLayers = defaultModelLayers.filter(l => l.group !== groupId);
setDefaultModelLayers(newLayers);
};

const setLayerOpacity = (id, newOpacity) => {
const newLayers = [...defaultModelLayers];
const index = newLayers.findIndex(l => l.id === id);
Expand Down Expand Up @@ -166,6 +175,7 @@ export const LayersProvider = ({ children }) => {
showShareComment, setShowShareComment,
swapLayers,
removeLayer,
removeModelRun,
layerTypes,
baseMap,
setBaseMap,
Expand Down

0 comments on commit 93a73e0

Please sign in to comment.