Skip to content
This repository has been archived by the owner on Oct 20, 2023. It is now read-only.

Commit

Permalink
Graph Updates: Labels, Anchor Mode, Colors, Styles (#182)
Browse files Browse the repository at this point in the history
* show host name in BeaconRow

* Show More Labels: add graph className.hiddenLabel rather than setting display:none;

* add graph toggleSimpleForces functionality

* Show More Labels: add toggle button; add expanded modes for GraphControls

* Unify graph Past and Future colors for more simplicity (REVERT if undesired)

* add more graph custom colors: foreground and background

* updateClassName of graph labels also

* GraphControls LegendStyle was missing radius

* ellipsize Row contents

* Revert "ellipsize Row contents"

This reverts commit 9efe5cd.

* Revert "show host name in BeaconRow"

This reverts commit 67aa5a0.

* Minor test update.

---------

Co-authored-by: James Bradford <j.bradford@pnnl.gov>
Co-authored-by: Courtney Carpenter <ccarpenter28@gmail.com>
  • Loading branch information
3 people authored Sep 20, 2023
1 parent 269e923 commit 80162d7
Show file tree
Hide file tree
Showing 12 changed files with 340 additions and 173 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ type NodePreviewProps = FlexProps &
};

export const NodePreview = observer<NodePreviewProps>(({ type, shape, text, color = 'default', size: _, ...props }) => (
<Flex align="center" gap="0.5ch" css={{ color: nodeColor[color].token }} {...props}>
<Flex align="center" gap="0.5ch" css={{ color: nodeColor[color].fgToken }} {...props}>
<NodeIcon {...{ type, shape, color }} />
{text && <Txt>{text === 'color' ? color : shape}</Txt>}
</Flex>
Expand Down
26 changes: 20 additions & 6 deletions applications/client/src/views/Campaign/Graph/Graph.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ import { CampaignLoadingMessage, useStore } from '@redeye/client/store';
import { CoreTokens, Header, Spacer, ThemeClasses, Txt } from '@redeye/ui-styles';
import { observer } from 'mobx-react-lite';
import type { ComponentProps } from 'react';
import { useEffect, useMemo, useRef } from 'react';
import { useEffect, useMemo, useRef, useState } from 'react';
import { ErrorBoundary } from 'react-error-boundary';
import { useResizeDetector } from 'react-resize-detector';
import { nodeColorStyles } from './node-colors';
import { graphStyles } from './graph-styles';
import { graphStyles, showMoreLabelsGraphStyles } from './graph-styles';
import type { GraphControlFunctions } from './GraphControls';
import { GraphControls } from './GraphControls';
import { LoadingOverlay } from './LoadingOverlay';

Expand Down Expand Up @@ -40,14 +41,22 @@ export const Graph = observer<GraphProps>((props) => {
}
}, [graphRef, store.campaign.isLoading]);

const zoomControls = useMemo(
const [showMoreLabels, setShowMoreLabels] = useState(false);
const [isSimpleForces, setIsSimpleForces] = useState(false);

const zoomControls: GraphControlFunctions = useMemo(
() => ({
zoomIn: () => store.campaign.graph?.zoomIn(),
zoomOut: () => store.campaign.graph?.zoomOut(),
zoomToFit: () => store.campaign.graph?.zoomToFit(),
exportSVG: () => store.campaign.graph?.exportSVG(CoreTokens.Background3),
toggleSimpleForces: (on) => {
store.campaign.graph?.useForceMode(on ? 'simple' : 'graph');
setIsSimpleForces(on);
},
setShowMoreLabels,
}),
[]
[setIsSimpleForces, setShowMoreLabels]
);

const currentMoment = store.settings.momentTz(store.campaign.timeline?.scrubberTime as Date);
Expand All @@ -70,11 +79,16 @@ export const Graph = observer<GraphProps>((props) => {
<LoadingOverlay />
) : null}
<svg
css={[graphLayoutStyles, graphStyles, nodeColorStyles]}
css={[graphLayoutStyles, graphStyles, nodeColorStyles, showMoreLabels && showMoreLabelsGraphStyles]}
ref={graphRef}
className={store.settings.theme === 'dark' ? ThemeClasses.DARK : ThemeClasses.LIGHT}
/>
<GraphControls {...zoomControls} css={controlsStyles} />
<GraphControls
{...zoomControls}
isSimpleForces={isSimpleForces}
showMoreLabels={showMoreLabels}
css={controlsStyles}
/>
</ErrorBoundary>
</div>
);
Expand Down
282 changes: 157 additions & 125 deletions applications/client/src/views/Campaign/Graph/GraphControls.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
import { Button, ButtonGroup, Classes, Divider } from '@blueprintjs/core';
import { Add16, CenterSquare16, Close16, Export16, Help16, Subtract16 } from '@carbon/icons-react';
import {
Add16,
CenterSquare16,
Close16,
Export16,
Harbor16,
Help16,
StringText16,
Subtract16,
} from '@carbon/icons-react';
import { css } from '@emotion/react';
import styled from '@emotion/styled';
import { CarbonIcon } from '@redeye/client/components';
Expand All @@ -10,144 +19,166 @@ import type { ComponentProps } from 'react';
import { useState } from 'react';
import { graphStyles } from './graph-styles';

type GraphControlsProps = ComponentProps<'div'> & {
export type GraphControlFunctions = {
zoomIn: () => void;
zoomOut: () => void;
zoomToFit: () => void;
exportSVG: () => void;
isSimpleForces?: boolean;
toggleSimpleForces: (on: boolean) => void;
showMoreLabels?: boolean;
setShowMoreLabels: (on: boolean) => void;
};

export const GraphControls = observer<GraphControlsProps>(({ zoomIn, zoomOut, zoomToFit, exportSVG, ...props }) => {
const [isOpen, setIsOpen] = useState(false);
export const GraphControls = observer<GraphControlFunctions & ComponentProps<'div'>>(
({
zoomIn,
zoomOut,
zoomToFit,
exportSVG,
isSimpleForces = false,
toggleSimpleForces,
showMoreLabels = false,
setShowMoreLabels,
...props
}) => {
const [isOpen, setIsOpen] = useState(false);

return (
<div css={rootStyle} {...props}>
{isOpen ? (
<div cy-test="legend-box" css={[controlGroupStyle, settingsWrapperStyle]}>
return (
<div css={rootStyle} {...props}>
{isOpen ? (
<div cy-test="legend-box" css={[controlGroupStyle, settingsWrapperStyle]}>
<Button
icon={<CarbonIcon icon={Close16} />}
css={settingsCloseStyle}
onClick={() => {
setIsOpen(false);
}}
minimal
small
/>
<Header small css={legendTitle}>
Legend
</Header>
{(
[
['Selected', [GCN.selectedFocus], [GCN.selected], [GCN.selected]],
['Preview', [GCN.previewed], [GCN.previewed]],
['Active', [GCN.present], [GCN.present]],
['Exited', [GCN.past], [GCN.past]],
['Future', [GCN.future], [GCN.future]],
] as [string, string[], string[], string[]?][]
).map((legendItem) => (
<div css={legendItemStyle}>
<svg height={svgStyle.height} width={svgStyle.width} css={graphStyles}>
<line
x1={svgStyle.center}
y1={svgStyle.center}
x2={svgStyle.width - svgStyle.center}
y2={svgStyle.center}
css={[legendLineStyle]}
className={[GCN.siblingLink, ...legendItem[2]].join(' ')}
/>
<circle
r={svgStyle.radius}
cx={svgStyle.center}
cy={svgStyle.center}
css={[legendNodeStyle]}
className={[GCN.subNode, GCN.softwareNode, ...legendItem[1]].join(' ')}
/>
<circle
r={svgStyle.radius}
cx={svgStyle.width - svgStyle.center}
cy={svgStyle.center}
css={[legendNodeStyle]}
className={[GCN.subNode, GCN.softwareNode, ...(legendItem[3] ?? legendItem[1])].join(' ')}
/>
</svg>
<Txt css={[legendLabelStyle]}>{legendItem[0]}</Txt>
</div>
))}
</div>
) : (
<GraphControlButtonGroup vertical hidden={isOpen}>
<Button
cy-test="graph-legend"
icon={<CarbonIcon icon={Help16} />}
onClick={() => {
setIsOpen(true);
}}
title="Settings"
minimal
/>
</GraphControlButtonGroup>
)}
<GraphControlButtonGroup vertical>
<Button
icon={<CarbonIcon icon={Close16} />}
css={settingsCloseStyle}
onClick={() => {
setIsOpen(false);
}}
active={isSimpleForces}
intent={isSimpleForces ? 'primary' : 'none'}
rightIcon={<CarbonIcon icon={Harbor16} />}
onClick={() => toggleSimpleForces(!isSimpleForces)}
title="Anchor Nodes on Drag"
text={isOpen && 'Anchor Drag'}
minimal
small
alignText="left"
/>
<Header small css={legendTitle}>
Legend
</Header>
{(
[
['Selected', [GCN.selectedFocus], [GCN.selected], [GCN.selected]],
['Preview', [GCN.previewed], [GCN.previewed]],
['Active', [GCN.present], [GCN.present]],
['Exited', [GCN.past], [GCN.past]],
['Future', [GCN.future], [GCN.future]],
] as [string, string[], string[], string[]?][]
).map((legendItem) => (
<div css={legendItemStyle}>
<svg height={svgStyle.height} width={svgStyle.width} css={graphStyles}>
<line
x1={svgStyle.center}
y1={svgStyle.center}
x2={svgStyle.width - svgStyle.center}
y2={svgStyle.center}
css={[legendLineStyle]}
className={[GCN.siblingLink, ...legendItem[2]].join(' ')}
/>
<circle
r={svgStyle.center}
cx={svgStyle.center}
cy={svgStyle.center}
css={[legendNodeStyle]}
className={[GCN.subNode, GCN.softwareNode, ...legendItem[1]].join(' ')}
/>
<circle
r={svgStyle.center}
cx={svgStyle.width - svgStyle.center}
cy={svgStyle.center}
css={[legendNodeStyle]}
className={[GCN.subNode, GCN.softwareNode, ...(legendItem[3] ?? legendItem[1])].join(' ')}
/>
</svg>
<Txt css={[legendLabelStyle]}>{legendItem[0]}</Txt>
</div>
))}
</div>
) : (
<GraphControlButtonGroup vertical hidden={isOpen}>
<GraphControlDivider />
<Button
cy-test="graph-legend"
icon={<CarbonIcon icon={Help16} />}
onClick={() => {
setIsOpen(true);
}}
title="Settings"
active={showMoreLabels}
intent={showMoreLabels ? 'primary' : 'none'}
rightIcon={<CarbonIcon icon={StringText16} />}
onClick={() => setShowMoreLabels(!showMoreLabels)}
title="Show Labels"
alignText="left"
text={isOpen && 'Show Labels'}
minimal
/>
<GraphControlDivider />
<Button
cy-test="export-graph"
rightIcon={<CarbonIcon icon={Export16} />}
onClick={exportSVG}
title="Export Graph"
text={isOpen && 'Export Graph'}
alignText="left"
minimal
/>
</GraphControlButtonGroup>
<GraphControlButtonGroup vertical>
<Button
cy-test="zoom-in"
rightIcon={<CarbonIcon icon={Add16} />}
onClick={zoomIn}
title="Zoom In"
text={isOpen && 'Zoom In'}
alignText="left"
minimal
/>
<GraphControlDivider />
<Button
cy-test="zoom-out"
rightIcon={<CarbonIcon icon={Subtract16} />}
onClick={zoomOut}
title="Zoom Out"
text={isOpen && 'Zoom Out'}
alignText="left"
minimal
/>
<GraphControlDivider />
<Button
cy-test="center-graph"
rightIcon={<CarbonIcon icon={CenterSquare16} />}
onClick={zoomToFit}
title="Zoom To Fit"
text={isOpen && 'Zoom To Fit'}
alignText="left"
minimal
/>
{/* <GraphControlDivider />
<Button
active={store.campaign?.graph.dragMode}
icon={<CarbonIcon icon={DataRefineryReference16} />}
onClick={() => store.campaign?.graph.setDragMode()}
disabled // no-op
minimal
/> */}
</GraphControlButtonGroup>
)}
{/* <GraphControlButtonGroup vertical>
<Button
rightIcon={<CarbonIcon icon={Network_216} />}
onClick={async () => {
await store.campaign?.graph.buildGraphTree();
zoomToFit();
}}
title="Tree Layout"
text={state.isOpen ? 'Tree Layout' : undefined}
minimal
/>
<GraphControlDivider />
<Button
rightIcon={<CarbonIcon icon={DataVis_116} />}
onClick={async () => {
await store.campaign?.graph.layout.colaLayout();
zoomToFit();
}}
title="Web Layout"
text={state.isOpen ? 'Web Layout' : undefined}
minimal
/>
</GraphControlButtonGroup> */}
<GraphControlButtonGroup vertical>
<Button cy-test="zoom-in" rightIcon={<CarbonIcon icon={Add16} />} onClick={zoomIn} title="Zoom In" minimal />
<GraphControlDivider />
<Button
cy-test="zoom-out"
rightIcon={<CarbonIcon icon={Subtract16} />}
onClick={zoomOut}
title="Zoom Out"
minimal
/>
<GraphControlDivider />
<Button
cy-test="center-graph"
rightIcon={<CarbonIcon icon={CenterSquare16} />}
onClick={zoomToFit}
title="Zoom To Fit"
minimal
/>
<GraphControlDivider />
<Button
cy-test="export-graph"
rightIcon={<CarbonIcon icon={Export16} />}
onClick={exportSVG}
title="Export Graph"
minimal
/>
</GraphControlButtonGroup>
</div>
);
});
</div>
);
}
);

const rootStyle = css`
display: flex;
Expand Down Expand Up @@ -188,6 +219,7 @@ const svgStyle = {
height: 24,
width: 56,
center: 12,
radius: 6,
};
const legendTitle = css`
margin-bottom: 0.5rem;
Expand Down
Loading

0 comments on commit 80162d7

Please sign in to comment.