Hey @StefanP74
ich war ein wenig fleißig und habe etwas erarbeitet
Dieses mal betrifft es aber ein paar mehr Zeilen - ich glaube es ist einfacher die komplette Datei zu tauschen. Leider ist es im Forum nicht möglich die ganze Datei hochzuladen...
/**
* i-doit cabling visualization javascript base class.
*
* @author Leonard Fischer <lfischer@i-doit.com>
*/
window.Cabling = Class.create({
$element: null,
data: null,
cache: null,
svg: null,
vis: null,
zoom: null,
options: {},
indexMapping: [],
rootContainer: {
height: 0,
matching: []
},
getOptions: function () {
return this.options;
},
getOption: function (option) {
return this.options[option];
},
setOption: function (option, value) {
this.options[option] = value;
return this;
},
getSelectedElements: function () {
// This method should be used to get all selected cable paths.
// this.svg.selectAll('.selected');
},
initialize: function ($el, data, options) {
var that = this;
that.$element = $el;
that.data = data || [];
that.options = {
authEdit: false, // Defines if the cabling view will allow any editing.
minZoomLevel: 0.1, // Defines the minimal zoom level.
maxZoomLevel: 1.5, // Defines the maximal zoom level.
objectTypeData: {}, // JSON with all necessary object type data (name, color, ...).
connectorTypeData: {}, // JSON with all necessary connector type data (name, color, ...).
objectTypeFilter: [], // Array pf object types, that shall not be displayed.
onAfterProcess: null, // 'Complete' event for when the rendering has finished.
onObjSelect: null, // Click event for a object node.
onObjUnselect: null, // Event for when a object gets unselected.
onCableSelect: null, // Click event for a cable.
onConnectorSelect: null, // Click event for a connector.
onConnectorUnselect: null, // Event for when a connector gets unselected.
onObjAcceptDrag: null, // Event for when a object gets unselected.
onObjAbortDrag: null, // Event for when a object gets unselected.
width: null, // This can be used to change the viewpoints width. The SVG element will always remain 100%x100%.
height: null, // This can be used to change the viewpoints height. The SVG element will always remain 100%x100%.
undefinedConnectorType: {
color: '#fff',
title: '-'
}, // Default 'connector type' object.
undefinedObjectType: {
color: '#fff',
title: '-'
}, // Default 'object type' object.
nodeWidth: 150, // Define the node width.
nodeHeight: 20, // Define the node heigt.
nodeMarginX: 25, // Define the horizontal margin between nodes.
nodeMarginY: 25, // Define the vertical margin between nodes.
displayWiring: false, // Define if the internal wiring shall be displayed (object boxes will get transparent).
showCableLabels: false, // Define if cables shall be displayed.
clickableConnectors: false // Define if connectors shall be clickable.
};
that.rootContainer = {
height: 0,
matching: []
};
// Setting the default width and height.
that.options.width = that.$element.getWidth();
that.options.height = that.$element.getHeight() + 16;
Object.extend(that.options, options || {});
that.svg = d3.select($el).append('svg')
.attr('width', that.options.width)
.attr('height', that.options.height);
that
.svg.append('rect')
.attr('width', that.options.width)
.attr('height', that.options.height)
.style('fill', 'none')
.style('pointer-events', 'all');
// Here we set the <g> after the <rect>, this is necessary for dragging and zooming but still clicking elements inside <g>.
that.vis = this.svg.append("g");
that.zoom = d3.zoom()
.scaleExtent([
that.options.minZoomLevel,
that.options.maxZoomLevel
])
.on('zoom', function () {
that.vis.attr('transform', d3.event.transform);
});
that.svg
.call(that.zoom)
.call(that.zoom.transform, d3.zoomIdentity.translate(that.options.width / 2, that.options.height / 2));
this.appendStyle();
return this;
},
appendStyle: function () {
// This method is necessary to include the SVG styles - this will be used in the SVG export.
var defs = this.svg.select('defs'),
isIE11 = !!window.MSInputMethodContext && !!document.documentMode;
if (defs.node() === null) {
defs = this.svg.append('defs');
defs.append('style').attr('type', 'text/css').text(
".cabling-object { \
fill: none;\
stroke: #444;\
stroke-width: 1px;\
}\
text {\
font-family: \"Helvetica Neue\", \"Lucida Grande\", \"Tahoma\", Arial, serif;\
font-size: 11px;\
paint-order: stroke;\
stroke: transparent;\
stroke-width: 0;\
fill: #000;\
}\
text.connector-left-title,\
text.connector-right-title {\
stroke-width: 5px;\
stroke: transparent;\
}\
text.connector-left-title.stroked,\
text.connector-right-title.stroked {\
" + (isIE11 ? "stroke-width: 0;" : "stroke-width: 5px;") + "\
stroke: #fff;\
}\
text.title,\
.linkname text,\
.linkname.clicked rect,\
.clicked rect.title {\
fill: #fff;\
}\
text.title {\
font-weight: bold;\
}\
.linkname rect,\
.linkname.clicked text {\
fill: #000;\
}\
rect.title,\
.clicked text.title {\
fill: #222;\
}\
circle.connector {\
stroke: rgba(0, 0, 0, 0.75);\
stroke-width: 1;\
}\
circle.connector-inner {\
fill: rgba(0, 0, 0, 0.75);\
stroke: none;\
}\
path {\
fill: none;\
stroke: #555;\
stroke-width: 2;\
}"
);
}
},
textFormat: function (text, width) {
var result = text;
if (width <= 0) {
return '';
}
d3.select(this).text(result);
while (this.getComputedTextLength() > width) {
result = result.substring(0, (result.length - 2));
d3.select(this).text(result);
}
if (result !== text && width > 2) {
result += '..';
}
return result;
},
clearCanvas: function () {
this.vis.selectAll('*').remove();
this.rootContainer = {
height: 0,
matching: []
};
},
responsive: function () {
this.options.width = this.$element.getWidth();
this.options.height = this.$element.getHeight() + 16;
return this;
},
preProcess: function () {
return this;
},
process: function () {
this.indexMapping = [];
return this.preProcess().processSide('left').processSide('right').postProcess();
},
processSide: function (dir) {
var that = this,
invertX = (dir === 'right'),
cache = this.data[dir];
// @see CABLING-49 Implement proper calculation for 'separation'.
var tree = d3.tree()
.separation((a, b) => Math.max(1, (a.children || []).length, (b.children || []).length))
.nodeSize([
(that.options.nodeHeight + that.options.nodeMarginY),
(that.options.nodeWidth + that.options.nodeMarginX)
]);
var root = d3.stratify()(cache)
.sort(function (a, b) {
// First sort by 'connected-index' to keep internally wired connectors near by each other.
if (dir === 'right' && a.data.siblingId && b.data.siblingId && a.data.siblingId !== b.data.siblingId) {
// @see CABLING-58 Use the newly created indexMapping to find the proper index.
return this.indexMapping.findIndex((d) => d == a.data.siblingId) - this.indexMapping.findIndex((d) => d == b.data.siblingId);
}
// @see CABLING-36 Any "unconnected" connector will be moved down, so that no wires cross each other.
if (a.data.siblingId && !b.data.siblingId) {
return -1;
}
if (!a.data.siblingId && b.data.siblingId) {
return 1;
}
// @see CABLING-19 Following code will try to sort the connectors in a more natural way!
// First we split by "non-word" characters (like dots, slashes etc.).
var aParts = a.data.title.split(/\W/),
bParts = b.data.title.split(/\W/),
minParts = Math.min(aParts.length, bParts.length), comparison, i;
for (i = 0; i < minParts; i++) {
if (isNumeric(aParts[i]) && isNumeric(bParts[i])) {
comparison = aParts[i] - bParts[i];
} else {
comparison = aParts[i].localeCompare(bParts[i]);
}
if (comparison !== 0) {
return comparison;
}
}
// If nothing worked out, use the default:
return a.data.title.localeCompare(b.data.title);
}.bind(this));
if (dir === 'left') {
this.indexMapping = root.children.map((d) => d.data.id);
}
var connectorTranslate = function (d) {
if (that.options.clickableConnectors) {
if (d.parent === null || d.parent && d.parent.id === 'root') {
return 'translate(' + (invertX ? 5 : -5) + ',0)';
}
if (d.data.inner) {
return 'translate(' + (invertX ? -5 : that.options.nodeWidth+5) + ',0)';
} else {
return 'translate(' + (invertX ? that.options.nodeWidth+5 : -(that.options.nodeWidth+5)) + ',0)';
}
} else {
if (d.parent === null || d.parent && d.parent.id === 'root') {
return 'translate(0,0)';
}
if (d.data.inner) {
return 'translate(' + (invertX ? 0 : that.options.nodeWidth) + ',0)';
} else {
return 'translate(' + (invertX ? that.options.nodeWidth : -that.options.nodeWidth) + ',0)';
}
}
};
var linkData = tree(root).links();
var cable = that.vis
.selectAll(".cable.cable-" + dir)
.data(linkData);
cable
.enter().append("path")
.style("opacity", 0)
.attr("class", "cable cable-" + dir);
// After the link data is assigned, we reduce the data to only hold cable information.
linkData = linkData.filter(function (d) {
return d.source.id !== 'root' && d.source.data.outer;
});
// @see CABLING-35 Show internal cabling
that.vis
.selectAll('.internal-link.internal-link-' + dir)
.data((root.children || []).filter((d) => d.data.wiredToItself))
.enter()
.append("path")
.style("opacity", 0)
.attr('class', 'internal-link internal-link-' + dir)
.attr('d', function (d) {
const connection = (root.children || []).find(function (conn) { return conn.id == d.data.connectorId});
if (!connection) {
return '';
}
d.y -= 20;
var xBetween = d.y + 30;
const yBetween = ((d.x + connection.x) / 2);
// Mirror the coordinates for the left side.
if (dir === 'left') {
d.y *= -1;
xBetween *= -1;
}
return 'M' + d.y + ',' + d.x +
' C' + xBetween + ',' + d.x + ' ' + xBetween + ',' + yBetween + ' ' + xBetween + ',' + yBetween;
})
.interrupt()
.transition().duration(500)
.transition().duration(250)
.style("opacity", 1);
var cableLabel = that.vis
.selectAll(".linkname.linkname-" + dir)
.data(linkData)
.enter()
.append("g")
.style("opacity", 0)
.attr("class", "linkname linkname-" + dir)
.attr('data-id', function (d) {
return d.source.data.cableId;
});
that.vis
.selectAll(".cable.cable-" + dir)
.classed('cable-root', function (d) {
return (d.source.id === 'root');
})
.interrupt().transition().duration(500)
.attr("d", function (d) {
var fromX = (d.source.y + (that.options.showCableLabels ? 50 : 125)),
fromX2 = (fromX+20),
fromX3 = (fromX+40),
fromY = d.source.x,
toX = (d.target.y + (that.options.showCableLabels ? 50 : 125)),
toY = d.target.x;
if (d.target.data.doubling) {
toX += 20;
}
if (!invertX) {
fromX *= -1;
fromX2 *= -1;
fromX3 *= -1;
toX *= -1;
}
return "M" + fromX + "," + fromY +
"C" + fromX2 + "," + fromY + " " + fromX2 + "," + toY + " " + fromX3 + "," + toY +
"L" + toX + "," + toY;
})
.transition().duration(250)
.style("opacity", 1)
.style('stroke-dasharray', function (d) {
if (d.target.data.doubling) {
return '2,2';
}
return null;
});
cable
.exit()
.interrupt().transition().duration(500)
.style("opacity", 0)
.remove();
cableLabel
.append('rect')
.attr('class', 'mouse-pointer')
.attr('height', 14)
.attr('width', 125)
.attr('data-id', function (d) {
return d.source.cableId
})
.on('click', that.selectCableObject.bind(this));
cableLabel
.append('text')
.attr('class', 'mouse-pointer')
.attr('dy', 10)
.attr('x', 62.5)
.style('text-anchor', 'middle')
.text(function (d) {
return d.source.data.cableTitle;
})
.on('click', that.selectCableObject.bind(this));
that.vis
.selectAll(".linkname.linkname-" + dir)
.classed('hide', !that.options.showCableLabels)
.interrupt().transition().duration(550)
.style('opacity', (that.options.showCableLabels ? 1 : 0))
.attr('transform', function (d) {
var translateX = d.source.y,
translateY = d.source.x;
if (invertX) {
translateX = translateX + 85;
} else {
translateX = translateX + that.options.nodeWidth + 65;
}
if (d.source.parent.id === 'root') {
translateX = translateX - 65;
}
return 'translate(' + (invertX ? '' : '-') + translateX + ',' + translateY + ')';
});
var node = that.vis
.selectAll(".node.node-" + dir)
.data(root.descendants());
var nodeEnter = node
.enter().append("g")
.style("opacity", 0)
.attr("class", "node node-" + dir)
.classed('root', function (d) {
return d.parent === null;
})
.attr('data-id', function (d) {
return d.id;
});
// This will append newly created and already existing nodes.
this.vis
.selectAll('.node.node-' + dir)
.transition().duration(500)
.style("opacity", 1)
.attr("transform", function (d) {
var translateX = d.y,
translateY = d.x;
if (d.parent === null) {
return "translate(" + (invertX ? 0 : -that.options.nodeWidth) + "," + translateY + ")";
}
if (d.parent && d.parent.id === 'root') {
return "translate(" + (invertX ? that.options.nodeWidth : -that.options.nodeWidth) + "," + translateY + ")";
}
if (d.data.inner) {
if (!invertX) {
translateX = -(translateX + that.options.nodeWidth);
}
} else {
if (!invertX) {
translateX = -(translateX + that.options.nodeWidth) + (that.options.nodeWidth + that.options.nodeMarginX);
} else {
translateX -= that.options.nodeMarginX;
}
}
return "translate(" + translateX + "," + translateY + ")";
});
node.exit()
.interrupt().transition().duration(500)
.style("opacity", 0)
.remove();
var container = nodeEnter
.filter(function (d) {
// Only draw boxes for the "root" element and "left" connectors.
return d.parent === null || d.data.inner;
})
.each(function (d) {
// Prepare some data for each node.
d.childNum = (d.children ? d.children.length : 1);
d.minX = d.y;
d.maxX = d.y + (that.options.nodeWidth * 2);
if (d.children) {
d.minY = d.children.first().x;
d.maxY = d.children.last().x + that.options.nodeHeight;
} else {
d.minY = d.x;
d.maxY = d.x + that.options.nodeHeight;
}
d.width = d.maxX - d.minX;
d.height = d.maxY - d.minY;
d.translateX = 0;
d.translateY = -(d.height / 2);
if (d.parent === null) {
if (that.rootContainer.height < d.height) {
that.rootContainer.height = d.height;
that.rootContainer.translateX = d.translateX;
that.rootContainer.translateY = d.translateY;
}
d.width = that.options.nodeWidth;
}
});
container
.append('rect')
.attr('class', 'title mouse-pointer')
.attr('width', function (d) {
return d.width;
})
.attr('height', function (d) {
return 20;
})
.attr('data-object-id', function (d) {
return d.data.objectId;
})
.attr('transform', function (d) {
var translateX = d.translateX,
translateY = d.translateY;
if (d.parent && d.parent !== 'root') {
if (!invertX) {
translateX = -that.options.nodeWidth;
}
}
return 'translate(' + translateX + ',' + (translateY - 20) + ')';
})
.on('click', that.selectObject.bind(this));
container
.append('rect')
.attr('class', 'body')
.attr('width', function (d) {
return d.width;
})
.attr('height', function (d) {
return d.height;
})
.attr('data-conn', function (d) {
return d.data.title;
})
.attr('transform', function (d) {
var translateX = d.translateX,
translateY = d.translateY;
if (d.parent && d.parent !== 'root') {
if (!invertX) {
translateX = -that.options.nodeWidth;
}
}
return 'translate(' + translateX + ',' + translateY + ')';
})
.attr('fill', function (d) {
if (!that.options.objectTypeData.hasOwnProperty(d.data.objectTypeId)) {
return that.options.undefinedObjectType.color;
}
return that.options.objectTypeData[d.data.objectTypeId].color || that.options.undefinedObjectType.color
});
nodeEnter
.filter(function (d) {
return d.parent !== null;
})
.append('circle')
.attr('data-connector-id', function (d) {
return d.data.id;
})
.attr('class', 'connector connector-' + dir)
.attr('transform', connectorTranslate)
.on('click', that.selectConnector.bind(this));
nodeEnter
.filter(function (d) {
return d.parent !== null;
})
.append('circle')
.attr('data-connector-id', function (d) {
return d.data.id;
})
.style('opacity', 0)
.attr('class', 'connector-inner hide connector-inner-' + dir)
.attr('r', 4)
.attr('transform', connectorTranslate)
.on('click', that.selectConnector.bind(this));
that.vis
.selectAll('circle.connector.connector-' + dir)
.classed('mouse-pointer', that.options.clickableConnectors)
.interrupt().transition().duration(500)
.attr('r', (that.options.clickableConnectors ? 10: 5))
.attr('fill', function (d) {
var connectorType = that.options.connectorTypeData[d.data.connectorType];
return connectorType ? connectorType.color : that.options.undefinedConnectorType.color;
})
.attr('transform', connectorTranslate)
.style('stroke-width', (that.options.clickableConnectors ? 3 : 1));
that.vis
.selectAll('circle.connector-inner.connector-inner-' + dir)
.classed('mouse-pointer', that.options.clickableConnectors)
.interrupt().transition().duration(500)
.attr('transform', connectorTranslate)
.style('opacity', (that.options.clickableConnectors ? 1 : 0));
container
.filter(function (d) {
return (d.parent !== null || invertX);
})
.append('text')
.attr('class', 'title mouse-pointer')
.attr("dy", function (d) {
if (d.id === 'root') {
return -((that.rootContainer.height / 2) + 7);
}
return -((d.height / 2) + 7);
})
.attr("x", function (d) {
if (d.parent === null) {
return 0
}
return invertX ? that.options.nodeWidth : 0;
})
.style("text-anchor", 'middle')
.text(function (d) {
return d.data.objectTitle;
})
.on('click', that.selectObject.bind(this));
nodeEnter
.append("text")
.attr('class', 'connector-' + dir + '-title')
.attr("dy", 3)
.attr("x", function (d) {
if (d.parent !== null && d.parent.id === 'root') {
return invertX ? -25 : 25;
}
if (d.data.outer) {
return invertX ? that.options.nodeWidth-10 : -(that.options.nodeWidth-10);
}
return invertX ? 10 : (that.options.nodeWidth-10);
})
.style("text-anchor", function (d) {
if (d.parent !== null && d.parent.id === 'root' || d.data.outer) {
return invertX ? 'end' : 'start';
}
return invertX ? "start" : "end";
})
.text(function (d) {
return that.textFormat.call(this, d.data.title, (that.options.nodeWidth - 15));
});
// @see CABLING-56 Show the full title when hovering connector.
nodeEnter.append('title').text(d => d.data.title);
// After all calculations have been done, we set the root height.
this.vis.selectAll('g.root rect.title')
.attr('transform', function (d) {
return 'translate(' + that.rootContainer.translateX + ',' + (that.rootContainer.translateY - 20) + ')';
});
this.vis.selectAll('g.root rect.body')
.attr('height', function () {
return that.rootContainer.height;
})
.attr('transform', function (d) {
return 'translate(' + that.rootContainer.translateX + ',' + that.rootContainer.translateY + ')';
});
this.vis.selectAll('rect.body')
.transition().duration(250)
.style('opacity', (this.options.displayWiring ? 0.5 : 1));
this.vis
.selectAll(".cable.cable-root.cable-" + dir)
.each(function (d) {
d.fromX = (invertX ? '' : '-') + 100;
d.fromY = d.target.x;
d.toX = (invertX ? '' : '-') + (d.target.y + (that.options.showCableLabels ? 50 : 125));
d.toY = d.target.x;
// If a connector is internally wired, we draw the line to the middle (to visually "connect" them).
if (d.target.data.wired) {
d.fromX = 0; // -25;
}
// If a root connector is not connected, only show a small "stump".
if (!d.target.children) {
d.toX = parseInt((invertX ? '' : '-') + 200);
}
})
.attr('data-index', (d, i) => i)
.interrupt().transition().delay(500).duration(250)
.style('opacity', 1)
.attr('d', function (d) {
// @see CABLING-35 Cut the 'self wired' connectors more short
if (d.target.data.wiredToItself || false) {
if (dir === 'right') {
d.toX -= 45;
} else {
d.toX += 45;
}
}
if (invertX && d.target.data.wired) {
const index = this.indexMapping.findIndex((i) => i == d.target.data.siblingId);
const $selection = this.vis.select('[data-index="' + index + '"]');
if (index >= 0 && $selection.size()) {
d.fromY = $selection.data()[0].fromY;
return "M" + d.fromX + "," + d.fromY +
"C" + (d.fromX+20) + "," + d.fromY + " " + (d.fromX+20) + "," + d.toY + " " + (d.fromX+40) + "," + d.toY +
"L" + d.toX + "," + d.toY;
}
}
return "M" + d.fromX + "," + d.fromY +
"L" + d.toX + "," + d.toY;
}.bind(this));
this.vis.selectAll('.connector-' + dir + '-title')
.classed('stroked', this.options.displayWiring);
return this;
},
postProcess: function () {
if (Object.isFunction(this.options.onAfterProcess)) {
this.options.onAfterProcess.call(this);
}
return this;
},
setData: function (data) {
this.data = data;
return this;
},
selectObject: function (d) {
var $object = this.vis.selectAll('[data-id="' + d.id + '"]');
if (!$object.classed('clicked')) {
this.vis.selectAll('.clicked').classed('clicked', false);
$object.classed('clicked', true);
if (Object.isFunction(this.options.onObjSelect)) {
this.options.onObjSelect.call(this, d);
}
}
return this;
},
selectCableObject: function (d) {
var $object = this.vis.selectAll('[data-id="' + d.source.data.cableId + '"]');
if (!$object.classed('clicked')) {
this.vis.selectAll('.clicked').classed('clicked', false);
$object.classed('clicked', true);
if (Object.isFunction(this.options.onCableSelect)) {
this.options.onCableSelect.call(this, d);
}
}
return this;
},
selectConnector: function (d) {
var $connector = this.vis.select('circle.connector-inner[data-connector-id="' + d.data.id + '"]');
if (! this.options.clickableConnectors) {
return;
}
$connector.classed('hide', !$connector.classed('hide'));
if (Object.isFunction(this.options.onConnectorSelect)) {
this.options.onConnectorSelect.call(this, d);
}
return this;
},
unselectConnectors: function (d) {
var data = this.vis.selectAll('.connector-inner:not(.hide)').classed('hide', true).data();
if (Object.isFunction(this.options.onConnectorUnselect)) {
this.options.onConnectorUnselect.call(this, data);
}
return this;
},
unselectObject: function (data) {
if (Object.isFunction(this.options.onObjUnselect)) {
this.options.onObjUnselect.call(this, data);
}
return this;
},
center: function () {
var translateX = (this.options.width / 2),
translateY = (this.options.height / 2);
this.vis.attr('transform', "translate(" + translateX + "," + translateY + ")");
this.svg.select('rect').call(this.zoom.transform, d3.zoomIdentity.translate(translateX, translateY));
return this;
}
});
Die Datei müsste mit diesem neuen Inhalt überschrieben werden ![:D 😄](https://community.i-doit.com/assets/plugins/nodebb-plugin-emoji/emoji/android/1f604.png?v=lsnlnvnu57s)
Das dürfte weiterhelfen!
![4b3a1091-f515-4bae-bfc1-f35baeca93b8-image.png](/assets/uploads/files/1725019103609-4b3a1091-f515-4bae-bfc1-f35baeca93b8-image.png)
VG Leo