Skip to content

Draw_Data: Implement new visualizers#3678

Open
elsxyss wants to merge 13 commits intomasterfrom
CP3108_26MH
Open

Draw_Data: Implement new visualizers#3678
elsxyss wants to merge 13 commits intomasterfrom
CP3108_26MH

Conversation

@elsxyss
Copy link

@elsxyss elsxyss commented Mar 18, 2026

Summary

This PR adds 2 new box-and-pointer visualizers for when the draw_data function is ran, so that now there are:

  • Original View (original draw_data rendering)
  • Render Binary Tree (binary tree structure with coloured node groups)
  • Render General Tree (tree structure with coloured node groups, including non-binary trees)

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • This change requires a documentation update
  • Code quality improvements

Added Functionality

  • Added 3 buttons to toggle between the Original View mode, Render Binary Tree mode, and Render General Tree mode
  • Added variable visual space generation for both of the Tree modes, to generate the space required in the data visualizer based on the input tree structure, before generating the input
  • Added specific node spacing algorithms for both of the Tree modes, to generate the input in a tree structure
  • Added sequential colouring to the nodes for both of the Tree modes
  • Added input validation checks for Render Binary Tree mode (valid binary tree input) and Render General Tree mode (valid tree input), and corresponding error messages
  • Adjusted arrow (pointer) generation for all 3 modes (for better alignment with node boxes)

Files Edited

  • Documentation:
    • src/features/dataVisualizer/[Documentation.md](http://documentation.md/)
    • src/features/dataVisualizer/images/BINARY_TREE_IMAGE.png
    • src/features/dataVisualizer/images/GENERAL_TREE_IMAGE.png
    • src/features/dataVisualizer/images/ORIGINAL_VIEW_IMAGE.png
  • Buttons:
    • src/commons/sideContent/content/SideContentDataVisualizer.tsx
  • Tree Drawing:
    • src/features/dataVisualizer/dataVisualizer.tsx
    • src/features/dataVisualizer/dataVisualizerTypes.ts
    • src/features/dataVisualizer/drawable/ArrayDrawable.tsx
    • src/features/dataVisualizer/drawable/ArrowDrawable.tsx
    • src/features/dataVisualizer/tree/ArrayTreeNode.tsx
    • src/features/dataVisualizer/tree/BaseTreeNode.ts
    • src/features/dataVisualizer/tree/DrawableTreeNode.tsx
    • src/features/dataVisualizer/tree/FunctionTreeNode.tsx
    • src/features/dataVisualizer/tree/Tree.tsx
    • src/commons/sagas/WorkspaceSaga/index.ts
  • Spacing:
    • src/features/dataVisualizer/Config.ts

How to Test

Run the following code in the Source Academy playground and open the Data Visualizer:
draw_data(list(1,2,3)): should only give error for “Render Binary Tree” mode.
draw_data([1,2,3]): should only give errors for “Render Binary Tree” mode and “Render General Tree” mode.
draw_data(list(1,list(2,null,null),list(3,null, null))): should not have any error, generates tree structures correctly in any of the 3 modes.

The appropriate box-and-pointer diagram should be generated. Boxes belonging to the same node should be the same colour, while those from the neighbouring nodes should be in different colours. Examples are shown in the documentation file.

Checklist

  • I have tested this code
  • I have updated the documentation

Copy link
Member

@RichDom2185 RichDom2185 left a comment

Choose a reason for hiding this comment

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

Please fix formatting issues and clean up unused code/log statements

Comment on lines +78 to +84
next=next[1];
}
if (count==3){
ans=true
}
return ans&&this.isBinaryTree(structures[0][1]);
}

This comment was marked as outdated.

}
DataVisualizer.isBinTree=this.isBinaryTree(structures);
DataVisualizer.isGenTree=this.isGeneralTree(structures);
this.get_depth(structures[0],0,0);

This comment was marked as outdated.

@elsxyss elsxyss requested a review from RichDom2185 March 18, 2026 09:43
Comment on lines +65 to +75
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]);
}

This comment was marked as outdated.

Comment on lines +155 to +165
DataVisualizer.toggleTreeMode();
}
DataVisualizer.toggleNormalMode();
DataVisualizer.redraw();
}}
>
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<Icon icon="grid-view" />
<Checkbox checked={DataVisualizer.getNormalMode()} style={{ marginTop: 7 }} />
</div>
</AnchorButton>

This comment was marked as outdated.

@coveralls
Copy link

coveralls commented Mar 18, 2026

Pull Request Test Coverage Report for Build 23415869203

Details

  • 20 of 213 (9.39%) changed or added relevant lines in 7 files are covered.
  • 8 unchanged lines in 4 files lost coverage.
  • Overall coverage decreased (-0.6%) to 41.352%

Changes Missing Coverage Covered Lines Changed/Added Lines %
src/features/dataVisualizer/tree/BaseTreeNode.ts 0 2 0.0%
src/features/dataVisualizer/tree/ArrayTreeNode.tsx 0 4 0.0%
src/features/dataVisualizer/drawable/ArrowDrawable.tsx 0 5 0.0%
src/commons/sideContent/content/SideContentDataVisualizer.tsx 1 24 4.17%
src/features/dataVisualizer/dataVisualizer.tsx 17 78 21.79%
src/features/dataVisualizer/tree/Tree.tsx 1 99 1.01%
Files with Coverage Reduction New Missed Lines %
src/features/dataVisualizer/dataVisualizer.tsx 1 17.24%
src/features/dataVisualizer/tree/ArrayTreeNode.tsx 1 0.0%
src/features/dataVisualizer/tree/FunctionTreeNode.tsx 1 0.0%
src/features/dataVisualizer/tree/Tree.tsx 5 0.35%
Totals Coverage Status
Change from base Build 23399236125: -0.6%
Covered Lines: 5783
Relevant Lines: 12920

