Skip to content
This repository has been archived by the owner on Oct 20, 2023. It is now read-only.

Commit

Permalink
GRAPH 1: Layout improvements (#116)
Browse files Browse the repository at this point in the history
* get the graph dev mode running again

* remove Text console log again

* .gitignore packages/graph/testData

* update graph TODOs

* graph dev mode will start without test data

* fix linting errors

* update forceClampToRadius

* better circle area calculations

* type forceClampToRadius

* split HierarchicalGraphRenderer constructor into initializeForces and initializeSelection

* refactor {method}CallAllChildren into callChildrenRecursively('method')

* GraphHandler useForces

* Update gt.redeye file.

* Update timeline and mult-command tests for failures.

---------

Co-authored-by: James Bradford <[email protected]>
Co-authored-by: Courtney Carpenter <[email protected]>
  • Loading branch information
3 people authored Apr 7, 2023
1 parent 751d9d5 commit 98e64a2
Show file tree
Hide file tree
Showing 12 changed files with 231 additions and 172 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ node_modules
dist
build
@redeye/darwin-x64
packages/graph/testData
applications/server/dev-databases
applications/server/campaign
.eslintcache
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"start:client": "yarn moon run @redeye/client:start-dev",
"start:server": "yarn moon run @redeye/server:start-dev",
"start:dev": "yarn moon run @redeye/server:start-dev @redeye/client:start-dev",
"start:graph": "yarn moon run @redeye/graph:start-dev",
"start:graph": "yarn moon run @redeye/graph:start-vite",
"combine:reports": "jrm dist/applications/redeye-e2e/results/combined-report.xml \"dist/applications/redeye-e2e/results/*.xml\"",
"cy:open": "yarn moon run @redeye/e2e:open-cy",
"cy:open-blue": "yarn moon run @redeye/e2e:open-cy-blue",
Expand Down
8 changes: 1 addition & 7 deletions packages/graph/TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
- –––––––––––––––––––––––––––––––––––

- CLIENT

- editing host/beacon/server name calls graphHandler.updateNodeName()
- patterned bg for lifecycle rather than only grayscale?
- 'timeless' / 'all time' mode to not show any time states
Expand All @@ -17,28 +16,25 @@
- persist pinned layout
- methods for auto-layout
- method to always expand hosts
- TIME STATE

- TIME STATE
- preview nodes when they change in the time state
- Maybe: preview all active? or just expand superNodes when there are active nodes?

- LAYOUT: SERVER RENDERING

- with no group or sub nodes
- host acts as node when interacted with...
- possibly as its own ServerGraphRenderer
- as a different shape?
- rename isServer to isSingle or something agnostic

- CUSTOMIZE VISUAL

- need UX mockups for this
- should visuals be attached to comments or to graph entities?
- icons for text labels
- colors for nodes? outlines possibly? additional bonus circle within to style

- ALTERNATE LAYOUTS

- time based simulation layout - for presentation mode
- option to prioritize nodes based on presentation mode focus...
- swarm plot
Expand All @@ -47,13 +43,11 @@
- give it special rendering and layout - possibly outside all other layout?

- UPDATE DATA

- update with new dataset that has overlap with the old
- test method to remove remove a host, beacon, whatever by ids
- method to remove a host, beacon, whatever by id?

- LINK INTERACTION

- preview and selection
- labels
- subdue all others while preview or selecting
Expand Down
32 changes: 15 additions & 17 deletions packages/graph/index.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,20 @@
import { json } from 'd3';
import { RedEyeGraph, getServers } from './src/index.ts';

export const testGraph = (svgElementId) => {
const randomGraphData = getServers(75, 5);
json('./testData/data1.json').then((rawGraphData) => {
window.graph = new RedEyeGraph({
// onSelectionChange: (node) => console.log(node?.id),
// onPreviewChange: (node) => console.log(node),
// graphData: randomGraphData,
graphData: {
...rawGraphData.graph,
parents: Object.keys(rawGraphData.hosts).map((hostId) => ({
name: rawGraphData.hosts[hostId].hostName,
id: hostId,
})),
},
element: document.getElementById('app'),
});
window.addEventListener('resize', () => window.graph.resize());
const getGraphData = async () => {
try {
return await json('./testData/large.json');
} catch {
return getServers(75, 5);
}
};

export const testGraph = async (svgElementId) => {
const graphData = await getGraphData();
window.graph = new RedEyeGraph({
graphData,
element: document.getElementById(svgElementId),
});
window.addEventListener('resize', () => window.graph.resize());
console.log(window.graph);
};
4 changes: 2 additions & 2 deletions packages/graph/src/GraphData/hierarchicalGraphDataParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ function addSiblingLink(
) {
const source = sourceNode.id!;
const target = targetNode.id!;
const parentNode = sourceNode.parent?.id!; // also targetNode.parent.id
const parentNode = sourceNode.parent!.id!; // also targetNode.parent.id
const siblingLinkId = createLinkId(source, target);
setMapKeyIfUnset(
linkMap,
Expand Down Expand Up @@ -259,7 +259,7 @@ function addParentLink(
id: parentLinkId,
parent: linkId,
type: 'parentLink',
parentNode: node.parent?.id!,
parentNode: node.parent!.id!,
})
)?.baseLinks.push(baseLink.id);

Expand Down
9 changes: 8 additions & 1 deletion packages/graph/src/GraphData/types.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { HierarchyNode, Selection, SimulationLinkDatum, SimulationNodeDatum, ZoomTransform } from 'd3';
import { HierarchyNode, Selection, Simulation, SimulationLinkDatum, SimulationNodeDatum, ZoomTransform } from 'd3';
import {
BaseLink,
HierarchicalGraphBaseLink,
Expand Down Expand Up @@ -103,6 +103,13 @@ export type GraphZoomTransform = ZoomTransform & {
rk: number;
};

export type HierarchicalSimulation = Simulation<HierarchicalGraphNode, HierarchicalGraphLinkDatum>;
export type HierarchicalSimulationForce = {
name: string;
force: Force<HierarchicalGraphNode, HierarchicalGraphLinkDatum>;
optional?: boolean;
};

export type NodeOrLink = HierarchicalGraphNode | HierarchicalGraphLink;

export type GraphDataEvent<Item = NodeOrLink> = (node?: HierarchicalGraphNode, selectedItems?: Item[]) => void;
17 changes: 12 additions & 5 deletions packages/graph/src/GraphHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,17 +101,17 @@ export class GraphHandler {
}

private _onSelectionChange: HierarchicalGraphData['onSelectionChange'] = (...args) => {
this.graphRoot.drawInteractionAllChildren();
this.graphRoot.callChildrenRecursively('drawInteraction');
this.drawTextOcclusion();
this.onSelectionChange(...args);
};
private _onPreviewChange: HierarchicalGraphData['onPreviewChange'] = (...args) => {
this.graphRoot.drawInteractionAllChildren();
this.graphRoot.callChildrenRecursively('drawInteraction');
this.drawTextOcclusion();
this.onPreviewChange(...args);
};
private _onTimeChange: HierarchicalGraphData['onTimeChange'] = () => {
this.graphRoot.drawTimeAllChildren();
this.graphRoot.callChildrenRecursively('drawTime');
this.drawTextOcclusion();
this.onTimeChange();
};
Expand All @@ -134,7 +134,7 @@ export class GraphHandler {

const zoomed = () => {
this.graphRoot.freeze(); // for draw performance...
this.graphRoot.drawLayoutAllChildren();
this.graphRoot.callChildrenRecursively('drawLayout');
// drawDotGrid() may decrease draw performance
// drawDotGrid(this.zoomTransform);
};
Expand Down Expand Up @@ -319,7 +319,14 @@ export class GraphHandler {
const _node = typeof node === 'string' ? this.graphData.allNodes.get(node) : node;
if (!_node) return;
_node.data.name = newName;
this.graphRoot.drawUpdateLabelAllChildren();
this.graphRoot.callChildrenRecursively('drawUpdateLabel');
}

useGraphForces() {
this.graphRoot.callChildrenRecursively('useGraphForces');
}
useSimpleForces() {
this.graphRoot.callChildrenRecursively('useSimpleForces');
}

static scaleRadius = (zk: number) => Math.min(zk, (zk - 1) * 0.3 + 1);
Expand Down
59 changes: 32 additions & 27 deletions packages/graph/src/GraphRenderers/GroupGraphRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import {
forceLink as d3ForceLink,
forceCollide as d3ForceCollide,
forceManyBody as d3ForceManyBody,
forceSimulation as d3ForceSimulation,
forceX as d3ForceX,
forceY as d3ForceY,
} from 'd3';
Expand All @@ -15,6 +14,8 @@ import {
shortenLine,
translateCenter,
isInteractionFocus,
circleArea,
circleRadius,
} from './layout-utils';
import { HierarchicalGraphLink, HierarchicalGraphNode } from '../GraphData/types';
import { defNum } from '../utils';
Expand All @@ -23,7 +24,10 @@ import { defNum } from '../utils';
export class GroupGraphRenderer extends HierarchicalGraphRenderer {
constructor(props: GraphHierarchicalConstructorProps) {
super(props);
super.initialize(SubGraphRenderer, true);
}

initializeForces() {
this.nodes.forEach((d) => (d.r = d.type === 'keyNode' ? GroupGraphRenderer.radius(d) : 1.5));

const forceNode = d3ForceManyBody<HierarchicalGraphNode>().strength((d) => {
Expand All @@ -34,31 +38,32 @@ export class GroupGraphRenderer extends HierarchicalGraphRenderer {
// .strength(d => d.group ? 0.2 : 0.05)
.strength((d) => d.source.graphLinks.length / 100)
.distance(
// @ts-ignore
this.keyNodes.length < 2
? this.rootNode.r!
: (d: HierarchicalGraphLink) =>
//
this.rootNode.r! / this.links.length + (d.source.r || 1)
? () => this.rootNode.r!
: (d: HierarchicalGraphLink) => this.rootNode.r! / this.links.length + (d.source.r || 1)
);
const forceClamp = forceClampToRadius<HierarchicalGraphNode>((d) =>
d.type === 'keyNode' ? d.parent!.r! - d.r! - 2 : undefined
);
const forceCollide = d3ForceCollide<HierarchicalGraphNode>().radius((d): number =>
d.type === 'keyNode' ? d.r! + 2 : 0
d.type === 'keyNode' ? d.parent!.r! - d.r! - 2 : 0
);
const forceCollide = d3ForceCollide<HierarchicalGraphNode>()
.strength(0.5)
.radius((d): number => (d.type === 'keyNode' ? d.r! + 2 : 0));
const forcePositionParentLinkNodes = () => positionParentLinkNodes(this.rootNode);

this.simulation = d3ForceSimulation(this.nodes)
.force('positionParentLinkNodes', forcePositionParentLinkNodes)
.force('link', forceLink)
.force('charge', forceNode)
.force('collide', forceCollide)
.force('x', d3ForceX(0).strength(0.05))
.force('y', d3ForceY(0).strength(0.05))
.force('clamp', forceClamp)
.on('tick', this.drawLayout.bind(this));
const optional = true;
this.simulationForces = [
{ name: 'positionParentLinkNodes', force: forcePositionParentLinkNodes },
{ name: 'link', force: forceLink, optional },
{ name: 'charge', force: forceNode, optional },
{ name: 'x', force: d3ForceX(0).strength(0.05), optional },
{ name: 'y', force: d3ForceY(0).strength(0.05), optional },
{ name: 'collide', force: forceCollide },
{ name: 'clamp', force: forceClamp },
];
super.initializeForces();
}

initializeSelection() {
this.rootGroupSelection = this.rootSelection
.data([this.rootNode])
.append('g')
Expand All @@ -73,7 +78,6 @@ export class GroupGraphRenderer extends HierarchicalGraphRenderer {
.attr('class', (d) => (d.type === 'siblingLink' ? classNames.siblingLink : classNames.parentLink));

this.childGraphRootSelection = this.rootGroupSelection
//
.append('g')
.selectAll('g')
.data(this.nodes)
Expand All @@ -88,9 +92,7 @@ export class GroupGraphRenderer extends HierarchicalGraphRenderer {
.attr('class', (d) => (d.type === 'parentLinkNode' ? classNames.parentLinkNode : classNames.keyNode))
.classed(classNames.groupNode, true);

this.hideLayout(); // start hidden
// super.initialize();
super.initialize(SubGraphRenderer);
super.initializeSelection();
}

drawLayout() {
Expand Down Expand Up @@ -128,9 +130,12 @@ export class GroupGraphRenderer extends HierarchicalGraphRenderer {
}

static radius = (d: HierarchicalGraphNode): number => {
const childNeededRadius = 4;
const childCount = d.children ? d.children.filter((d) => d.type === 'keyNode').length : 1;
const areaNeeded = Math.pow(childNeededRadius, 2) * Math.PI * childCount;
return Math.sqrt(areaNeeded / Math.PI);
const keyNodeChildren = d.children?.filter((d) => d.type === 'keyNode') || [];
let areaNeeded = 0;
for (let i = 0; i < keyNodeChildren.length; i++) {
const keyNodeChild = keyNodeChildren[i];
areaNeeded += circleArea(SubGraphRenderer.radius(keyNodeChild) + 3);
}
return circleRadius(areaNeeded);
};
}
Loading

0 comments on commit 98e64a2

Please sign in to comment.