Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/force graph #117

Open
wants to merge 18 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 17 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .eslintrc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ parserOptions:
ignorePatterns:
- .github
- dist
- __tests__

rules:
curly: error
Expand Down
32 changes: 32 additions & 0 deletions __tests__/force-chart.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import puppeteer from "puppeteer";
import { error } from "console";

let browser = await puppeteer.launch({
headless: true,
});
let page = await browser.newPage();
await page.goto("http://localhost:8080/force-graph.html");

try {
//--silent=false
test("force chart test", async () => {
await page.waitForSelector("togostanza-force-graph");

await page.click("#edge-show_arrows");

const isArrowsHidden = await page.evaluate(() => {
error("hi!");
return document
.querySelector("togostanza-force-graph")
.shadowRoot.querySelector("#force-graph > svg > defs");
});

error(isArrowsHidden);

expect(1).toBe(1);
});
} catch (e) {
error(e);
} finally {
browser.close();
}
130 changes: 75 additions & 55 deletions lib/prepareGraphData.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const nodeLabelSym = Symbol("nodeLabel");
const nodeBorderColorSym = Symbol("nodeBorderColor");
const edgeColorSym = Symbol("edgeColor");
const idSym = Symbol("id");
const isPairEdge = Symbol("pairEdge");

const symbols = {
edgeSym,
Expand All @@ -25,12 +26,15 @@ const symbols = {
nodeLabelSym,
idSym,
nodeBorderColorSym,
isPairEdge,
};

export default function (nodesC, edgesC, params) {
nodesC = JSON.parse(JSON.stringify(nodesC));
edgesC = JSON.parse(JSON.stringify(edgesC));

// find edges wich are pairs and mark them

const {
color,
nodeSizeParams,
Expand All @@ -57,37 +61,29 @@ export default function (nodesC, edgesC, params) {
const nodeHash = {};

const groupHash = {};

nodesC.forEach((node) => {
const groupName = "" + node.group;
groupHash[groupName]
? groupHash[groupName].push(node)
: (groupHash[groupName] = [node]);
if (groupHash[groupName]) {
groupHash[groupName].push(node);
} else {
groupHash[groupName] = [node];
}

nodeHash[node.id] = node;
});

// Edges width
let edgeWidthScale;
if (
edgeWidthParams.basedOn === "data key" &&
edgesC.some(
(edge) =>
edge[edgeWidthParams.dataKey] &&
edgeWidthParams.minWidth &&
edgeWidthParams.maxWidth
)
) {
edgeWidthScale = d3
.scaleLinear()
.domain(d3.extent(edgesC, (d) => d[edgeWidthParams.dataKey]))
.range([edgeWidthParams.minWidth, edgeWidthParams.maxWidth]);
} else {
edgeWidthScale = () => edgeWidthParams.fixedWidth;
}
const edgeWidthScale = getScaleFunc(edgeWidthParams.scale);
edgeWidthScale
.domain(d3.extent(edgesC, (d) => d[edgeWidthParams.dataKey]))
.range([edgeWidthParams.minWidth, edgeWidthParams.maxWidth]);

edgesC.forEach((edge) => {
edge[symbols.edgeWidthSym] = edgeWidthScale(
parseFloat(edge[edgeWidthParams.dataKey]) || edgeWidthParams.minWidth || 1
parseFloat(edge[edgeWidthParams.dataKey])
);

edge[symbols.sourceNodeSym] = nodeHash[edge.source];
edge[symbols.targetNodeSym] = nodeHash[edge.target];
edge[symbols.idSym] = uuidv4();
Expand All @@ -106,10 +102,7 @@ export default function (nodesC, edgesC, params) {
});

// Nodes color
if (
nodeColorParams.basedOn === "data key" &&
nodesC.some((d) => d[nodeColorParams.dataKey])
) {
if (nodesC.some((d) => d[nodeColorParams.dataKey])) {
const nodeColorFunc = color().domain(
[...new Set(nodesC.map((d) => "" + d[nodeColorParams.dataKey]))].sort()
);
Expand All @@ -118,11 +111,12 @@ export default function (nodesC, edgesC, params) {
nodesC.forEach((node) => {
// if data key value is a hex color, use it, else use color ordinal scale provided
if (regex.test(node[nodeColorParams.dataKey])) {
node[symbols.nodeColorSym] = node[nodeColorParams.dataKey];
node[symbols.nodeColorSym] =
node[nodeColorParams.dataKey].toUpperCase();
} else if (typeof node[nodeColorParams.dataKey] !== "undefined") {
node[symbols.nodeColorSym] = nodeColorFunc(
"" + node[nodeColorParams.dataKey]
);
).toUpperCase();
} else {
node[symbols.nodeColorSym] = "black";
}
Expand All @@ -148,48 +142,63 @@ export default function (nodesC, edgesC, params) {
edgesC.forEach((edge) => {
// if data key value is a hex color, use it, else use color ordinal scale provided
if (regex.test(edge[edgeColorParams.dataKey])) {
edge[symbols.edgeColorSym] = edge[edgeColorParams.dataKey];
edge[symbols.edgeColorSym] =
edge[edgeColorParams.dataKey].toUpperCase();
} else if (edge[edgeColorParams.dataKey]) {
edge[symbols.edgeColorSym] = edgeColorFunc(
edge[edgeColorParams.dataKey]
);
} else {
edge[symbols.edgeColorSym] = null;
).toUpperCase();
}
});
} else if (edgeColorParams.basedOn.match(/source|target/i)) {
const wichColor = edgeColorParams.basedOn.match(/source|target/i)[0];
edgesC.forEach((edge) => {
edge[symbols.edgeColorSym] =
edge[symbols[`${wichColor}NodeSym`]][symbols.nodeColorSym];
});
} else {
edgesC.forEach((edge) => {
edge[symbols.edgeColorSym] = null;
edge[symbols[`${wichColor}NodeSym`]][
symbols.nodeColorSym
]?.toUpperCase() || null;
});
}

// ===

// Nodes size
let nodeSizeScale;
if (
nodeSizeParams.basedOn === "data key" &&
nodesC.some((d) => d[nodeSizeParams.dataKey]) &&
nodeSizeParams.minSize &&
nodeSizeParams.maxSize
) {
nodeSizeScale = d3
.scaleLinear()
.domain(d3.extent(nodesC, (d) => d[nodeSizeParams.dataKey]))
.range([nodeSizeParams.minSize, nodeSizeParams.maxSize]);
nodesC.forEach((node) => {
node[symbols.nodeSizeSym] = nodeSizeScale(node[nodeSizeParams.dataKey]);
});
} else {
nodesC.forEach((node) => {
node[symbols.nodeSizeSym] = nodeSizeParams.fixedSize || 4;
});
const nodeSizeScale = getScaleFunc(nodeSizeParams.scale);

nodeSizeScale
.domain(d3.extent(nodesC, (d) => d[nodeSizeParams.dataKey]))
.range([nodeSizeParams.minSize, nodeSizeParams.maxSize]);

nodesC.forEach((node) => {
if (node[nodeSizeParams.dataKey]) {
node[symbols.nodeSizeSym] =
nodeSizeScale(node[nodeSizeParams.dataKey]) || nodeSizeParams.minSize;
} else {
node[symbols.nodeSizeSym] = nodeSizeParams.minSize || 3;
}
});

// Double edges
const checkedIndexes = [];
for (let i = 0; i < edgesC.length; i++) {
if (checkedIndexes.includes(i)) {
continue;
}
const edgeA = edgesC[i];
edgeA[symbols.isPairEdge] = 0;

for (let j = i + 1; j < edgesC.length; j++) {
if (checkedIndexes.includes(j)) {
continue;
}
const edgeB = edgesC[j];

if (edgeB.source === edgeA.target && edgeB.target === edgeA.source) {
// mark that edge as double
edgeA[symbols.isPairEdge] = 1;
edgeB[symbols.isPairEdge] = -1;
checkedIndexes.push(i, j);
}
}
}

return { prepNodes: nodesC, prepEdges: edgesC, nodeHash, groupHash, symbols };
Expand Down Expand Up @@ -245,3 +254,14 @@ export const getGroupPlanes = (groupHash, planeParams) => {

return groupIds.map(getGroupPlane);
};

function getScaleFunc(scaleStr) {
switch (scaleStr) {
case "sqrt":
return d3.scaleSqrt();
case "log10":
return d3.scaleLog();
default:
return d3.scaleLinear();
}
}
Loading