💛 - Coveralls

}

if (node.children![1] instanceof ArrayTreeNode) {
if (node.children![1].children![0] instanceof ArrayTreeNode) {

This comment was marked as outdated.

Copy link
Member

@RichDom2185 RichDom2185 left a comment

Choose a reason for hiding this comment

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

I personally think this is in violation of OOP principles. Data Visualizer should ideally be a facade that handles the state like currently selected mode, etc. and forwards the calls accordingly.

The actual implementations will belong in two separate classes instead, one for the binary tree stuff and one for the existing implementation. That way we don't have to wrap so much code inside the if-else, and no need for commenting //OriginalView or //TreeNode as everything becomes obvious

Common logic shared across both implementation can live in an abstract class that is subclassed by both.

Thoughts? CC @sayomaki

}
DataVisualizer.isBinTree = this.isBinaryTree(structures);
DataVisualizer.isGenTree = this.isGeneralTree(structures);
this.get_depth(structures[0], 0, 0);

This comment was marked as outdated.

Comment on lines +185 to +188
if (DataVisualizer.getBinTreeMode()) {
DataVisualizer.toggleNormalMode();
}
DataVisualizer.toggleBinTreeMode();

This comment was marked as outdated.

Comment on lines +61 to +64
public static isBinaryTree(structures: Data[]): boolean {
if (structures[0] === null) {
return true;
}

This comment was marked as outdated.

@martin-henz
Copy link
Member

I personally think this is in violation of OOP principles. Data Visualizer should ideally be a facade that handles the state like currently selected mode, etc. and forwards the calls accordingly.

The actual implementations will belong in two separate classes instead, one for the binary tree stuff and one for the existing implementation. That way we don't have to wrap so much code inside the if-else, and no need for commenting //OriginalView or //TreeNode as everything becomes obvious

Common logic shared across both implementation can live in an abstract class that is subclassed by both.

Thoughts? CC @sayomaki

I agree. The team is working on an OOP refactor of this PR.

Comment on lines +66 to +76
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]);
}
Copy link

Choose a reason for hiding this comment

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

Bug: The isBinaryTree function throws a TypeError when given a non-binary-tree structure because it recursively calls itself on undefined without proper validation.
Severity: HIGH

Suggested Fix

Before making a recursive call to isBinaryTree on a child node, add a check to ensure the child is an array. For example, use Array.isArray(structure[1]) and Array.isArray(structure[2]) before proceeding with the recursion.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.

Location: src/features/dataVisualizer/dataVisualizer.tsx#L61-L76

Potential issue: The `isBinaryTree` function recursively calls itself on child nodes
without first verifying that the child structure is a valid array. When provided with a
data structure that is not a binary tree (e.g., a node has a non-array child), the
function will attempt to call `isBinaryTree(undefined)`. This results in a `TypeError:
Cannot read properties of undefined`, causing a crash.

Comment on lines +48 to +58
structures.push(this.nodeCount[depth]);
if (this.nodeCount[depth] > this.longestNodePos) {
this.longestNodePos = this.nodeCount[depth];
}
this.nodeCount[depth]++;
}

this.TreeDepth = Math.max(this.TreeDepth, depth);
this.get_depth(structures[0], depth + 1, 0);
this.get_depth(structures[1], depth, nodePos + 1);
return depth;
Copy link

Choose a reason for hiding this comment

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

Bug: The get_depth function mutates the input dataRecords array. On redraw(), this leads to compounded data corruption and incorrect tree rendering in GeneralTree mode.
Severity: MEDIUM

Suggested Fix

Refactor get_depth to be a pure function. Instead of mutating the input array, it should create a copy of the array before performing any operations, for instance by using the spread syntax [...tree].

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.

Location: src/features/dataVisualizer/dataVisualizer.tsx#L39-L58

Potential issue: In GeneralTree mode, the `get_depth` function mutates the original
user-provided arrays in `dataRecords` by pushing elements onto them. When `redraw()` is
called, this mutation is not correctly reversed, as `tree.pop()` only removes the last
added element. Subsequent calls to `redraw()` will operate on corrupted data, leading to
incorrect tree visualizations as the depth calculation compounds with each redraw.

Comment on lines +255 to +259
static redraw() {
this.isRedraw = true;
this.clear();
DataVisualizer.counter = -DataVisualizer.counter;
return DataVisualizer.dataRecords.map(structures => this.drawData(structures));
Copy link

Choose a reason for hiding this comment

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

Bug: The redraw() method repeatedly appends nodePos values to the data structure in GeneralTree mode, corrupting the data with each redraw.
Severity: MEDIUM

Suggested Fix

Ensure that the redraw() function operates on a clean or deep-copied version of the data for each render, rather than mutating the original data structure passed to it. This will prevent state corruption between redraws.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.

Location: src/features/dataVisualizer/dataVisualizer.tsx#L255-L259

Potential issue: The `redraw()` method in GeneralTree mode corrupts the original data
structures by repeatedly appending `nodePos` values to nodes during each redraw
operation. This mutation persists across redraws, causing the data structure to grow
incorrectly with each call, leading to rendering errors and incorrect visualizations.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants