Skip to content

Commit e3a2867

Browse files
committed
Merge branch 'warshalls' into 2024_sem2
Warshall's with newer graph interface, better circular random graphs, get still move nodes
2 parents 02780ed + 5b4cb76 commit e3a2867

File tree

6 files changed

+114
-48
lines changed

6 files changed

+114
-48
lines changed

src/algorithms/controllers/transitiveClosure.js

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ export default {
1919
initVisualisers() {
2020
return {
2121
graph: {
22-
instance: new GraphTracer('key', null, 'Transitive Closure'),
22+
// instance: new GraphTracer('key', null, 'Transitive Closure'),
23+
instance: new GraphTracer('key', null, 'Graph view'),
2324
order: 0
2425
},
2526
// create a separate component for displaying the matrix as a 2D array
@@ -42,22 +43,25 @@ export default {
4243
return out;
4344
},
4445

45-
run(chunker, { matrix, size }) {
46+
run(chunker, { edgeValueMatrix, coordsMatrix, startNode, endNodes, moveNode}) {
4647
// eslint-disable-next-line no-unused-expressions
48+
const matrix = edgeValueMatrix;
49+
const size = matrix.length;
4750
const numOfNodes = size;
4851
const nodes = new Array(numOfNodes);
4952
let prevI = 0;
5053
let prevJ = 0;
5154
let prevK = 0;
52-
chunker.add(1, (g) => {
55+
chunker.add(1, (g, edgeArray, coordsArray) => {
5356
// show kth tag when step back
5457
setKthVisible(true);
5558
g.array.set([...matrix], 'tc');
56-
g.graph.set([...matrix], Array.from({ length: matrix.length }, (v, k) => (k + 1)));
57-
g.graph.layoutCircle();
59+
g.graph.set(edgeArray, Array.from({ length: matrix.length }, (v, k) => (k + 1)),coordsArray);
60+
// g.graph.layoutCircle();
5861
// initialise the matrix in the 'Matrix' component
5962
g.graph.setIstc();
60-
}, [this.graph], [this.array]);
63+
g.graph.moveNodeFn(moveNode);
64+
}, [[...edgeValueMatrix], [...coordsMatrix]]);
6165

6266
for (let i = 0; i < numOfNodes; i++) {
6367
nodes[i] = this.copyArr([...matrix]);

src/algorithms/parameters/TCParam.js

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,56 @@
11
/* eslint-disable no-unused-vars */
22
import React, { useState } from 'react';
3-
import MatrixParam from './helpers/MatrixParam';
3+
// import MatrixParam from './helpers/MatrixParam';
4+
import EuclideanMatrixParams from './helpers/EuclideanMatrixParams';
45
import '../../styles/Param.scss';
56

6-
const DEFAULT_SIZE = 4;
7+
const DEFAULT_SIZE = 4; // gets overwritten by GRAPH_EGS[0] now
8+
const DEFAULT_START = null; // disable
9+
const DEFAULT_END = null; // disable
10+
const DEFAULT_HEUR = null; // disable
11+
const DEFAULT_WEIGHT = 2; // weights "as input"
712
const TRANSITIVE_CLOSURE = 'Transitive Closure';
813
const TRANSITIVE_CLOSURE_EXAMPLE = 'Please follow the example provided: 0,1';
14+
const GRAPH_EGS = [ // XXX think up better examples?
15+
{ name: 'Graph 1',
16+
size: 4,
17+
coords: '10-13,20-13,20-3,10-3',
18+
edges: '1-1,2-2,3-3,4-4,1-2,2-3,3-4'
19+
},
20+
{ name: 'Graph 2',
21+
size: 4,
22+
coords: '10-13,20-13,20-3,10-3',
23+
edges: '1-1,2-2,3-3,4-4,1-3,2-1,3-2,4-3'
24+
},
25+
{ name: 'Graph 3',
26+
size: 5,
27+
coords: '6-9,15-16,20-10,17-2,8-2',
28+
edges: '1-2,2-1,2-3,3-4,4-5,5-3'
29+
}];
930

1031
function TransitiveClosureParam() {
1132
const [message, setMessage] = useState(null);
1233

1334
return (
1435
<>
1536
{/* Matrix input */}
16-
<MatrixParam
37+
<EuclideanMatrixParams
1738
name="transitiveClosure"
1839
mode="tc"
1940
defaultSize={DEFAULT_SIZE}
20-
min={0}
21-
max={1}
41+
defaultStart={DEFAULT_START}
42+
defaultEnd={DEFAULT_END}
43+
defaultWeight = {DEFAULT_WEIGHT}
44+
defaultHeur = {DEFAULT_HEUR}
45+
min={1}
46+
max={49}
47+
graphEgs={GRAPH_EGS}
2248
ALGORITHM_NAME={TRANSITIVE_CLOSURE}
2349
EXAMPLE={TRANSITIVE_CLOSURE_EXAMPLE}
2450
setMessage={setMessage}
51+
symmetric={false}
52+
circular={true}
53+
unweighted
2554
/>
2655

2756
{/* render success/error message */}

src/algorithms/parameters/helpers/EuclideanMatrixParams.js

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,9 @@ function EuclideanMatrixParams({
184184
defaultSize,
185185
defaultStart,
186186
defaultEnd,
187+
// XXX should have defaultWeight = 0 (=Euclidean) defined in the
188+
// graph traversal parameters; included explicitly in Warshall's
189+
defaultWeight = 0, // in case defaultWeight not defined
187190
defaultHeur,
188191
graphEgs,
189192
min,
@@ -195,7 +198,8 @@ function EuclideanMatrixParams({
195198
ALGORITHM_NAME,
196199
EXAMPLE,
197200
EXAMPLE2,
198-
unweighted
201+
unweighted,
202+
circular
199203
}) {
200204

201205
// XXX these get re-evaluated when anything much changes - could
@@ -220,7 +224,7 @@ function EuclideanMatrixParams({
220224
const W_MANHATTAN = 1;
221225
const W_INPUT = 2; // as defined by input
222226
const weightCalcName = ['Euclidean', 'Manhattan', 'As input'];
223-
const [weightCalc, setCalcMethod] = useState(W_EUCLIDEAN);
227+
const [weightCalc, setCalcMethod] = useState(defaultWeight);
224228

225229
// Button toggles Euclidean/Manhattan for heuristic
226230
const HEURCALCMAX = 2; // number of heuristic calculation options
@@ -318,8 +322,8 @@ function EuclideanMatrixParams({
318322
setEndNodes(newEndNodes);
319323
}
320324
if (graphChoice === GRAPHCHOICERAND) {
321-
const edges = makeWeights(newSize, 1, 10, symmetric, unweighted);
322-
const coords = makeXYCoords(newSize, min, max);
325+
const edges = makeWeights(newSize, 1, 10, symmetric, unweighted, circular);
326+
const coords = makeXYCoords(newSize, min, max, circular);
323327
setData1(coords);
324328
setCoordsTxt(getCoordinateList(coords));
325329
setData2(edges);
@@ -346,7 +350,7 @@ function EuclideanMatrixParams({
346350
// to generate new random graphs of any size (thats still a bit
347351
// cumbersome with the current setup)
348352
// XXX maybe we should have a text box for the size instead of + and -
349-
const updateTableSize = (newSize) => {
353+
const updateTableSize = (newSize, circular=false) => {
350354
if (newSize < 1) return;
351355
if (newSize < startNode)
352356
setStartNode(newSize);
@@ -422,7 +426,6 @@ function EuclideanMatrixParams({
422426
// components/DataStructures/Graph/GraphRenderer/index.js) so
423427
// coordinates here can be updated
424428
const moveNode = (nodeID, x, y) => {
425-
console.log(['moveNode', nodeID, x, y]);
426429
const newData1 = data1.map((row, index) => {
427430
if (index === nodeID) {
428431
return {
@@ -730,6 +733,20 @@ function EuclideanMatrixParams({
730733
+
731734
</button>
732735
</div>);
736+
737+
let startButton = '';
738+
if (defaultStart !== null)
739+
startButton =
740+
(<div className="sLineButtonContainer">
741+
<button className="startBtn" onClick={() => updateStartNode(startNode - 1)}>
742+
743+
</button>
744+
<span className='size'>Start: {startNode}</span>
745+
<button className="sizeBtn" onClick={() => updateStartNode(startNode + 1)}>
746+
+
747+
</button>
748+
749+
</div>);
733750

734751
let weightButton = '';
735752
if (!unweighted)
@@ -764,16 +781,7 @@ function EuclideanMatrixParams({
764781
</div>
765782
{weightButton}
766783
{heurButton}
767-
<div className="sLineButtonContainer">
768-
<button className="startBtn" onClick={() => updateStartNode(startNode - 1)}>
769-
770-
</button>
771-
<span className='size'>Start: {startNode}</span>
772-
<button className="sizeBtn" onClick={() => updateStartNode(startNode + 1)}>
773-
+
774-
</button>
775-
776-
</div>
784+
{startButton}
777785
{endButton}
778786
</div>
779787
{endNodeDiv}

src/algorithms/parameters/helpers/ParamHelper.js

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -245,12 +245,18 @@ export const makeWeightsOld = (len, min, max, symmetric, unweighted) => {
245245
// for a large number - could make some adjustments. Could be nice for
246246
// this to have the X-Y coordinates passed in; currently they are done
247247
// independently, which limits things somewhat.
248-
export const makeWeights = (len, min, max, symmetric, unweighted) => {
248+
export const makeWeights = (len, min, max, symmetric, unweighted, circular=false) => {
249249
const rows = [];
250250

251251
// get pseudo-random len*len edge matrix
252252
// Try to get average degree around 3, edges being more likely between
253-
// nodes with similar numbers
253+
// nodes with similar numbers; lower degree for circular graphs
254+
let diffMult = 4; // magic number for random edge generation
255+
let sub = 6; // magic number for random edge generation
256+
if (circular) {
257+
diffMult = 3 ; // larger means fewer edges between close nodes
258+
sub = 0; // smaller means fewer edges in total
259+
}
254260
for (let i = 0; i < len; i += 1) {
255261
let row = [];
256262
let edgeCount = 0;
@@ -269,20 +275,19 @@ export const makeWeights = (len, min, max, symmetric, unweighted) => {
269275
// code did...
270276
val = (symmetric? 0 : 1);
271277
// always have an edge between i and i+1 to make sure graph is
272-
// connected (may not always want this but you can edit graphs
273-
// if desired)
274-
} else if (j == i+1) {
278+
// connected, unless its circular
279+
} else if (j == i+1 && !circular) {
275280
val = (unweighted? 1: getRandomInt(min, max));
276281
// else determine if we want an edge between i and j
277282
// - if i&j differ more we reduce likelihood
278-
} else if (Math.random() < 0.75**(Math.abs(i-j)*3 - 6)) {
283+
} else if (Math.random() < 0.75**(Math.abs(i-j)*diffMult - sub)) {
279284
val = (unweighted? 1: getRandomInt(min, max));
280285
}
281286
if (val > 0) edgeCount++;
282287
row.push(val);
283288
}
284289
// console.log('try ' + tries + 'edgeCount = ' + edgeCount);
285-
} while (tries < 40 && (edgeCount === 0 || edgeCount > 4) && len > 1)
290+
} while (tries < 40 && (edgeCount === 0 && !circular || edgeCount > 4) && len > 1)
286291
// console.log('row' + i + ': ' + row);
287292
rows.push(row);
288293
}
@@ -300,7 +305,10 @@ export const makeWeights = (len, min, max, symmetric, unweighted) => {
300305

301306
// Create len random-ish XY coordinates in range min to max for
302307
// Euclidean graphs
303-
export const makeXYCoords = (len, min, max) => {
308+
export const makeXYCoords = (len, min, max, circular=false) => {
309+
// added circular layout for Warshall's
310+
if (circular)
311+
return makeXYCoordsCircular(len, min, max);
304312
const rows = [];
305313
let arr = [];
306314
let prevX = 0; // keep track of previous 2 X,Y values to reduce close nodes
@@ -348,6 +356,25 @@ export const makeXYCoords = (len, min, max) => {
348356
return arr;
349357
}
350358

359+
// Create len circular-ish XY coordinates for graph in range min to max for
360+
// Warshall's
361+
export const makeXYCoordsCircular = (len, min, max) => {
362+
const unitAngle = (2 * Math.PI) / len;
363+
const radius = (max-min)/6; // height on screen is limited
364+
const midX = (max-min)/2;
365+
const midY = 1 + (max-min)/6; // avoid X axis
366+
let arr = [];
367+
for (let i = 0; i < len; i += 1) {
368+
let data = {};
369+
let x = Math.round(midX + (Math.cos(Math.PI+unitAngle*i) * radius));
370+
let y = Math.round(midY + (Math.sin(Math.PI+unitAngle*i) * radius));
371+
data[`col0`] = `${x}`;
372+
data[`col1`] = `${y}`;
373+
arr.push(data);
374+
}
375+
return arr;
376+
}
377+
351378
// Euclidean distance between two points (rounded up)
352379
// We round up so we can have the choice of admissible and inadmissible
353380
// heuristics in A* more easily (and avoid floating point)

src/components/DataStructures/Graph/GraphRenderer/index.js

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,12 @@ class GraphRenderer extends Renderer {
5555
constructor(props) {
5656
super(props);
5757

58+
// XXX shouldn't rely on this.props.title
59+
// XXX This plus the code for axes and graph layout (eg layoutCircle()
60+
// and code where X-Y coordinates are explicitly given by the user) is
61+
// linked. Some magic numbers were added to shift things around and make
62+
// things look ok. It should be rethought or at least the numbers for
63+
// this.centerX and this.centerY should be put in one place.
5864
if (this.props.title === 'Graph view') {
5965
// Center to new axis origin
6066
// this.centerX = 180;
@@ -83,17 +89,7 @@ class GraphRenderer extends Renderer {
8389
handleMouseMove(e) {
8490
// XXX would be nice to avoid selecting text with reverse video
8591
// as we move the mouse around!
86-
// XXX really shouldn't depend on this.props.title - moving away
87-
// from this but still have a hack for Warshalls
88-
if (this.selectedNode && this.props.title === 'Transitive Closure') {
89-
// XXX Old stuff so Warshall's remains as it was
90-
// Allow mouse movement
91-
const { x, y } = this.computeCoords(e);
92-
const node = this.props.data.findNode(this.selectedNode.id);
93-
node.x = x;
94-
node.y = y;
95-
this.refresh();
96-
} else if (this.selectedNode && this.props.data.moveNode) {
92+
if (this.selectedNode && this.props.data.moveNode) {
9793
// Allow mouse to move nodes (for Euclidean graphs) if
9894
// this.props.data.moveNode function is defined
9995
const { x, y } = this.computeCoords(e);

src/components/DataStructures/Graph/GraphTracer.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -404,8 +404,10 @@ class GraphTracer extends Tracer {
404404
const unitAngle = (2 * Math.PI) / this.nodes.length;
405405
let angle = -Math.PI / 2;
406406
for (const node of this.nodes) {
407-
const x = (Math.cos(angle) * rect.width) / 2;
408-
const y = (Math.sin(angle) * rect.height) / 2;
407+
// XXX see comment about magic numbers in constructor() in
408+
// Graph/GraphRenderer/index.js
409+
const x = 650 - 200 + (Math.cos(angle) * rect.width) / 2;
410+
const y = -200 - 70 + (Math.sin(angle) * rect.height) / 2;
409411
node.x = x;
410412
node.y = y;
411413
angle += unitAngle;

0 commit comments

Comments
 (0)