Newer
Older
import ManagedData from "./manageddata";
import { PLUGIN_PATH, COLOR_PALETTE } from "../../config";
export const NODE_LABEL = "name";
export const NODE_ID = "id";
export const NODE_TYPE = "type";
export const NODE_DESCRIPTION = "description";
export const NODE_IMAGE = "image";
export const NODE_REFERENCES = "references";
export const NODE_VIDEOS = "videos";
export const LINK_SOURCE = "source";
export const LINK_TARGET = "target";
export const LINK_TYPE = "type";
export const LINK_PARTICLE_COUNT = 4;
export const GRAPH_NODES = "nodes";
export const GRAPH_LINKS = "links";
export const IMAGE_SIZE = 12;
export const IMAGE_SRC = PLUGIN_PATH + "datasets/images/";
export const LINK_PARAMS = [LINK_TYPE];
export const NODE_PARAMS = [
NODE_ID,
NODE_LABEL,
NODE_IMAGE,
NODE_DESCRIPTION,
NODE_REFERENCES,
NODE_VIDEOS,
NODE_TYPE,
];
export const LINK_SIM_PARAMS = ["index"];
export const NODE_SIM_PARAMS = ["index", "x", "y", "vx", "vy", "fx", "fy"]; // Based on https://github.com/d3/d3-force#simulation_nodes
export const JSON_CONFIG = PLUGIN_PATH + "datasets/aud1v2.json";
export const STOP_PHYSICS_DELAY = 5000; // ms
export class Graph extends ManagedData {
constructor(data) {
super(Graph.addIdentifiers(data));
this.calculateLinkTypes();
this.onChangeCallbacks = [];
}
triggerOnChange() {
this.onChangeCallbacks.forEach((fn) => fn(this.data));
}
onRedo() {
this.triggerOnChange();
}
onUndo() {
this.triggerOnChange();
}
/**
* Based on the function from the 3d-graph code from @JoschaRode
*/
calculateLinkTypes() {
const linkClasses = [];
this.data[GRAPH_LINKS].forEach((link) =>
linkClasses.push(link[LINK_TYPE])
);
this.linkTypes = [...new Set(linkClasses)];
}
getLinkColor(link) {
}
getLinkTypeColor(linkClass) {
var classIndex = this.linkTypes.indexOf(linkClass);
return COLOR_PALETTE[classIndex % COLOR_PALETTE.length];
}
this.data[GRAPH_NODES] = this.data[GRAPH_NODES].filter(
(n) => n[NODE_ID] !== nodeId
);
// Delete links with node
this.data[GRAPH_LINKS] = this.data[GRAPH_LINKS].filter(
(l) =>
l[LINK_SOURCE][NODE_ID] !== nodeId &&
l[LINK_TARGET][NODE_ID] !== nodeId
);
this.storeCurrentData("Deleted node with id [" + nodeId + "]");
}
static addIdentifiers(data) {
data[GRAPH_NODES].forEach((n) => {
deleteLink(sourceId, targetId) {
// Only keep links, of one of the nodes is different
this.data[GRAPH_LINKS] = this.data[GRAPH_LINKS].filter(
(l) =>
l[LINK_SOURCE][NODE_ID] !== sourceId ||
l[LINK_TARGET][NODE_ID] !== targetId
);
this.storeCurrentData(
"Deleted link connecting [" + sourceId + "] with [" + targetId + "]"
);
}
isLinkOnNode(link, node) {
if (link === undefined || node === undefined) {
return false;
}
if (link.link !== true || node.node !== true) {
return false;
}
return (
link[LINK_SOURCE][NODE_ID] === node[NODE_ID] ||
link[LINK_TARGET][NODE_ID] === node[NODE_ID]
);
for (var i = 0; i < links.length; i++) {
var link = links[i];
if (
link[LINK_SOURCE][NODE_ID] === sourceId &&
link[LINK_TARGET][NODE_ID] === targetId
) {
return true;
}
}
return false;
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
changeDetails(selectionDetails) {
if (selectionDetails.node === true) {
this.changeNodeDetails(selectionDetails[NODE_ID].newDetails);
} else if (selectionDetails.link === true) {
this.changeLinkDetails(
selectionDetails[LINK_SOURCE][NODE_ID],
selectionDetails[LINK_TARGET][NODE_ID],
selectionDetails
);
}
}
changeNodeDetails(nodeId, newDetails) {
var nodes = this.data[GRAPH_NODES];
for (var i = 0; i < nodes.length; i++) {
// Is relevant node?
if (nodes[i][NODE_ID] !== nodeId) {
continue; // No
}
// Changed details
nodes[i] = Object.assign(nodes[i], newDetails);
// All done
return;
}
}
changeLinkDetails(sourceId, targetId, newDetails) {
var links = this.data[GRAPH_LINKS];
for (var i = 0; i < links.length; i++) {
// Is relevant link?
if (
links[i][LINK_SOURCE][NODE_ID] !== sourceId ||
links[i][LINK_TARGET][NODE_ID] !== targetId
) {
continue; // No
}
// Changed details
links[i] = Object.assign(links[i], newDetails);
// All done
return;
}
}
connectNodes(sourceId, targetIds) {
targetIds.forEach((targetId) => {
if (
this.existsLink(sourceId, targetId) ||
this.existsLink(targetId, sourceId)
getCleanData(data = undefined, simulationParameters = false) {
if (data === undefined) {
data = this.data;
}
var cleanData = {};
cleanData[GRAPH_LINKS] = [];
cleanData[GRAPH_NODES] = [];
cleanData[GRAPH_LINKS].push(
this.getCleanLink(link, simulationParameters)
)
cleanData[GRAPH_NODES].push(
this.getCleanNode(node, simulationParameters)
)
getCleanNode(node, simulationParameters) {
var cleanNode = {};
NODE_PARAMS.forEach((param) => {
cleanNode[param] = node[param];
});
if (simulationParameters) {
NODE_SIM_PARAMS.forEach((param) => {
cleanNode[param] = node[param];
});
getCleanLink(link, simulationParameters) {
// Assuming that all nodes are valid, there are two possible formats
// 1. source and target are node objects
if (link[LINK_SOURCE][NODE_ID] !== undefined) {
// Source and target nodes
// Node ids will be converted to complete node objects on running graphs, gotta convert back
cleanLink[LINK_SOURCE] = link[LINK_SOURCE][NODE_ID];
cleanLink[LINK_TARGET] = link[LINK_TARGET][NODE_ID];
} else {
// 2. source and target are just node ids
cleanLink[LINK_SOURCE] = link[LINK_SOURCE];
cleanLink[LINK_TARGET] = link[LINK_TARGET];
}
// Other parameters
LINK_PARAMS.forEach((param) => {
cleanLink[param] = link[param];
});
if (simulationParameters) {
LINK_SIM_PARAMS.forEach((param) => {
cleanLink[param] = link[param];
});
}
for (var i = 0; i < nodes.length; i++) {
if (nodes[i][NODE_ID] === nodeId) {
return true;
}
}
return false;
id = this.getRandomString();
} while (this.existsNodeId(id));
// Based on: https://stackoverflow.com/a/1349426/7376120
var characters =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
var charactersLength = characters.length;
var result = "";
for (var i = 0; i < length; i++) {
result += characters.charAt(
Math.floor(Math.random() * charactersLength)
);
}
return result;
addLink(sourceId, targetId, linkDetails = {}) {
// Copy params
var newLink = linkDetails;
// Make sure the IDs exist
if (
sourceId === undefined ||
targetId === undefined ||
this.existsNodeId(sourceId) === false ||
this.existsNodeId(targetId) === false
) {
return;
}
// Make sure the link is unique
return;
}
newLink[LINK_SOURCE] = sourceId;
newLink[LINK_TARGET] = targetId;
// Basic node properties
newLink.link = true;
newLink.node = false;
// Add node
this.data[GRAPH_LINKS].push(newLink);
this.triggerOnChange();
this.storeCurrentData(
"Added custom link connecting [" +
sourceId +
"] with [" +
targetId +
"]"
);
addNode(nodeDetails) {
// Copy params
var newNode = nodeDetails;
// Make sure the ID is set and unique
if (newNode[NODE_ID] === undefined) {
newNode[NODE_ID] = this.getUnusedNodeId();
} else if (this.existsNodeId(newNode[NODE_ID])) {
return;
}
// Basic node properties
newNode.node = true;
newNode.link = false;
// Add node
this.data[GRAPH_NODES].push(newNode);
this.triggerOnChange();
this.storeCurrentData(
"Added custom node with id [" + newNode[NODE_ID] + "]"
);
static toStr(item) {
if (item === undefined) {
return "UNDEFINED";
}
if (item.node) {
return item[Graph.NODE_LABEL];
} else if (item.link) {
return (
Graph.toStr(item[Graph.LINK_SOURCE]) +
LINK_NAME_CONNECTOR +
Graph.toStr(item[Graph.LINK_TARGET])
);
} else {
return "UNDEFINED";
}
}