Skip to content

Commit

Permalink
Merge pull request #65 from concord-consortium/188319491-highlight-ou…
Browse files Browse the repository at this point in the history
…tputs

feat: Highlight outputs [PT-188319491]
  • Loading branch information
dougmartin authored Sep 27, 2024
2 parents 6cdaa09 + 129b41f commit 464968c
Show file tree
Hide file tree
Showing 5 changed files with 104 additions and 9 deletions.
11 changes: 11 additions & 0 deletions src/components/app.scss
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,17 @@
}
.sequences {
padding: 5px;

.sequence {
cursor: pointer;

&.highlighted {
background-color: #FF00877f;
}
&.disabled {
cursor: default;
}
}
}
}
}
Expand Down
34 changes: 33 additions & 1 deletion src/components/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ export const App = () => {
const innerOutputRef = useRef<HTMLDivElement | null>(null);
const [fastSimulation, setFastSimulation] = useState(defaultFastSimulation);
const fastSimulationRef = useRef(false);
const [highlightOutput, setHighlightOutput] = useState<{group: SequenceGroup, sequence: Node[]}|undefined>();

const handleDimensionChange = ({width, height}: {width: number, height: number}) => {
widthRef.current = width;
Expand All @@ -153,6 +154,7 @@ export const App = () => {

const setSelectedNodeId = useCallback((id?: string, skipToggle?: boolean) => {
if (!animating) {
setHighlightOutput(undefined);
if ((!id || (id === selectedNodeId)) && !skipToggle) {
_setSelectedNodeId(undefined);
} else {
Expand All @@ -175,6 +177,10 @@ export const App = () => {

const graphEmpty = useMemo(() => graph.nodes.length === 0, [graph]);

const highlightOutputNodes = useMemo(() => {
return animating ? undefined : highlightOutput?.sequence;
}, [animating, highlightOutput]);

const generateNewSequence = useCallback(async () => {
currentSequence.current = [];
currentSequenceIndex.current = 0;
Expand Down Expand Up @@ -323,6 +329,7 @@ export const App = () => {
}, [finishAnimating, startAnimationInterval]);

const handleStep = useCallback(async () => {
setHighlightOutput(undefined);
if ((generationMode !== "stepping") && (generationMode !== "paused")) {
setGenerationMode("stepping");
await generateNewSequence();
Expand All @@ -335,6 +342,7 @@ export const App = () => {
}, [generationMode, animateCurrentSequenceIndex, animateNextSequenceIndex, finishAnimating, generateNewSequence]);

const handlePlay = useCallback(async () => {
setHighlightOutput(undefined);
setGenerationMode("playing");
await generateNewSequence();
animateCurrentSequenceIndex();
Expand All @@ -350,6 +358,15 @@ export const App = () => {
finishAnimating(true);
};

const toggleHighlightOutput = useCallback((group: SequenceGroup, sequence: Node[]) => {
setSelectedNodeId();
if (!highlightOutput || (highlightOutput.group !== group) || (highlightOutput.sequence !== sequence)) {
setHighlightOutput({group, sequence});
} else {
setHighlightOutput(undefined);
}
}, [highlightOutput, setHighlightOutput, setSelectedNodeId]);

const uiForGenerate = () => {
const playLabel = generationMode === "playing" ? "Pause" : (generationMode === "paused" ? "Resume" : "Play");
const PlayOrPauseIcon = generationMode === "playing" ? PauseIcon : PlayIcon;
Expand Down Expand Up @@ -431,7 +448,20 @@ export const App = () => {
<div className="group" key={i}>
<SequenceOutputHeader group={group} />
<div className="sequences">
{group.sequences.map((s, j) => <div key={j}>{s.map(n => n.label).join(group.delimiter)}</div>)}
{group.sequences.map((s, j) => (
<div
key={j}
className={clsx("sequence", {
disabled: animating,
highlighted: (
highlightOutput && highlightOutput.group === group && highlightOutput.sequence === s
)
})}
onClick={animating ? undefined : () => toggleHighlightOutput(group, s)}
>
{s.map(n => n.label).join(group.delimiter)}
</div>
))}
</div>
</div>
);
Expand Down Expand Up @@ -546,6 +576,7 @@ export const App = () => {
highlightLoopOnNode={highlightLoopOnNode}
highlightEdge={highlightEdge}
highlightAllNextNodes={highlightAllNextNodes}
highlightOutputNodes={highlightOutputNodes}
selectedNodeId={selectedNodeId}
animating={animating}
setGraph={setGraph}
Expand All @@ -566,6 +597,7 @@ export const App = () => {
highlightLoopOnNode={highlightLoopOnNode}
highlightEdge={highlightEdge}
highlightAllNextNodes={highlightAllNextNodes}
highlightOutputNodes={highlightOutputNodes}
selectedNodeId={selectedNodeId}
animating={animating}
graphEmpty={graphEmpty}
Expand Down
4 changes: 3 additions & 1 deletion src/components/dataset.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ interface Props {
highlightLoopOnNode?: Node,
highlightEdge?: Edge,
highlightAllNextNodes: boolean;
highlightOutputNodes?: Node[];
graph: GraphData;
selectedNodeId?: string;
animating: boolean;
Expand All @@ -25,7 +26,7 @@ interface Props {
}

export const Dataset = (props: Props) => {
const {highlightNode, highlightLoopOnNode, highlightEdge, highlightAllNextNodes,
const {highlightNode, highlightLoopOnNode, highlightEdge, highlightAllNextNodes, highlightOutputNodes,
graph, graphEmpty, setSelectedNodeId, selectedNodeId, animating,
fitViewAt, recenterViewAt,
onReset, onReturnToMainMenu, onFitView, onRecenterView} = props;
Expand Down Expand Up @@ -79,6 +80,7 @@ export const Dataset = (props: Props) => {
highlightLoopOnNode={highlightLoopOnNode}
highlightEdge={highlightEdge}
highlightAllNextNodes={highlightAllNextNodes}
highlightOutputNodes={highlightOutputNodes}
selectedNodeId={selectedNodeId}
animating={animating}
allowDragging={true && !animating}
Expand Down
4 changes: 3 additions & 1 deletion src/components/drawing.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ interface Props {
highlightLoopOnNode?: Node,
highlightEdge?: Edge,
highlightAllNextNodes: boolean;
highlightOutputNodes?: Node[];
graph: GraphData;
selectedNodeId?: string;
animating: boolean;
Expand All @@ -38,7 +39,7 @@ const keepPunctuationRegex = /[.,?!:;]/g;
const removePunctuationRegex = /["(){}[\]_+=|\\/><]/g;

export const Drawing = (props: Props) => {
const {highlightNode, highlightLoopOnNode, highlightEdge, highlightAllNextNodes,
const {highlightNode, highlightLoopOnNode, highlightEdge, highlightAllNextNodes, highlightOutputNodes,
graph, setGraph, setHighlightNode, setSelectedNodeId: _setSelectedNodeId,
fitViewAt, recenterViewAt,
selectedNodeId, animating, onReset, onReturnToMainMenu, onFitView, onRecenterView} = props;
Expand Down Expand Up @@ -314,6 +315,7 @@ export const Drawing = (props: Props) => {
highlightNode={highlightNode}
highlightEdge={highlightEdge}
highlightAllNextNodes={highlightAllNextNodes}
highlightOutputNodes={highlightOutputNodes}
highlightLoopOnNode={highlightLoopOnNode}
allowDragging={drawingMode === "select"}
autoArrange={autoArrange}
Expand Down
60 changes: 54 additions & 6 deletions src/components/graph.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ type Props = {
highlightLoopOnNode?: Node,
highlightEdge?: Edge,
highlightAllNextNodes: boolean;
highlightOutputNodes?: Node[];
allowDragging: boolean;
autoArrange: boolean;
rubberBand?: RubberBand;
Expand Down Expand Up @@ -242,7 +243,7 @@ const calculateNodeFontSize = (d: D3Node) => {
const lineDashArray = (edge: D3Edge) => edge.value ? "" : "4";

export const Graph = (props: Props) => {
const {graph, highlightNode, highlightLoopOnNode, highlightEdge, highlightAllNextNodes,
const {graph, highlightNode, highlightLoopOnNode, highlightEdge, highlightAllNextNodes, highlightOutputNodes,
allowDragging, autoArrange, rubberBand, drawingMode,
onClick, onNodeClick, onNodeDoubleClick, onEdgeClick, onDragStop,
fitViewAt, recenterViewAt,
Expand Down Expand Up @@ -327,6 +328,47 @@ export const Graph = (props: Props) => {

}, [selectedNodeId, animating, graph]);

const unhighlightNonOutput = useCallback((svg: d3.Selection<any, unknown, null, undefined>) => {
if (animating) {
return;
}

const nodeIds = highlightOutputNodes?.map(n => n.id) ?? [];
const haveNodes = nodeIds.length > 0;

// unhighlight non-highlighted nodes
svg
.selectAll("g.node")
.selectAll("ellipse")
.style("opacity", 1)
.filter((d: any) => haveNodes && !nodeIds.includes(d.id))
.style("opacity", unselectedOpacity);

// unhighlight non-highlighted edges
svg
.selectAll("line")
.style("opacity", 1)
.filter((d: any) => ((
(d.value > 0) && haveNodes && (!(nodeIds.includes(d.source?.id) && nodeIds.includes(d.target?.id))))))
.style("opacity", unselectedOpacity);

// unhighlight loops
svg
.selectAll("path.loop")
.style("opacity", 1)
.filter((d: any) => haveNodes && !nodeIds.includes(d.id))
.style("opacity", unselectedOpacity);

// unhighlight text
svg
.selectAll("g.node")
.selectAll("text")
.style("opacity", 1)
.filter((d: any) => haveNodes && !nodeIds.includes(d.id))
.style("opacity", unselectedOpacity);

}, [highlightOutputNodes, animating]);

// calculate the svg dimensions
useEffect(() => {
if (dimensions) {
Expand Down Expand Up @@ -769,11 +811,11 @@ export const Graph = (props: Props) => {
.selectAll("ellipse")
.attr("fill", "#fff");

// highlight animated node
// highlight animated node and output nodes
root
.selectAll("g.node")
.selectAll("ellipse")
.filter((d: any) => highlightNode?.id === d.id)
.filter((d: any) => (highlightNode?.id === d.id) || !!highlightOutputNodes?.find(n => n.id === d.id))
.attr("fill", animatedNodeColor);

// highlight animated edges
Expand All @@ -784,8 +826,12 @@ export const Graph = (props: Props) => {
.attr("marker-end", arrowUrl)
.filter((d: any) => ((
(d.value > 0) && (
(highlightNode?.id === d.source?.id && highlightAllNextNodes) ||
(highlightEdge?.from === d.source?.id && highlightEdge?.to === d.target?.id)))))
(highlightNode?.id === d.source?.id && highlightAllNextNodes) ||
(highlightEdge?.from === d.source?.id && highlightEdge?.to === d.target?.id) ||
(!!highlightOutputNodes?.find(n => n.id === d.source?.id)
&& !!highlightOutputNodes?.find(n => n.id === d.target?.id))
)
)))
.attr("stroke", animatedArrowColor)
.attr("stroke-dasharray", highlightAllNextNodes ? "4" : "")
.attr("marker-end", animatedArrowUrl);
Expand All @@ -800,9 +846,11 @@ export const Graph = (props: Props) => {
.attr("stroke-dasharray", highlightAllNextNodes ? "4" : "")
.attr("marker-end", animatedArrowUrl);

unhighlightNonOutput(root);
highlightSelected(root);

}, [svgRef, d3Graph.nodes, selectedNodeId, highlightNode, highlightLoopOnNode,
highlightEdge, highlightAllNextNodes, highlightSelected]);
highlightEdge, highlightAllNextNodes, highlightSelected, highlightOutputNodes, unhighlightNonOutput]);

const fitOrCenter = useCallback((op: "fit" | "center") => {
if (!width || !height || !zoomRef.current) {
Expand Down

0 comments on commit 464968c

Please sign in to comment.