Skip to content

Commit

Permalink
pack based initial position + more layout simulation controls
Browse files Browse the repository at this point in the history
  • Loading branch information
Antriel committed Dec 17, 2024
1 parent 758d3dc commit 163306a
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 34 deletions.
35 changes: 23 additions & 12 deletions src/config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ import GUI from 'lil-gui';
export const config = JSON.parse(localStorage?.getItem('haxeDepsCfg') ?? '{}');
export let onConfigChanged = {};
config.visualDependencies ??= true;
config.visualSize ??= 'dependenciesRec';
config.visualSizeMin ??= 1;
config.visualSize ??= 'dependencies';
config.visualSizeMin ??= 3;
config.visualSizeMax ??= 15;
config.visualAllPaths ??= true;
config.visualCycles ??= true;
config.visualLabelsDensity ??= 1;
config.layoutEnable ??= true;
config.layoutInit ??= 'bubble';
config.layoutForces ??= true;
config.layoutForcesRelative ??= true;
config.layoutPackageForces ??= 1;
Expand Down Expand Up @@ -66,16 +68,6 @@ visual.add(config, 'visualAllPaths').name('show all possible paths of active nod
visual.add(config, 'visualCycles').name('show cyclic paths in red');
visual.add(config, 'visualLabelsDensity', 0.1, 3).name('labels density');

const layout = visual.addFolder('Layout/Simulation (Quite Fiddly...)').close();
layout.add(config, 'layoutForces').name('apply forces based on links');
layout.add(config, 'layoutForcesRelative').name('forces relative to link count');
layout.add(config, 'layoutPackageForces', 0, 3, 0.001).name('same package force');
layout.add(config, 'layoutForcePower', 0, 3, 0.001).name('force power');
layout.add(config, 'layoutForceSlowdown', 1, 100, 0.001).name('slowdown');
// TODO stop sim button.
// TODO initial layout config options.
// layout.open().parent.open().parent.open();

const exclusions = gui.addFolder('Exclusions').close();
exclusions.add(config, 'hideStd').name('hide Haxe Std library');
exclusions.add(config, 'hideImport').name('hide import.hx');
Expand Down Expand Up @@ -119,4 +111,23 @@ labels.add(config, 'smartLabelsPrefix').name('remove common prefix');
labels.add(config, 'smartLabelsShowMacro').name('mark macros in labels');
addCustomStringValues(labels, 'Custom Smart Labels (Use Capture Group)', config.smartLabelsCustom);

const layout = gui.addFolder('Layout/Simulation (Quite Fiddly...)').close();
layout.add(config, 'layoutEnable').name('enable simulation');
layout.add(config, 'layoutInit', {
'packages bubble chart': 'bubble',
'circular chart': 'circle',
'top-down': 'topdown',
}).name('initial position algorithm');
layout.add({
reset: () => {
onConfigChanged.reset?.();
}
}, 'reset').name('reset initial positions');
layout.add(config, 'layoutForces').name('apply forces based on links');
layout.add(config, 'layoutForcesRelative').name('forces relative to link count');
layout.add(config, 'layoutPackageForces', 0, 3, 0.001).name('same package force');
layout.add(config, 'layoutForcePower', 0, 3, 0.001).name('force power');
layout.add(config, 'layoutForceSlowdown', 1, 100, 0.001).name('slowdown');
layout.open().parent.open();

// TODO load state of the GUI?
62 changes: 41 additions & 21 deletions src/createGraph.mjs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import Graph from "graphology";
import { circular } from 'graphology-layout';
import circular from 'graphology-layout/circular';
import circlepack from 'graphology-layout/circlepack';
import forceAtlas2 from "graphology-layout-forceatlas2";
import FA2Layout from 'graphology-layout-forceatlas2/worker';
import randomColor from "randomcolor";

let layout;
let abort;
let idCounter = 0;
let lastLayoutInit;
const nodeIds = new Map();
const nodeAtts = new Map();
const edgeAtts = new Map();
Expand All @@ -30,6 +32,8 @@ const labelHaxelib = [
* @property {boolean} visualAllPaths
* @property {boolean} visualCycles
* @property {number} visualLabelsDensity
* @property {boolean} layoutEnable
* @property {'bubble'|'circle'|'topdown'} layoutInit
* @property {boolean} layoutForces
* @property {boolean} layoutForcesRelative
* @property {number} layoutPackageForces
Expand Down Expand Up @@ -131,6 +135,7 @@ export default function createGraph(deps, config) {

const graph = new Graph({ /* multi: true */ });
const posGraph = new Graph({ multi: true });
graph.setAttribute('posGraph', posGraph);

function add(node) {
if (!graph.hasNode(node.path)) {
Expand Down Expand Up @@ -191,32 +196,28 @@ export default function createGraph(deps, config) {
const maxRadius = Math.max(config.visualSizeMin, config.visualSizeMax);
for (const { attributes } of graph.nodeEntries())
attributes.size = minRadius + (attributes.degree / maxDegree) * (maxRadius - minRadius);

const pos = circular(graph);
for (const { attributes, node } of graph.nodeEntries()) {
attributes.size = minRadius + (attributes.degree / maxDegree) * (maxRadius - minRadius);
if (typeof attributes.y === 'undefined') {
// TODO x based on packs?
const { x, y } = pos[node];
attributes.x = x;
attributes.y = y;
attributes.prevX = attributes.x
attributes.prevY = attributes.y;
}
}
graph.setAttribute('maxDegree', maxDegree);
graph.setAttribute('minRadius', minRadius);
graph.setAttribute('maxRadius', maxRadius);

// Add edges between types within the same pack, for better position grouping.
const packToNodes = new Map();
let maxPackCount = 0;
for (const node of deps.keys()) {
const pack = node.label.split('/');
pack.pop();
maxPackCount = Math.max(maxPackCount, pack.length);
const atts = graph.getNodeAttributes(node.path);
for (let i = 0; i < pack.length; i++) atts['pack' + i] = pack[i];
node.packs = pack.slice();
while (pack.length) {
const packStr = pack.join('/');
if (!packToNodes.has(packStr)) packToNodes.set(packStr, []);
packToNodes.get(packStr).push(node);
pack.pop();
}
}
graph.setAttribute('maxPackCount', maxPackCount);
function setColors(color) {
for (const node of color.nodes) {
if (graph.hasNode(node.path))
Expand Down Expand Up @@ -261,6 +262,31 @@ export default function createGraph(deps, config) {
}
}
}
// // Drop pack edges.
// for (const edge of edges) graph.dropEdge(edge);

setGraphPositions(graph, config, lastLayoutInit != config.layoutInit);
return graph;
}

export function setGraphPositions(graph, config, reset = false) {
lastLayoutInit = config.layoutInit;
const { maxDegree, minRadius, maxRadius, maxPackCount, posGraph } = graph.getAttributes();
let pos;
if (config.layoutInit === 'bubble') pos = circlepack(graph, {
hierarchyAttributes: new Array(maxPackCount).fill(null).map((_, i) => 'pack' + i),
});
else if (config.layoutInit === 'circle') pos = circular(graph);
for (const { attributes, node } of graph.nodeEntries()) {
attributes.size = minRadius + (attributes.degree / maxDegree) * (maxRadius - minRadius);
if (reset || (typeof attributes.y === 'undefined')) {
const { x, y } = pos?.[node] ?? { x: 10, y: attributes.size * 60 };
attributes.x = x;
attributes.y = y;
attributes.prevX = attributes.x
attributes.prevY = attributes.y;
}
}

const settings = forceAtlas2.inferSettings(posGraph);
// settings.adjustSizes = true;
Expand Down Expand Up @@ -289,11 +315,5 @@ export default function createGraph(deps, config) {
if (isIdle) layout.stop();
}, { signal: abort.signal });

layout.start();
// TODO also add a button toggle for starting stopping the thing.

// // Drop pack edges.
// for (const edge of edges) graph.dropEdge(edge);

return graph;
if (config.layoutEnable) layout.start();
}
6 changes: 5 additions & 1 deletion src/renderer.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { bidirectional, singleSourceLength } from 'graphology-shortest-path/unwe
import { edgePathFromNodePath } from "graphology-shortest-path";
import Sigma from "sigma";
import { parse } from "./parser.mjs";
import createGraph from "./createGraph.mjs";
import createGraph, { setGraphPositions } from "./createGraph.mjs";
import { config, dataVal, onConfigChanged, refresh, syncData } from './config.mjs';
const searchInput = document.getElementById("search-input");
const searchSuggestions = document.getElementById("suggestions");
Expand Down Expand Up @@ -34,6 +34,10 @@ onConfigChanged.run = () => {
.map((node) => `<option value="${graph.getNodeAttribute(node, "label")}"></option>`)
.join("\n");
};
onConfigChanged.reset = () => {
setGraphPositions(graph, config, true);
sigma.refresh({ skipIndexation: true });
}


const sigma = new Sigma(graph, document.getElementById("container"), {
Expand Down

0 comments on commit 163306a

Please sign in to comment.