Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Changes for dashboard #23

Merged
merged 4 commits into from
Jun 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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']);
}
});
};
}
Loading