提交 9831354d 编写于 作者: J Jason Park 提交者: Jason

Revise GraphData

上级 edf88741
......@@ -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;
}
}
......
......@@ -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) {
......
......@@ -29,7 +29,6 @@ class Data {
}
set() {
this.render();
}
wait() {
......
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
......@@ -8,7 +8,6 @@ class LogData extends Data {
print(message) {
this.messages.push(message);
this.render();
}
}
......
......@@ -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 {
<marker id="markerArrow" markerWidth="4" markerHeight="4" refX="2" refY="2" orient="auto">
<path d="M0,0 L0,4 L4,2 L0,0" className={styles.arrow} />
</marker>
<marker id="markerArrowSelected" markerWidth="4" markerHeight="4" refX="2" refY="2" orient="auto">
<path d="M0,0 L0,4 L4,2 L0,0" className={classes(styles.arrow, styles.selected)} />
</marker>
<marker id="markerArrowVisited" markerWidth="4" markerHeight="4" refX="2" refY="2" orient="auto">
<path d="M0,0 L0,4 L4,2 L0,0" className={classes(styles.arrow, styles.visited)} />
</marker>
</defs>
{
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 (
<g className={classes(styles.edge, visitedCount && styles.visited)} key={`${source}-${target}`}>
<g className={classes(styles.edge, selectedCount && styles.selected, visitedCount && styles.visited)} key={`${source}-${target}`}>
<path d={`M${sx},${sy} L${ex},${ey}`} className={classes(styles.line, directed && styles.directed)} />
{
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 (
<g className={classes(styles.node, visitedCount && styles.visited)} key={id}
<g className={classes(styles.node, selectedCount && styles.selected, visitedCount && styles.visited)} key={id}
transform={`translate(${x},${y})`}>
<circle className={styles.circle} r={nodeRadius} />
<text className={styles.id}>{id}</text>
......
......@@ -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;
}
......
......@@ -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;
}
......
......@@ -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);
}
......
......@@ -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
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册