/**
|
|
* Created by Alex on 3/4/2015.
|
|
*/
|
|
|
|
|
|
var util = require("../../util");
|
|
var DataSet = require('../../DataSet');
|
|
var DataView = require('../../DataView');
|
|
|
|
import Edge from "./components/Edge"
|
|
|
|
class EdgesHandler {
|
|
constructor(body, images, groups) {
|
|
this.body = body;
|
|
this.images = images;
|
|
this.groups = groups;
|
|
|
|
// create the edge API in the body container
|
|
this.body.functions.createEdge = this.create.bind(this);
|
|
|
|
this.edgesListeners = {
|
|
'add': (event, params) => {this.add(params.items);},
|
|
'update': (event, params) => {this.update(params.items);},
|
|
'remove': (event, params) => {this.remove(params.items);}
|
|
};
|
|
|
|
this.options = {};
|
|
this.defaultOptions = {
|
|
arrows: {
|
|
to: {enabled: false, scaleFactor:1}, // boolean / {arrowScaleFactor:1} / {enabled: false, arrowScaleFactor:1}
|
|
middle: {enabled: false, scaleFactor:1},
|
|
from: {enabled: false, scaleFactor:1}
|
|
},
|
|
color: {
|
|
color:'#848484',
|
|
highlight:'#848484',
|
|
hover: '#848484',
|
|
inherit: {
|
|
enabled: true,
|
|
source: 'from', // from / true
|
|
useGradients: false // release in 4.0
|
|
},
|
|
opacity:1.0
|
|
},
|
|
dashes:{
|
|
enabled: false,
|
|
preset: 'dotted',
|
|
length: 10,
|
|
gap: 5,
|
|
altLength: undefined
|
|
},
|
|
font: {
|
|
color: '#343434',
|
|
size: 14, // px
|
|
face: 'arial',
|
|
background: 'none',
|
|
stroke: 1, // px
|
|
strokeColor: '#ffffff',
|
|
align:'horizontal'
|
|
},
|
|
hidden: false,
|
|
hoverWidth: 1.5,
|
|
label: undefined,
|
|
length: undefined,
|
|
physics: true,
|
|
scaling:{
|
|
min: 1,
|
|
max: 15,
|
|
label: {
|
|
enabled: true,
|
|
min: 14,
|
|
max: 30,
|
|
maxVisible: 30,
|
|
drawThreshold: 3
|
|
},
|
|
customScalingFunction: function (min,max,total,value) {
|
|
if (max == min) {
|
|
return 0.5;
|
|
}
|
|
else {
|
|
var scale = 1 / (max - min);
|
|
return Math.max(0,(value - min)*scale);
|
|
}
|
|
}
|
|
},
|
|
selfReferenceSize:20,
|
|
smooth: {
|
|
enabled: true,
|
|
dynamic: true,
|
|
type: "continuous",
|
|
roundness: 0.5
|
|
},
|
|
title:undefined,
|
|
width: 1,
|
|
widthSelectionMultiplier: 2,
|
|
value:1
|
|
};
|
|
|
|
util.extend(this.options, this.defaultOptions);
|
|
|
|
|
|
// this allows external modules to force all dynamic curves to turn static.
|
|
this.body.emitter.on("_forceDisableDynamicCurves", (type) => {
|
|
let emitChange = false;
|
|
for (let edgeId in this.body.edges) {
|
|
if (this.body.edges.hasOwnProperty(edgeId)) {
|
|
let edgeOptions = this.body.edges[edgeId].options.smooth;
|
|
if (edgeOptions.enabled === true && edgeOptions.dynamic === true) {
|
|
if (type === undefined) {
|
|
edge.setOptions({smooth:false});
|
|
}
|
|
else {
|
|
edge.setOptions({smooth:{dynamic:false, type:type}});
|
|
}
|
|
emitChange = true;
|
|
}
|
|
}
|
|
}
|
|
if (emitChange === true) {
|
|
this.body.emitter.emit("_dataChanged");
|
|
}
|
|
});
|
|
|
|
// this is called when options of EXISTING nodes or edges have changed.
|
|
this.body.emitter.on("_dataUpdated", () => {
|
|
this.reconnectEdges();
|
|
this.markAllEdgesAsDirty();
|
|
});
|
|
|
|
}
|
|
|
|
setOptions(options) {
|
|
if (options !== undefined) {
|
|
var fields = [
|
|
'font',
|
|
'from',
|
|
'hidden',
|
|
'hoverWidth',
|
|
'label',
|
|
'length',
|
|
'line',
|
|
'opacity',
|
|
'physics',
|
|
'selfReferenceSize',
|
|
'to',
|
|
'title',
|
|
'value',
|
|
'width',
|
|
'widthMin',
|
|
'widthMax',
|
|
'widthSelectionMultiplier'
|
|
];
|
|
util.selectiveExtend(fields, this.options, options);
|
|
util.mergeOptions(this.options, options, 'smooth');
|
|
util.mergeOptions(this.options, options, 'dashes');
|
|
|
|
// set the scaling options
|
|
if (options.scaling !== undefined) {
|
|
if (options.scaling.min !== undefined) {this.options.scaling.min = options.scaling.min;}
|
|
if (options.scaling.max !== undefined) {this.options.scaling.max = options.scaling.max;}
|
|
util.mergeOptions(this.options.scaling, options.scaling, 'label');
|
|
}
|
|
|
|
|
|
// hanlde multiple input cases for arrows
|
|
if (options.arrows !== undefined) {
|
|
if (typeof options.arrows === 'string') {
|
|
let arrows = options.arrows.toLowerCase();
|
|
if (arrows.indexOf("to") != -1) {this.options.arrows.to.enabled = true;}
|
|
if (arrows.indexOf("middle") != -1) {this.options.arrows.middle.enabled = true;}
|
|
if (arrows.indexOf("from") != -1) {this.options.arrows.from.enabled = true;}
|
|
}
|
|
else if (typeof options.arrows === 'object') {
|
|
util.mergeOptions(this.options.arrows, options.arrows, 'to');
|
|
util.mergeOptions(this.options.arrows, options.arrows, 'middle');
|
|
util.mergeOptions(this.options.arrows, options.arrows, 'from');
|
|
}
|
|
else {
|
|
throw new Error("The arrow options can only be an object or a string. Refer to the documentation. You used:" + JSON.stringify(options.arrows));
|
|
}
|
|
}
|
|
|
|
|
|
// hanlde multiple input cases for color
|
|
if (options.color !== undefined) {
|
|
if (util.isString(options.color)) {
|
|
util.assignAllKeys(this.options.color, options.color);
|
|
this.options.color.inherit.enabled = false;
|
|
}
|
|
else {
|
|
if (typeof options.color === 'object') {
|
|
let colorsDefined = false;
|
|
if (options.color.color !== undefined) {this.options.color.color = options.color.color; colorsDefined = true;}
|
|
if (options.color.highlight !== undefined) {this.options.color.highlight = options.color.highlight; colorsDefined = true;}
|
|
if (options.color.hover !== undefined) {this.options.color.hover = options.color.hover; colorsDefined = true;}
|
|
if (options.color.opacity !== undefined) {this.options.color.opacity = options.color.opacity;}
|
|
|
|
if (options.color.inherit === undefined && colorsDefined === true) {
|
|
this.options.color.inherit.enabled = false;
|
|
}
|
|
}
|
|
}
|
|
util.mergeOptions(this.options.color, options.color, 'inherit');
|
|
this.markAllEdgesAsDirty();
|
|
}
|
|
|
|
// update smooth settings in all edges
|
|
let dataChanged = false;
|
|
if (options.smooth !== undefined) {
|
|
for (let edgeId in this.body.edges) {
|
|
if (this.body.edges.hasOwnProperty(edgeId)) {
|
|
dataChanged = this.body.edges[edgeId].updateEdgeType() || dataChanged;
|
|
}
|
|
}
|
|
}
|
|
|
|
// update fonts in all edges
|
|
if (options.font) {
|
|
if (typeof options.font === 'string') {
|
|
let optionsArray = options.font.split(" ");
|
|
this.options.font.size = optionsArray[0].replace("px",'');
|
|
this.options.font.face = optionsArray[1];
|
|
this.options.font.color = optionsArray[2];
|
|
}
|
|
else if (typeof options.font == 'object') {
|
|
this.options.font = util.bridgeObject(options.font);
|
|
}
|
|
this.options.font.size = Number(this.options.font.size);
|
|
|
|
for (let edgeId in this.body.edges) {
|
|
if (this.body.edges.hasOwnProperty(edgeId)) {
|
|
this.body.edges[edgeId].updateLabelModule();
|
|
}
|
|
}
|
|
}
|
|
|
|
// update the state of the variables if needed
|
|
if (options.hidden !== undefined || options.physics !== undefined || dataChanged === true) {
|
|
this.body.emitter.emit('_dataChanged');
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Load edges by reading the data table
|
|
* @param {Array | DataSet | DataView} edges The data containing the edges.
|
|
* @private
|
|
* @private
|
|
*/
|
|
setData(edges, doNotEmit = false) {
|
|
var oldEdgesData = this.body.data.edges;
|
|
|
|
if (edges instanceof DataSet || edges instanceof DataView) {
|
|
this.body.data.edges = edges;
|
|
}
|
|
else if (Array.isArray(edges)) {
|
|
this.body.data.edges = new DataSet();
|
|
this.body.data.edges.add(edges);
|
|
}
|
|
else if (!edges) {
|
|
this.body.data.edges = new DataSet();
|
|
}
|
|
else {
|
|
throw new TypeError('Array or DataSet expected');
|
|
}
|
|
|
|
// TODO: is this null or undefined or false?
|
|
if (oldEdgesData) {
|
|
// unsubscribe from old dataset
|
|
util.forEach(this.edgesListeners, (callback, event) => {oldEdgesData.off(event, callback);});
|
|
}
|
|
|
|
// remove drawn edges
|
|
this.body.edges = {};
|
|
|
|
// TODO: is this null or undefined or false?
|
|
if (this.body.data.edges) {
|
|
// subscribe to new dataset
|
|
util.forEach(this.edgesListeners, (callback, event) => {this.body.data.edges.on(event, callback);});
|
|
|
|
// draw all new nodes
|
|
var ids = this.body.data.edges.getIds();
|
|
this.add(ids, true);
|
|
}
|
|
|
|
if (doNotEmit === false) {
|
|
this.body.emitter.emit("_dataChanged");
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Add edges
|
|
* @param {Number[] | String[]} ids
|
|
* @private
|
|
*/
|
|
add(ids, doNotEmit = false) {
|
|
var edges = this.body.edges;
|
|
var edgesData = this.body.data.edges;
|
|
|
|
for (let i = 0; i < ids.length; i++) {
|
|
var id = ids[i];
|
|
|
|
var oldEdge = edges[id];
|
|
if (oldEdge) {
|
|
oldEdge.disconnect();
|
|
}
|
|
|
|
var data = edgesData.get(id, {"showInternalIds" : true});
|
|
edges[id] = this.create(data);
|
|
}
|
|
|
|
if (doNotEmit === false) {
|
|
this.body.emitter.emit("_dataChanged");
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Update existing edges, or create them when not yet existing
|
|
* @param {Number[] | String[]} ids
|
|
* @private
|
|
*/
|
|
update(ids) {
|
|
var edges = this.body.edges;
|
|
var edgesData = this.body.data.edges;
|
|
var dataChanged = false;
|
|
for (var i = 0; i < ids.length; i++) {
|
|
var id = ids[i];
|
|
var data = edgesData.get(id);
|
|
var edge = edges[id];
|
|
if (edge === null) {
|
|
// update edge
|
|
edge.disconnect();
|
|
dataChanged = edge.setOptions(data) || dataChanged; // if a support node is added, data can be changed.
|
|
edge.connect();
|
|
}
|
|
else {
|
|
// create edge
|
|
this.body.edges[id] = this.create(data);
|
|
dataChanged = true;
|
|
}
|
|
}
|
|
|
|
if (dataChanged === true) {
|
|
this.body.emitter.emit("_dataChanged");
|
|
}
|
|
else {
|
|
this.body.emitter.emit("_dataUpdated");
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Remove existing edges. Non existing ids will be ignored
|
|
* @param {Number[] | String[]} ids
|
|
* @private
|
|
*/
|
|
remove(ids) {
|
|
var edges = this.body.edges;
|
|
for (var i = 0; i < ids.length; i++) {
|
|
var id = ids[i];
|
|
var edge = edges[id];
|
|
if (edge !== undefined) {
|
|
if (edge.via != null) {
|
|
delete this.body.supportNodes[edge.via.id];
|
|
}
|
|
edge.disconnect();
|
|
delete edges[id];
|
|
}
|
|
}
|
|
|
|
this.body.emitter.emit("_dataChanged");
|
|
}
|
|
|
|
create(properties) {
|
|
return new Edge(properties, this.body, this.options)
|
|
}
|
|
|
|
|
|
markAllEdgesAsDirty() {
|
|
for (var edgeId in this.body.edges) {
|
|
this.body.edges[edgeId].edgeType.colorDirty = true;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Reconnect all edges
|
|
* @private
|
|
*/
|
|
reconnectEdges() {
|
|
var id;
|
|
var nodes = this.body.nodes;
|
|
var edges = this.body.edges;
|
|
|
|
for (id in nodes) {
|
|
if (nodes.hasOwnProperty(id)) {
|
|
nodes[id].edges = [];
|
|
}
|
|
}
|
|
|
|
for (id in edges) {
|
|
if (edges.hasOwnProperty(id)) {
|
|
var edge = edges[id];
|
|
edge.from = null;
|
|
edge.to = null;
|
|
edge.connect();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
|
|
export default EdgesHandler;
|