Skip to content

Commit

Permalink
Changes for dashboard (#23)
Browse files Browse the repository at this point in the history
* Enable passing a parsed dotfile for graph creation

* Handle error when canvas is undefined

* Update global dashboard functions

* Enable dashboard to update token nodes
  • Loading branch information
OliverStolzBO committed Jun 11, 2024
1 parent 3f68e12 commit 66e0fdc
Show file tree
Hide file tree
Showing 4 changed files with 158 additions and 116 deletions.
172 changes: 87 additions & 85 deletions client/src/canvas/draw_legend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,98 +55,100 @@ function drawRoundedRect(

// set the canvas properties
const canvas = document.getElementById('myCanvas') as HTMLCanvasElement;
const width = window.innerWidth;
canvas.width = width;
const scalingFactor = width / 400; // adjust the legend size according to the screen width
canvas.height = 460 * scalingFactor;
const ctx: CanvasRenderingContext2D = canvas.getContext('2d');
if (canvas != null) {
const width = window.innerWidth;
canvas.width = width;
const scalingFactor = width / 400; // adjust the legend size according to the screen width
canvas.height = 460 * scalingFactor;
const ctx: CanvasRenderingContext2D = canvas.getContext('2d');

// style properties for the legend that are found to fit well
//// circles
const circleRadius = 15 * scalingFactor;
const circleX = 30 * scalingFactor;
const orderedCircleColorsAndLabels = [
['--component-started-color', 'Component started'],
['--component-finished-color', 'Component finished'],
['--component-done-color', 'Component done'],
['--statement-passed-color', 'Statement passed'],
['--statement-failed-color', 'Statement failed'],
['--statement-entry-color', 'Loop / Condition entry']
];
//// boxes
const rectX = 17 * scalingFactor;
const rectWidth = 27 * scalingFactor;
const rectBorderRadius = 6 * scalingFactor;
const orderedBoxColorsAndLabels = [
['--task-box-color', 'Task'],
['--service-box-color', 'Service'],
['--condition-box-color', 'Condition'],
['--loop-box-color', 'Loop'],
['--parallel-box-color', 'Parallel']
];
//// descriptionText
const textX = 80 * scalingFactor;
//// entries
const lineWidth = 2 * scalingFactor;
const lineHeight = 40 * scalingFactor;
const objectsHeightDifferenceCircleAndBox = -10 * scalingFactor; // the boxes are bigger, so make the gap between the circle and box smaller
let objectsLineStartY = 30 * scalingFactor;
const fontSize = 27 * scalingFactor;
const textHeightDifferenceCircleAndBox = 4 * scalingFactor; // same as for the objects
let textLineStartY = 39 * scalingFactor;
// style properties for the legend that are found to fit well
//// circles
const circleRadius = 15 * scalingFactor;
const circleX = 30 * scalingFactor;
const orderedCircleColorsAndLabels = [
['--component-started-color', 'Component started'],
['--component-finished-color', 'Component finished'],
['--component-done-color', 'Component done'],
['--statement-passed-color', 'Statement passed'],
['--statement-failed-color', 'Statement failed'],
['--statement-entry-color', 'Loop / Condition entry']
];
//// boxes
const rectX = 17 * scalingFactor;
const rectWidth = 27 * scalingFactor;
const rectBorderRadius = 6 * scalingFactor;
const orderedBoxColorsAndLabels = [
['--task-box-color', 'Task'],
['--service-box-color', 'Service'],
['--condition-box-color', 'Condition'],
['--loop-box-color', 'Loop'],
['--parallel-box-color', 'Parallel']
];
//// descriptionText
const textX = 80 * scalingFactor;
//// entries
const lineWidth = 2 * scalingFactor;
const lineHeight = 40 * scalingFactor;
const objectsHeightDifferenceCircleAndBox = -10 * scalingFactor; // the boxes are bigger, so make the gap between the circle and box smaller
let objectsLineStartY = 30 * scalingFactor;
const fontSize = 27 * scalingFactor;
const textHeightDifferenceCircleAndBox = 4 * scalingFactor; // same as for the objects
let textLineStartY = 39 * scalingFactor;

// draw the legend symbols as coloured circles and rectangles
// draw the legend symbols as coloured circles and rectangles

// draw the circles
for (const [circleColor, circleLabel] of orderedCircleColorsAndLabels) {
// draw the colored circle
ctx.beginPath();
ctx.arc(circleX, objectsLineStartY, circleRadius, 0, 2 * Math.PI);
ctx.fillStyle = getComputedStyle(document.documentElement).getPropertyValue(
circleColor
);
ctx.fill();
ctx.lineWidth = lineWidth;
ctx.stroke();
// draw the circles
for (const [circleColor, circleLabel] of orderedCircleColorsAndLabels) {
// draw the colored circle
ctx.beginPath();
ctx.arc(circleX, objectsLineStartY, circleRadius, 0, 2 * Math.PI);
ctx.fillStyle = getComputedStyle(document.documentElement).getPropertyValue(
circleColor
);
ctx.fill();
ctx.lineWidth = lineWidth;
ctx.stroke();

// draw the label
ctx.font = String(fontSize) + 'px arial';
ctx.fillStyle = '#000000';
ctx.fillText(circleLabel, textX, textLineStartY);
// draw the label
ctx.font = String(fontSize) + 'px arial';
ctx.fillStyle = '#000000';
ctx.fillText(circleLabel, textX, textLineStartY);

// set the start height for the next line
objectsLineStartY += lineHeight;
textLineStartY += lineHeight;
}
// set the start height for the next line
objectsLineStartY += lineHeight;
textLineStartY += lineHeight;
}

// adjust the start height for the next line when switching from circle to box
objectsLineStartY += objectsHeightDifferenceCircleAndBox;
textLineStartY += textHeightDifferenceCircleAndBox;
// adjust the start height for the next line when switching from circle to box
objectsLineStartY += objectsHeightDifferenceCircleAndBox;
textLineStartY += textHeightDifferenceCircleAndBox;

// draw the boxes
for (const [boxColor, boxLabel] of orderedBoxColorsAndLabels) {
ctx.beginPath();
drawRoundedRect(
ctx,
rectX,
objectsLineStartY,
rectWidth,
rectWidth,
rectBorderRadius
);
ctx.fillStyle = getComputedStyle(document.documentElement).getPropertyValue(
boxColor
);
ctx.fill();
ctx.lineWidth = lineWidth;
ctx.stroke();
// draw the boxes
for (const [boxColor, boxLabel] of orderedBoxColorsAndLabels) {
ctx.beginPath();
drawRoundedRect(
ctx,
rectX,
objectsLineStartY,
rectWidth,
rectWidth,
rectBorderRadius
);
ctx.fillStyle = getComputedStyle(document.documentElement).getPropertyValue(
boxColor
);
ctx.fill();
ctx.lineWidth = lineWidth;
ctx.stroke();

// draw the label
ctx.font = String(fontSize) + 'px arial';
ctx.fillStyle = '#000000';
ctx.fillText(boxLabel, textX, textLineStartY);
// draw the label
ctx.font = String(fontSize) + 'px arial';
ctx.fillStyle = '#000000';
ctx.fillText(boxLabel, textX, textLineStartY);

// set the start height for the next line
objectsLineStartY += lineHeight;
textLineStartY += lineHeight;
// set the start height for the next line
objectsLineStartY += lineHeight;
textLineStartY += lineHeight;
}
}
15 changes: 9 additions & 6 deletions client/src/code_visualization/element_creation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { Point } from '../types/point';
* read out the stored HTML data to create nodes, edges and containers to visualize the PFDL program.
* @returns graph elements as valid JSON strings that are accepted by cytoscape
*/
export function getGraphElements() {
export function getGraphElements(parsedDotfile = null) {
// retrieve graph data
const dotfileContent = document.getElementById('graphElementsDiv').innerText;
if (dotfileContent == null || dotfileContent.search('graph') == -1) {
Expand All @@ -23,12 +23,15 @@ export function getGraphElements() {

const containers = buildTreeStructure(treeStructure, dotfileString);

// 3rd party package
const parse = require('dotparser');
const parsedContent = parse(dotfileString)[0];
if (parsedDotfile == null) {
// parse the dotfile here
// 3rd party package
const parse = require('dotparser');
parsedDotfile = parse(dotfileString)[0];
}

// convert into utf-8 string
const [nodes, edges] = parseElementsFromDotFileString(parsedContent);
const [nodes, edges] = parseElementsFromDotFileString(parsedDotfile);
return getFinalGraphElements(nodes, edges, containers);
}

Expand Down Expand Up @@ -159,7 +162,7 @@ const loopTree = (
* @param fileString a JSON string containing the graph objects
* @returns an array containing all existing nodes and edges in the dotfile
*/
const parseElementsFromDotFileString = (
export const parseElementsFromDotFileString = (
fileString: any,
standaloneTesting = false
) => {
Expand Down
4 changes: 2 additions & 2 deletions client/src/code_visualization/graph_creation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ import { createTooltips } from './event_handler';
*/
const getGraphElements = require('./element_creation').getGraphElements;

export function createGraph(cy) {
export function createGraph(cy, parsedDotfile = null) {
// add all graph nodes to cytoscape
const elements = getGraphElements();
const elements = getGraphElements(parsedDotfile);
if (elements == null) {
return false;
}
Expand Down
83 changes: 60 additions & 23 deletions client/src/code_visualization/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,32 +6,69 @@ import { setupCytoscapeInstance } from './setup_cytoscape';
import { setupEventHandlers } from './event_handler';
import { createGraph } from './graph_creation';
import { rotateNodes } from './context_menu';
import { parseElementsFromDotFileString } from './element_creation';

const cy = setupCytoscapeInstance(document.getElementById('cy'));
setupEventHandlers(cy);
const cytoscapeDiv = document.getElementById('cy');
if (cytoscapeDiv) {
const cy = setupCytoscapeInstance(document.getElementById('cy'));
setupEventHandlers(cy);

// create the graph
createGraph(cy);
// create the graph
createGraph(cy, null);

// global function used to update the dashboard in the standalone browser version
(window as any).createCodeVisualization = function (refreshData = true) {
if (refreshData) {
// global function to enable the dashboard in the standalone browser version to access the cytoscape instance
(window as any).getCy = function () {
return cy;
};

// global function used to update the dashboard in the standalone browser version
(window as any).createCodeVisualization = function (parsedDotfile = null) {
cy.elements().remove();
const successfullyCreated = createGraph(cy);
if (!successfullyCreated) {
// no elements drawn
return;
createGraph(cy, parsedDotfile);
const imgElement = document.getElementById('codeVisuImg');
if (imgElement) {
// graph should be displayed as an image, rotate it
rotateNodes(cy.nodes(), cy.nodes('.container'), false);
}
}
};

// global function for the dashboard in the standalone browser version to enable the comparison of two dotfiles
(window as any).getElementsFromDotfile = function (dotfile) {
return parseElementsFromDotFileString(dotfile);
};

// create and display image of the code visualization if the <img> element 'codeVisuImg' exists
const imgElement = document.getElementById('codeVisuImg');
if (imgElement) {
// only exists in standalone browser version (without vscode)
// create img of rotated graph
rotateNodes(cy.nodes(), cy.nodes('.container'), false);
const pngToDownload = cy.png({ full: true });
rotateNodes(cy.nodes(), cy.nodes('.container'), true);
imgElement.setAttribute('src', pngToDownload);
}
};
(window as any).flipTokensForNodeIds = function (nodeIds) {
nodeIds.forEach((id) => {
const node = cy.getElementById(id);
const tokenNode = cy.getElementById(id + '_token');
if (tokenNode.length && tokenNode.inside()) {
// node had a token before, remove it
const newParentId = tokenNode.data('parent');
node.data('label', '');
node.move({ parent: newParentId });
node.classes(['single_node', 'upper_round_node']);
tokenNode.remove();
} else {
if (tokenNode.removed()) {
tokenNode.restore();
} else {
// create a new token node for the object
const newTokenNode = {
group: 'nodes',
classes: ['upper_round_node', 'token_label'],
data: {
id: id + '_token',
label: '',
tippyContent: node.label,
parent: node.parentId
}
};
cy.add(newTokenNode);
}
node.data('label', '•');
node.move({ parent: node.id + '_token' });
node.classes(['single_node', 'token_node']);
}
});
};
}

0 comments on commit 66e0fdc

Please sign in to comment.