diff --git a/src/frontend/core/datas/Array1DData.js b/src/frontend/core/datas/Array1DData.js
index 6b7fb5ec3d537ebe05e3399d52520ba13dd360a4..44752c8c8cf0126f54921455649b67cbe7295fc8 100644
--- a/src/frontend/core/datas/Array1DData.js
+++ b/src/frontend/core/datas/Array1DData.js
@@ -7,14 +7,10 @@ class Array1DData extends Array2DData {
this.chartData = null;
}
- render() {
- super.render();
- this.syncChartData();
- }
-
set(array1d = []) {
const array2d = [array1d];
super.set(array2d);
+ this.syncChartData();
}
notify(x, v) {
@@ -39,10 +35,7 @@ class Array1DData extends Array2DData {
}
syncChartData() {
- if (this.chartData) {
- this.chartData.data = this.data;
- this.chartData.render();
- }
+ if (this.chartData) this.chartData.data = this.data;
}
}
diff --git a/src/frontend/core/datas/Array2DData.js b/src/frontend/core/datas/Array2DData.js
index a2feeeb9d1e290b4531756ed8ff1dca96752bad6..9ff3802db422c0f112b8640b8bce9c557860a7dd 100644
--- a/src/frontend/core/datas/Array2DData.js
+++ b/src/frontend/core/datas/Array2DData.js
@@ -21,12 +21,10 @@ class Array2DData extends Data {
notify(x, y, v = this.data[x][y].value) {
this.data[x][y].value = v;
this.data[x][y].notified = true;
- this.render();
}
denotify(x, y) {
this.data[x][y].notified = false;
- this.render();
}
select(sx, sy, ex = sx, ey = sy) {
@@ -35,7 +33,6 @@ class Array2DData extends Data {
this.data[x][y].selected = true;
}
}
- this.render();
}
selectRow(x, sy, ey) {
@@ -52,7 +49,6 @@ class Array2DData extends Data {
this.data[x][y].selected = false;
}
}
- this.render();
}
deselectRow(x, sy, ey) {
diff --git a/src/frontend/core/datas/Data.js b/src/frontend/core/datas/Data.js
index 765224f0806d9e796130e2af8328bcc1693403a1..f6982cb1d946037706777484123193d3754cc7d1 100644
--- a/src/frontend/core/datas/Data.js
+++ b/src/frontend/core/datas/Data.js
@@ -29,7 +29,6 @@ class Data {
}
set() {
- this.render();
}
wait() {
diff --git a/src/frontend/core/datas/GraphData.js b/src/frontend/core/datas/GraphData.js
index c575fe00301ff03f7b017866435c0577b4c7301b..819c8fd3d9bfd2d8bcb11618f9931001b8fbc5c6 100644
--- a/src/frontend/core/datas/GraphData.js
+++ b/src/frontend/core/datas/GraphData.js
@@ -1,5 +1,4 @@
import { Data } from '/core/datas';
-import { GraphTracer } from '/core/tracers';
import { distance } from '/common/util';
import { tracerManager } from '/core';
@@ -24,105 +23,48 @@ class GraphData extends Data {
edgeWeightGap: 4,
};
this.logData = null;
+ this.callLayout = { method: this.layoutCircle, args: [] };
}
- set(array2d = [], layout = GraphTracer.LAYOUT.CIRCLE, root = 0) {
- const { directed, weighted } = this.options;
- const { baseWidth, baseHeight, padding } = this.dimensions;
- this.graph = new Graph([], [], directed);
+ set(array2d = []) {
+ const { weighted } = this.options;
+ this.nodes = [];
+ this.edges = [];
for (let i = 0; i < array2d.length; i++) {
- const id = i;
- const weight = null;
- const visitedCount = 0;
- this.graph.addNode(id, weight, visitedCount);
+ this.addNode(i);
for (let j = 0; j < array2d.length; j++) {
const value = array2d[i][j];
if (value) {
- const source = i;
- const target = j;
- const weight = weighted ? value : null;
- const visitedCount = 0;
- this.graph.addEdge(source, target, weight, visitedCount);
+ this.addEdge(i, j, weighted ? value : null);
}
}
}
- const left = -baseWidth / 2 + padding;
- const top = -baseHeight / 2 + padding;
- const right = baseWidth / 2 - padding;
- const bottom = baseHeight / 2 - padding;
- const width = right - left;
- const height = bottom - top;
- const rect = { left, top, right, bottom, width, height };
- switch (layout) {
- case GraphTracer.LAYOUT.CIRCLE:
- this.graph.layoutCircle(rect);
- break;
- case GraphTracer.LAYOUT.TREE:
- this.graph.layoutTree(rect, root);
- break;
- case GraphTracer.LAYOUT.RANDOM:
- this.graph.layoutRandom(rect);
- break;
- case GraphTracer.LAYOUT.NONE:
- default:
- break;
- }
+ this.layout();
super.set();
}
- visit(target, source, weight) {
- this.visitOrLeave(target, source, weight, true);
- }
-
- leave(target, source, weight) {
- this.visitOrLeave(target, source, weight, false);
+ addNode(id, weight = null, visitedCount = 0, selectedCount = 0, x = 0, y = 0) {
+ if (this.findNode(id)) return;
+ this.nodes.push({ id, weight, visitedCount, selectedCount, x, y });
+ this.layout();
}
- visitOrLeave(target, source, weight, visit) {
- const edge = this.graph.findEdge(source, target);
- if (edge) edge.visitedCount += visit ? 1 : -1;
- const node = this.graph.findNode(target);
- node.weight = weight;
- node.visitedCount += visit ? 1 : -1;
- this.render();
- if (this.logData) {
- this.logData.print(visit ? (source || '') + ' -> ' + target : (source || '') + ' <- ' + target);
- }
+ addEdge(source, target, weight = null, visitedCount = 0, selectedCount = 0) {
+ if (this.findEdge(source, target)) return;
+ this.edges.push({ source, target, weight, visitedCount, selectedCount });
+ this.layout();
}
updateNode(id, update) {
- const node = this.graph.findNode(id);
+ const node = this.findNode(id);
Object.assign(node, update);
- this.render();
- }
-
- log(tracerKey) {
- this.logData = tracerKey ? tracerManager.datas[tracerKey] : null;
- }
-}
-
-class Graph {
- constructor(nodes, edges, directed) {
- this.nodes = nodes;
- this.edges = edges;
- this.directed = directed;
- }
-
- addNode(id, weight, visitedCount, x = 0, y = 0) {
- if (this.findNode(id)) return;
- this.nodes.push({ id, weight, visitedCount, x, y });
- }
-
- addEdge(source, target, weight, visitedCount) {
- if (this.findEdge(source, target)) return;
- this.edges.push({ source, target, weight, visitedCount });
}
findNode(id) {
return this.nodes.find(node => node.id === id);
}
- findEdge(source, target, directed = this.directed) {
+ findEdge(source, target, directed = this.options.directed) {
if (directed) {
return this.edges.find(edge => edge.source === source && edge.target === target);
} else {
@@ -132,7 +74,7 @@ class Graph {
}
}
- findLinkedEdges(source, directed = this.directed) {
+ findLinkedEdges(source, directed = this.options.directed) {
if (directed) {
return this.edges.filter(edge => edge.source === source);
} else {
@@ -140,17 +82,35 @@ class Graph {
}
}
- findLinkedNodeIds(source, directed = this.directed) {
+ findLinkedNodeIds(source, directed = this.options.directed) {
const edges = this.findLinkedEdges(source, directed);
return edges.map(edge => edge.source === source ? edge.target : edge.source);
}
- findLinkedNodes(source, directed = this.directed) {
+ findLinkedNodes(source, directed = this.options.directed) {
const ids = this.findLinkedNodeIds(source, directed);
return ids.map(id => this.findNode(id));
}
- layoutCircle(rect) {
+ getRect() {
+ const { baseWidth, baseHeight, padding } = this.dimensions;
+ const left = -baseWidth / 2 + padding;
+ const top = -baseHeight / 2 + padding;
+ const right = baseWidth / 2 - padding;
+ const bottom = baseHeight / 2 - padding;
+ const width = right - left;
+ const height = bottom - top;
+ return { left, top, right, bottom, width, height };
+ }
+
+ layout() {
+ const { method, args } = this.callLayout;
+ method.apply(this, args);
+ }
+
+ layoutCircle() {
+ this.callLayout = { method: this.layoutCircle, args: arguments };
+ const rect = this.getRect();
const unitAngle = 2 * Math.PI / this.nodes.length;
let angle = -Math.PI / 2;
for (const node of this.nodes) {
@@ -162,7 +122,17 @@ class Graph {
}
}
- layoutTree(rect, root) {
+ layoutTree(root = 0, sorted = false) {
+ this.callLayout = { method: this.layoutTree, args: arguments };
+ const rect = this.getRect();
+
+ if (this.nodes.length === 1) {
+ const [node] = this.nodes;
+ node.x = (rect.left + rect.right) / 2;
+ node.y = (rect.top + rect.bottom) / 2;
+ return;
+ }
+
let maxDepth = 0;
const leafCounts = {};
let marked = {};
@@ -188,6 +158,7 @@ class Graph {
node.x = rect.left + (h + leafCounts[node.id] / 2) * hGap;
node.y = rect.top + v * vGap;
const linkedNodes = this.findLinkedNodes(node.id, false);
+ if (sorted) linkedNodes.sort((a, b) => a.id - b.id);
for (const linkedNode of linkedNodes) {
if (marked[linkedNode.id]) continue;
recursivePosition(linkedNode, h, v + 1);
@@ -198,7 +169,9 @@ class Graph {
recursivePosition(rootNode, 0, 0);
}
- layoutRandom(rect) {
+ layoutRandom() {
+ this.callLayout = { method: this.layoutRandom, args: arguments };
+ const rect = this.getRect();
const placedNodes = [];
for (const node of this.nodes) {
do {
@@ -208,6 +181,47 @@ class Graph {
placedNodes.push(node);
}
}
+
+ visit(target, source, weight) {
+ this.visitOrLeave(target, source, weight, true);
+ }
+
+ leave(target, source, weight) {
+ this.visitOrLeave(target, source, weight, false);
+ }
+
+ visitOrLeave(target, source, weight, visit) {
+ const edge = this.findEdge(source, target);
+ if (edge) edge.visitedCount += visit ? 1 : -1;
+ const node = this.findNode(target);
+ node.weight = weight;
+ node.visitedCount += visit ? 1 : -1;
+ if (this.logData) {
+ this.logData.print(visit ? (source || '') + ' -> ' + target : (source || '') + ' <- ' + target);
+ }
+ }
+
+ select(target, source) {
+ this.selectOrDeselect(target, source, true);
+ }
+
+ deselect(target, source) {
+ this.selectOrDeselect(target, source, false);
+ }
+
+ selectOrDeselect(target, source, select) {
+ const edge = this.findEdge(source, target);
+ if (edge) edge.selectedCount += select ? 1 : -1;
+ const node = this.findNode(target);
+ node.selectedCount += select ? 1 : -1;
+ if (this.logData) {
+ this.logData.print(select ? (source || '') + ' => ' + target : (source || '') + ' <= ' + target);
+ }
+ }
+
+ log(tracerKey) {
+ this.logData = tracerKey ? tracerManager.datas[tracerKey] : null;
+ }
}
export default GraphData;
\ No newline at end of file
diff --git a/src/frontend/core/datas/LogData.js b/src/frontend/core/datas/LogData.js
index 44d1f9ae16a0a2a457f398725d8b7ffed7629013..98bca09392e7f01145253f6476f8d7d5ce13291e 100644
--- a/src/frontend/core/datas/LogData.js
+++ b/src/frontend/core/datas/LogData.js
@@ -8,7 +8,6 @@ class LogData extends Data {
print(message) {
this.messages.push(message);
- this.render();
}
}
diff --git a/src/frontend/core/renderers/GraphRenderer/index.jsx b/src/frontend/core/renderers/GraphRenderer/index.jsx
index 402c0eeee87a782a72a77f6fd2aef1249f6aace0..417bec7280f71a10882332edc995733d4dd3c5b7 100644
--- a/src/frontend/core/renderers/GraphRenderer/index.jsx
+++ b/src/frontend/core/renderers/GraphRenderer/index.jsx
@@ -14,15 +14,16 @@ class GraphRenderer extends Renderer {
handleMouseDown(e) {
super.handleMouseDown(e);
const coords = this.computeCoords(e);
- const { graph, dimensions } = this.props.data;
+ const { nodes, dimensions } = this.props.data;
const { nodeRadius } = dimensions;
- this.selectedNode = graph.nodes.find(node => distance(coords, node) <= nodeRadius);
+ this.selectedNode = nodes.find(node => distance(coords, node) <= nodeRadius);
}
handleMouseMove(e) {
if (this.selectedNode) {
const coords = this.computeCoords(e);
this.props.data.updateNode(this.selectedNode.id, coords);
+ this.refresh();
} else {
super.handleMouseMove(e);
}
@@ -38,7 +39,7 @@ class GraphRenderer extends Renderer {
}
renderData() {
- const { graph, options, dimensions } = this.props.data;
+ const { nodes, edges, options, dimensions } = this.props.data;
const { baseWidth, baseHeight, nodeRadius, arrowGap, nodeWeightGap, edgeWeightGap } = dimensions;
const { directed, weighted } = options;
const viewBox = [
@@ -53,15 +54,18 @@ class GraphRenderer extends Renderer {
+
+
+
{
- graph.edges.sort((a, b) => a.visitedCount - b.visitedCount).map(edge => {
- const { source, target, weight, visitedCount } = edge;
- const { x: sx, y: sy } = graph.findNode(source);
- let { x: ex, y: ey } = graph.findNode(target);
+ edges.sort((a, b) => a.visitedCount - b.visitedCount).map(edge => {
+ const { source, target, weight, visitedCount, selectedCount } = edge;
+ const { x: sx, y: sy } = this.props.data.findNode(source);
+ let { x: ex, y: ey } = this.props.data.findNode(target);
const mx = (sx + ex) / 2;
const my = (sy + ey) / 2;
const dx = ex - sx;
@@ -76,7 +80,7 @@ class GraphRenderer extends Renderer {
}
return (
-
+
{
weighted &&
@@ -90,10 +94,10 @@ class GraphRenderer extends Renderer {
})
}
{
- graph.nodes.map(node => {
- const { id, x, y, weight, visitedCount } = node;
+ nodes.map(node => {
+ const { id, x, y, weight, visitedCount, selectedCount } = node;
return (
-
{id}
diff --git a/src/frontend/core/renderers/GraphRenderer/stylesheet.scss b/src/frontend/core/renderers/GraphRenderer/stylesheet.scss
index a4a2f901c1bd0baef2e772d02cb1bb31da7e2ca6..d4f2331c5a766e5e676f6a8fe7495ce52562290c 100644
--- a/src/frontend/core/renderers/GraphRenderer/stylesheet.scss
+++ b/src/frontend/core/renderers/GraphRenderer/stylesheet.scss
@@ -24,6 +24,13 @@
text-anchor: left;
}
+ &.selected {
+ .circle {
+ fill: $color-selected;
+ stroke: $color-selected;
+ }
+ }
+
&.visited {
.circle {
fill: $color-notified;
@@ -48,6 +55,20 @@
text-anchor: middle;
}
+ &.selected {
+ .line {
+ stroke: $color-selected;
+
+ &.directed {
+ marker-end: url(#markerArrowSelected);
+ }
+ }
+
+ .weight {
+ fill: $color-selected;
+ }
+ }
+
&.visited {
.line {
stroke: $color-notified;
@@ -66,6 +87,10 @@
.arrow {
fill: $color-font;
+ &.selected {
+ fill: $color-selected;
+ }
+
&.visited {
fill: $color-notified;
}
diff --git a/src/frontend/core/renderers/Renderer/index.jsx b/src/frontend/core/renderers/Renderer/index.jsx
index 885062f48da3b54e34ff30687b40321ffd94884f..6ef937ee73eb2cb54b8e55162a6c5aeb69d906a6 100644
--- a/src/frontend/core/renderers/Renderer/index.jsx
+++ b/src/frontend/core/renderers/Renderer/index.jsx
@@ -54,12 +54,6 @@ class Renderer extends React.Component {
data.setOnRender(null);
}
- handleDrag(dx, dy) {
- this.centerX -= dx;
- this.centerY -= dy;
- this.refresh();
- }
-
handleMouseDown(e) {
const { clientX, clientY } = e;
this.lastX = clientX;
@@ -72,7 +66,9 @@ class Renderer extends React.Component {
const { clientX, clientY } = e;
const dx = clientX - this.lastX;
const dy = clientY - this.lastY;
- this.handleDrag(dx, dy);
+ this.centerX -= dx;
+ this.centerY -= dy;
+ this.refresh();
this.lastX = clientX;
this.lastY = clientY;
}
diff --git a/src/frontend/core/tracerManager.jsx b/src/frontend/core/tracerManager.jsx
index 767ca53ef3517f7433cdd5ba657de8cbe068f6e3..afa5c24698a0694545d8b3012ec95752e05bd775 100644
--- a/src/frontend/core/tracerManager.jsx
+++ b/src/frontend/core/tracerManager.jsx
@@ -52,6 +52,7 @@ class TracerManager {
}
render() {
+ Object.values(this.datas).forEach(data => data.render());
if (this.onRender) this.onRender(this.renderers);
}
@@ -166,7 +167,7 @@ class TracerManager {
}
}
- sandboxEval(code){
+ sandboxEval(code) {
const require = moduleName => window.modules[moduleName]; // fake require
eval(code);
}
diff --git a/src/frontend/core/tracers/GraphTracer.js b/src/frontend/core/tracers/GraphTracer.js
index feda89223aee6a4ae48eabfe34cdf59bc1cfd96c..206020df5c78f9cb14c2a8259e225098723317c3 100644
--- a/src/frontend/core/tracers/GraphTracer.js
+++ b/src/frontend/core/tracers/GraphTracer.js
@@ -5,19 +5,18 @@ class GraphTracer extends Tracer {
super(title, options);
this.register(
+ 'addNode',
+ 'addEdge',
+ 'layoutCircle',
+ 'layoutTree',
+ 'layoutRandom',
'visit',
'leave',
+ 'select',
+ 'deselect',
'log',
);
}
}
-GraphTracer.LAYOUT = {
- CIRCLE: 'circle',
- TREE: 'tree',
- RANDOM: 'random',
- NONE: 'none',
- // FORCE_DIRECTED: 'force_directed',
-};
-
export default GraphTracer;
\ No newline at end of file