Skip to content
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
cd1e01f
Draw_Data: Implement new visualizers
elsxyss Mar 18, 2026
b49f0a6
Draw_Data: Implement new visualizers
elsxyss Mar 18, 2026
d85785f
Minor fixes to correct typescript checks
elsxyss Mar 18, 2026
a315027
Minor fixes to remove console logs
elsxyss Mar 18, 2026
05bb175
Minor fixes to remove unused code
elsxyss Mar 18, 2026
58a208c
Minor fixes for formatting and bug
TYS2 Mar 18, 2026
c65e301
Fixed formatting for SideContentDataVisualizer
TYS2 Mar 18, 2026
eb9aac3
Minor fixes to checkbox bug and input validation
TYS2 Mar 18, 2026
2d1c95c
Merge branch 'master' into CP3108_26MH
martin-henz Mar 19, 2026
c0b52bb
Update GenTree mode spacing
elsxyss Mar 20, 2026
7787a4e
Fix for errors
elsxyss Mar 20, 2026
8d19cb3
Merge branch 'master' into CP3108_26MH
martin-henz Mar 20, 2026
495784a
Merge branch 'master' into CP3108_26MH
martin-henz Mar 23, 2026
ac0eddf
OOP Refactoring and GenTree Fixes
elsxyss Mar 25, 2026
daeae3b
Minor fix for errors
elsxyss Mar 25, 2026
8611d1d
Minor fixes for errors
elsxyss Mar 25, 2026
01c2ca3
Removed outdated documentation (in Wiki)
elsxyss Mar 25, 2026
9cfe51e
Fixes as per bug reports
elsxyss Mar 27, 2026
1fa3f15
Further fixes for AI reviews
elsxyss Mar 27, 2026
0fb5d00
Fix lint error
elsxyss Mar 27, 2026
dc6f460
Merge branch 'master' into CP3108_26MH
elsxyss Mar 27, 2026
10893c5
Merge branch 'master' into CP3108_26MH
martin-henz Apr 6, 2026
c98f6f5
made tree checks more complete
TYS2 Apr 6, 2026
e29a4f8
Merge branch 'master' into CP3108_26MH
martin-henz Apr 6, 2026
fd2c700
Merge branch 'master' into CP3108_26MH
martin-henz Apr 6, 2026
3f8a3fa
changed pushing of data to map, simplified toggling of mode
TYS2 Apr 6, 2026
a291d70
removed unused imports
TYS2 Apr 6, 2026
b8e71bd
made binary tree checker more robust"
TYS2 Apr 6, 2026
83f8166
Merge branch 'master' into CP3108_26MH
martin-henz Apr 8, 2026
3872542
fixed bugs
TYS2 Apr 8, 2026
f556faa
fixed formatting
TYS2 Apr 8, 2026
0c1756e
Merge branch 'master' into CP3108_26MH
elsxyss Apr 8, 2026
58156b6
Merge branch 'master' into CP3108_26MH
martin-henz Apr 8, 2026
965dd8e
final
TYS2 Apr 8, 2026
bad97f5
naming conventions
elsxyss Apr 8, 2026
d64a8e6
resolving sentry review
elsxyss Apr 8, 2026
55f27ef
resolving sentry review
elsxyss Apr 8, 2026
585f2bd
resolving sentry review
elsxyss Apr 8, 2026
fafea9a
Merge branch 'master' into CP3108_26MH
martin-henz Apr 8, 2026
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
2 changes: 1 addition & 1 deletion src/commons/sagas/WorkspaceSaga/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +407,7 @@ const WorkspaceSaga = combineSagaHandlers({
* the function.
*/
[WorkspaceActions.beginClearContext.type]: function* (action): any {
yield call([DataVisualizer, DataVisualizer.clear]);
yield call([DataVisualizer, DataVisualizer.clearWithData]);
yield call([CseMachine, CseMachine.clear]);
const globals: Array<[string, any]> = action.payload.library.globals as Array<[string, any]>;
for (const [key, value] of globals) {
Expand Down
70 changes: 69 additions & 1 deletion src/commons/sideContent/content/SideContentDataVisualizer.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { Button, Card, Classes } from '@blueprintjs/core';
import { AnchorButton, Button, Card, Checkbox, Classes } from '@blueprintjs/core';
import { Tooltip } from '@blueprintjs/core';
import { Icon } from '@blueprintjs/core';
import { IconNames } from '@blueprintjs/icons';
import { HotkeyItem } from '@mantine/hooks';
import { bindActionCreators } from '@reduxjs/toolkit';
Expand Down Expand Up @@ -134,6 +136,72 @@ class SideContentDataVisualizerBase extends React.Component<OwnProps & DispatchP
) : (
<DataVisualizerDefaultText />
)}
{this.state.steps.length > 0 && (
<>
<Tooltip content="Original View" position="top">
<AnchorButton
style={{
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center'
}}
onMouseUp={() => {
DataVisualizer.setMode('normal');
DataVisualizer.redraw();
}}
>
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<Icon icon="grid-view" />
<Checkbox checked={DataVisualizer.getNormalMode()} style={{ marginTop: 7 }} />
Comment thread
elsxyss marked this conversation as resolved.
Outdated
</div>
</AnchorButton>
Comment thread
TYS2 marked this conversation as resolved.
Outdated
</Tooltip>
<Tooltip content="Render Binary Tree" position="top">
<AnchorButton
style={{
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
marginLeft: 10
}}
onMouseUp={() => {
DataVisualizer.setMode('binTree');
DataVisualizer.redraw();
}}
>
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<Icon
icon="one-to-many"
style={{ transform: 'rotate(90deg)', marginLeft: 6 }}
/>
<Checkbox checked={DataVisualizer.getBinTreeMode()} style={{ marginTop: 7 }} />
</div>
</AnchorButton>
</Tooltip>
<Tooltip content="Render General Tree" position="top">
<AnchorButton
style={{
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
marginLeft: 10
}}
onMouseUp={() => {
DataVisualizer.setMode('tree');
DataVisualizer.redraw();
}}
>
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<Icon icon="diagram-tree" />
<Checkbox checked={DataVisualizer.getTreeMode()} style={{ marginTop: 7 }} />
</div>
</AnchorButton>
</Tooltip>
</>
)}
</div>
</HotKeys>
);
Expand Down
1 change: 1 addition & 0 deletions src/features/dataVisualizer/Config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export const Config = {
Stroke: 'white',
Fill: 'white',

NWidth: 90,
BoxWidth: 45,
BoxMinWidth: 15, // Set to half of BoxHeight for empty arrays following CseMachineConfig
BoxHeight: 30,
Expand Down
167 changes: 152 additions & 15 deletions src/features/dataVisualizer/dataVisualizer.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import type { JSX } from 'react';
import { Stage } from 'react-konva';

import { Config } from './Config';
import { Data, Step } from './dataVisualizerTypes';
Expand All @@ -15,30 +14,172 @@ import { DataTreeNode } from './tree/TreeNode';
* clear is used by WorkspaceSaga to reset the visualizer after every "Run" button press
*/
export default class DataVisualizer {
private static counter = 1;
Comment thread
elsxyss marked this conversation as resolved.
Outdated
private static empty(step: Step[]) {}
private static setSteps: (step: Step[]) => void = DataVisualizer.empty;
public static dataRecords: Data[] = [];
public static isRedraw = false;
private static _instance = new DataVisualizer();
public static treeMode = false;
public static BinTreeMode = false;
public static normalMode = true;
public static TreeDepth = 0;
public static isBinTree = false;
public static isGenTree = false;
public static nodeCount: number[] = [];
public static nodeColor: number[] = [];
public static longestNodePos: number = 0;
public static colorMap: WeakMap<any, number> = new WeakMap();
public static posMap: WeakMap<any, number> = new WeakMap();

private steps: Step[] = [];
private nodeLabel = 0;
private nodeToLabelMap: Map<DataTreeNode, number> = new Map();

private constructor() {}

public static isBinaryTree(structures: Data[]): boolean {
if (!(structures instanceof Array)) {
return false;
}
if (structures[0] === null) {
return true;
}
Comment thread
TYS2 marked this conversation as resolved.
if (structures[0].length != 2) {
return false;
}
let next = structures[0];
let ans = false;
let count = 0;
while (next instanceof Array) {
count++;
next = next[1];
}
if (count == 3) {
ans = true;
}
return ans && this.isBinaryTree(structures[0][1]);
Comment thread
TYS2 marked this conversation as resolved.
Outdated
}
Comment thread
TYS2 marked this conversation as resolved.
Comment thread
TYS2 marked this conversation as resolved.
Comment thread
TYS2 marked this conversation as resolved.
Comment thread
elsxyss marked this conversation as resolved.

public static isGeneralTree(structures: Data[]): boolean {
if (structures == null || (structures.length == 2 && structures[1] == null)) {
return true;
}
if (!(structures[0] instanceof Array) && structures[1] != null) {
return this.isGeneralTree(structures[1]);
}
if (structures.length != 2 || (!(structures[1] instanceof Array) && structures[1] != null)) {
return false;
}
return this.isGeneralTree(structures[1]) && this.isGeneralTree(structures[0]);
}

public static get_depth(
structures: Data[],
depth: number,
nodePos: number,
newNode: boolean
): number {
Comment thread
elsxyss marked this conversation as resolved.
Outdated
if (!(structures instanceof Array)) {
return 0;
}
// nodeCount keeps track of the current index of nodes at each depth
if (this.getTreeMode()) {
if (this.nodeCount[depth] === undefined) {
this.nodeCount[depth] = 0;
}
this.posMap.set(structures, this.nodeCount[depth]);
if (this.nodeCount[depth] > this.longestNodePos) {
this.longestNodePos = this.nodeCount[depth];
}
this.nodeCount[depth]++;
}
if (this.getBinTreeMode() || this.getTreeMode()) {
if (this.nodeColor[depth] === undefined) {
this.nodeColor[depth] = depth;
}
if (newNode) {
this.nodeColor[depth]++;
}
this.colorMap.set(structures, this.nodeColor[depth]);
}

this.TreeDepth = Math.max(this.TreeDepth, depth);
this.get_depth(structures[0], depth + 1, 0, true);
this.get_depth(structures[1], depth, nodePos + 1, false);
Comment thread
TYS2 marked this conversation as resolved.
Outdated
return depth;
}

public static init(setSteps: (step: Step[]) => void): void {
DataVisualizer.setSteps = setSteps;
}

/**
* Set the visualization mode. This ensures only one mode is active at a time.
* @param mode - 'normal' for original view, 'binTree' for binary tree, 'tree' for general tree
*/
public static setMode(mode: 'normal' | 'binTree' | 'tree'): void {
DataVisualizer.normalMode = mode === 'normal';
DataVisualizer.BinTreeMode = mode === 'binTree';
DataVisualizer.treeMode = mode === 'tree';
}

// RenderBinaryTree
public static toggleBinTreeMode(): void {
DataVisualizer.BinTreeMode = !DataVisualizer.BinTreeMode;
}

// RenderGeneralTree
public static toggleTreeMode(): void {
DataVisualizer.treeMode = !DataVisualizer.treeMode;
}

// OriginalView
public static toggleNormalMode(): void {
DataVisualizer.normalMode = !DataVisualizer.normalMode;
}

public static getBinTreeMode(): boolean {
return DataVisualizer.BinTreeMode;
}

public static getTreeMode(): boolean {
return DataVisualizer.treeMode;
}

public static getNormalMode(): boolean {
return DataVisualizer.normalMode;
}

public static drawData(structures: Data[]): void {
if (!DataVisualizer.setSteps) {
throw new Error('Data visualizer not initialized');
}
if (!DataVisualizer.isRedraw) {
this.dataRecords.push(structures);
}
DataVisualizer.isBinTree = this.isBinaryTree(structures);
DataVisualizer.isGenTree = this.isGeneralTree(structures[0]);
DataVisualizer.nodeCount = [];
DataVisualizer.nodeColor = [];
this.nodeColor[0] = -1;
this.get_depth(structures[0], 0, 0, false);
Comment thread
TYS2 marked this conversation as resolved.
Outdated

DataVisualizer._instance.addStep(structures);
DataVisualizer.setSteps(DataVisualizer._instance.steps);
}

public static clearWithData(): void {
DataVisualizer.longestNodePos = 0;
DataVisualizer.dataRecords = [];
DataVisualizer.isRedraw = false;
DataVisualizer.clear();
}

public static clear(): void {
DataVisualizer._instance = new DataVisualizer();
this.nodeCount = [];
this.TreeDepth = 0;
DataVisualizer.setSteps(DataVisualizer._instance.steps);
}

Expand All @@ -50,36 +191,32 @@ export default class DataVisualizer {
if (this.nodeToLabelMap.has(dataNode)) {
return this.nodeToLabelMap.get(dataNode) ?? 0;
} else {
console.log('*' + this.nodeLabel + ': ' + dataNode.data);
this.nodeToLabelMap.set(dataNode, this.nodeLabel);
return this.nodeLabel++;
}
}

private addStep(structures: Data[]) {
const step = structures.map(this.createDrawing);
const step = structures.map((xs, index) => this.createDrawing(xs, index));
this.steps.push(step);
}

/**
* For student use. Draws a structure by converting it into a tree object, attempts to draw on the canvas,
* Then shift it to the left end.
*/
private createDrawing(xs: Data): JSX.Element {
private createDrawing(xs: Data, key: number): JSX.Element {
const treeDrawer = Tree.fromSourceStructure(xs).draw();

// To account for overflow to the left side due to a backward arrow
// const leftMargin = Config.ArrowMarginHorizontal + Config.StrokeWidth;
const leftMargin = Config.StrokeWidth / 2;

// To account for overflow to the top due to a backward arrow
const topMargin = Config.StrokeWidth / 2;

const layer = treeDrawer.draw(leftMargin, topMargin);
return (
<Stage key={xs} width={treeDrawer.width + leftMargin} height={treeDrawer.height + topMargin}>
{layer}
</Stage>
);
return treeDrawer.draw(leftMargin, topMargin, key);
}

static redraw() {
this.isRedraw = true;
this.clear();
DataVisualizer.counter = -DataVisualizer.counter;
return DataVisualizer.dataRecords.map(structures => this.drawData(structures));
Comment thread
TYS2 marked this conversation as resolved.
Outdated
Comment thread
TYS2 marked this conversation as resolved.
Outdated

Copilot AI Mar 25, 2026

Copy link

Choose a reason for hiding this comment

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

redraw() sets isRedraw = true but never resets it to false. After the first mode toggle, subsequent drawData calls will stop recording into dataRecords (because of the if (!isRedraw) { dataRecords.push(...) } guard), so later mode changes won't be able to redraw newly-rendered structures. Reset isRedraw back to false once the redraw pass completes (and consider removing counter as it is currently unused).

Suggested change
return DataVisualizer.dataRecords.map(structures => this.drawData(structures));
try {
return DataVisualizer.dataRecords.map(structures => this.drawData(structures));
} finally {
this.isRedraw = false;
}

Copilot uses AI. Check for mistakes.
}
}
1 change: 0 additions & 1 deletion src/features/dataVisualizer/dataVisualizerUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ export function isArray(data: Data): data is Array<Data> {
export function isFunction(data: Data): data is Function {
return typeof data === 'function';
}

export function isPair(data: Data): data is Pair {
return is_pair(data);
}
Expand Down
3 changes: 2 additions & 1 deletion src/features/dataVisualizer/drawable/ArrayDrawable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ type ArrayProps = {
nodes: TreeNode[];
x: number;
y: number;
color: string;
};

/**
Expand Down Expand Up @@ -55,7 +56,7 @@ class ArrayDrawable extends React.PureComponent<ArrayProps> {
height={Config.BoxHeight}
strokeWidth={Config.StrokeWidth}
stroke={Config.Stroke}
fill="#17181A"
fill={this.props.color}
preventDefault={false}
/>
{/* Vertical lines in the box */}
Expand Down
Loading
Loading