Привет, добрые люди из StackOverflow.
У меня довольно серьезная ошибка. Я использую SVG в приложении Ioni c и использую d3 для построения дерева решений. Проблема в том, что когда бы я ни пытался применить стиль / класс к узлу дерева, он либо применяется / частично применяется / вообще не применяется. Я перепробовал много решений и методов отладки, но безрезультатно. Эта проблема встречается только в iOS <= 12. Она работает безупречно на android и iOS 13. Я прикрепил изображения правильно отрендеренного и неправильного узла. </p>
Директива:
angular.module("vshare.decisionTree", []).directive("decisionTree", function() {
return {
restrict: "E",
transclude: "true",
template:
"<svg xmlns='http://www.w3.org/2000/svg' version='1.1' id='dt-{{::$id}}' ng-transclude class='treeContainer'>A:{{internalControl}}</svg>",
scope: {
control: "="
},
link: function(scope, elem, attrs) {
scope.internalControl = scope.control || {};
scope.internalControl.highlightNode = function(data) {
highlightNode(data, "#dt-" + scope.$id);
};
setTimeout(function() {
var svg = d3.select("#dt-" + scope.$id);
init(JSON.parse(attrs.json), svg, true, "dt-" + scope.$id);
}, 50);
}
};
});
JS Код для дерева решений: // Горизонтальная ось x, а вертикальная y
var treeState = [];
var flatTree = [];
var NODE_WIDTH = 150;
var NODE_TEXT_WIDTH = 100;
var LEVEL_MARGIN = NODE_WIDTH + 100;
var NODE_VERT_PADDING = 50;
var NODE_VERT_MARGIN = 50;
var CONTAINER_ID;
function init(data, svg, isPreview, containerId) {
//Flatten the D tree for Search and store it in the flatTree variable (after resetting it)
flatTree = [];
flattenTree(data[0]);
treeState = data;
CONTAINER_ID = containerId;
drawSiblingNodes(treeState, svg);
onNodeClick(treeState[0], svg, 0);
svg.on("click", function() {
clearAttachmentPopups();
});
if (isPreview) {
removeColorAndIndicatorsFromNodes();
}
}
function flattenTree(currentNode) {
var i, currentChild, result;
flatTree.push({ node_id: currentNode.node_id, label: currentNode.label });
for (i = 0; i < currentNode.children.length; i++) {
currentChild = currentNode.children[i];
result = flattenTree(currentChild);
if (result !== false) {
return result;
}
}
return false;
}
function drawNode(components) {
var svg = components.svg;
var level = components.level;
var node = components.sibling;
var isFirstChild = components.isFirstChild;
var lastNodeBBox = components.bBox;
var nodeStartY = lastNodeBBox ? lastNodeBBox.y + lastNodeBBox.height : 0;
var id = "N" + node.node_id;
var nodeGroup = svg.append("g").attr("id", id);
// var text = "Level " + level + " " + node.label;
var text = node.label;
var nodeHeight = 0;
nodeGroup.append("rect").attr("class", "rect");
// make text and wrap it and get the BBox
var textBBox = nodeGroup
.append("text")
.attr("x", 10)
.attr("y", 20)
.text(text)
.call(wrap, NODE_TEXT_WIDTH)
.node()
.getBBox();
//now we know the hight to give of out node
nodeHeight = textBBox.height + NODE_VERT_PADDING;
//set height of rect + some padding
d3.select("#" + CONTAINER_ID + " " + "#" + id + " rect")
.attr("height", nodeHeight)
.attr("width", NODE_WIDTH);
var vertMargin = isFirstChild ? 20 : NODE_VERT_MARGIN;
var translateX = level * LEVEL_MARGIN;
var translateY = nodeStartY + vertMargin;
nodeGroup.attr(
"transform",
"translate(" + translateX + "," + translateY + ")"
);
//make the node bounding box info
var nodeBBox = {
x: level * LEVEL_MARGIN,
y: translateY,
height: nodeHeight,
width: NODE_WIDTH
};
// draw connector to parent
// connector is only required if its a child
if (node.parent_id) {
var parent = findNodeById(treeState[0], node.parent_id);
var parentBBox = parent.bBox;
drawConnector(svg, parentBBox, nodeBBox, node.node_id);
}
//Draw Pointer
if (node.children && node.children.length > 0) {
drawPointer(svg, nodeBBox, node.node_id);
}
//Add this node to our state object.
node.isVisible = true;
node.bBox = nodeBBox;
//on click function
nodeGroup.on("click", () => {
onNodeClick(node, svg, level);
});
// Attachment image
if (
(node.attachment && node.attachment.length > 0) ||
(node.link && node.link.length > 0)
) {
var attachmentButton = nodeGroup
.append("svg:image")
.attr("x", NODE_WIDTH - 25)
.attr("y", 5)
.attr("width", 20)
.attr("height", 24)
.attr("xlink:href", "./img/decisionTree/attach.png");
attachmentButton.on("click", () => {
onAttachmentClick(node, svg);
d3.event.stopPropagation();
});
}
resizeSvg(nodeGroup, svg);
return nodeBBox;
}
function onNodeClick(node, svg, level) {
if (node.parent_id) {
eraseVisibleSubtreeOfChildren(treeState, node.parent_id);
} else {
eraseVisibleSubtreeOfChildren(treeState, node.node_id);
}
drawSiblingNodes(node.children, svg, level + 1);
clearAllPaths();
updateAllNodeColorsById(node.node_id, treeState);
//Draw Metadata indicators
drawMetadataIndicators(svg, node, node.bBox);
}
function drawSiblingNodes(arrOfSiblings, svg, level = 0) {
var sibling, bBox, isFirstChild;
for (var i = 0; i < arrOfSiblings.length; i++) {
sibling = arrOfSiblings[i];
isFirstChild = i === 0;
bBox = drawNode({
svg: svg,
level: level,
sibling: sibling,
isFirstChild: isFirstChild,
bBox: bBox
});
}
}
//*********************************************//
function onAttachmentClick(node, svg) {
var nodeBBox = node.bBox;
var attachBox = svg.append("g").attr("id", "attachment");
var hasLink = node.link.length > 0;
var hasAttachment = node.attachment.length > 0;
var POPUP_HEIGHT = 40;
var xOffset = nodeBBox.x + 20;
var yOffset = nodeBBox.y + 20;
if (hasLink && hasAttachment) {
POPUP_HEIGHT = 80;
}
attachBox
.append("rect")
.attr("width", NODE_WIDTH + 50)
.attr("height", POPUP_HEIGHT)
.attr("class", "attach")
.attr("x", xOffset)
.attr("y", yOffset);
if (hasLink) {
attachBox
.append("svg:image")
.attr("x", xOffset + 10)
.attr("y", yOffset + 10)
.attr("width", 20)
.attr("height", 24)
.attr("xlink:href", "./img/decisionTree/link.png");
attachBox
.append("text")
.attr("class", "attachText")
.attr("x", xOffset + 40)
.attr("y", yOffset + 30)
.text("View Link")
.on("click", function() {
clearAttachmentPopups();
alert("View Link");
});
}
if (hasAttachment && hasLink) {
attachBox
.append("svg:image")
.attr("x", xOffset + 10)
.attr("y", yOffset + 50)
.attr("width", 20)
.attr("height", 24)
.attr("xlink:href", "./img/decisionTree/download.png");
attachBox
.append("text")
.attr("class", "attachText")
.attr("x", xOffset + 40)
.attr("y", yOffset + 70)
.text("Download Attachment")
.on("click", function() {
clearAttachmentPopups();
alert("Download Attachment");
});
}
if (hasAttachment && !hasLink) {
attachBox
.append("svg:image")
.attr("x", xOffset + 10)
.attr("y", yOffset + 10)
.attr("width", 20)
.attr("height", 24)
.attr("xlink:href", "./img/decisionTree/download.png");
attachBox
.append("text")
.attr("class", "attachText")
.attr("x", xOffset + 40)
.attr("y", yOffset + 30)
.text("Download Attachment")
.on("click", function() {
clearAttachmentPopups();
alert("Download Attachment");
});
}
}
function wrap(text, width) {
text.each(function() {
let text = d3.select(this),
words = text
.text()
.split(/\s+/)
.reverse(),
word,
line = [],
lineNumber = 0,
lineHeight = 1.1, // ems
x = text.attr("x"),
y = text.attr("y"),
dy = 1.1,
tspan = text
.text(null)
.append("tspan")
.attr("x", x)
.attr("y", y)
.attr("dy", dy + "em");
while ((word = words.pop())) {
line.push(word);
tspan.text(line.join(" "));
if (tspan.node().getComputedTextLength() > width) {
line.pop();
tspan.text(line.join(" "));
line = [word];
tspan = text
.append("tspan")
.attr("x", x)
.attr("y", y)
.attr("dy", ++lineNumber * lineHeight + dy + "em")
.text(word);
}
}
});
}
function findNodeById(currentNode, id) {
var i, currentChild, result;
if (id == currentNode.node_id) {
return currentNode;
} else {
// Use a for loop instead of forEach to avoid nested functions
// Otherwise "return" will not work properly
for (i = 0; i < currentNode.children.length; i++) {
currentChild = currentNode.children[i];
// Search in the current child
result = findNodeById(currentChild, id);
// Return the result if the node has been found
if (result !== false) {
return result;
}
}
// The node has not been found and we have no more options
return false;
}
}
function eraseNodesOfTree(tree) {
if (!tree || tree.length === 0) {
return;
}
for (var i = 0; i < tree.length; i++) {
if (tree[i].children) {
if (tree[i].isVisible) {
d3.select("#" + CONTAINER_ID + " " + "#N" + tree[i].node_id).remove();
d3.select("#" + CONTAINER_ID + " " + "#P" + tree[i].node_id).remove();
d3.selectAll(
"#" + CONTAINER_ID + " " + ".L" + tree[i].node_id
).remove();
tree[i].isVisible = false;
}
eraseNodesOfTree(tree[i].children);
}
}
}
function eraseVisibleSubtreeOfChildren(tree, parentNodeId) {
var parent = findNodeById(tree[0], parentNodeId);
var children = parent.children;
for (var i = 0; i < children.length; i++) {
eraseNodesOfTree(children[i].children);
}
}
function drawConnector(svg, p, c, nodeId) {
//Draw Line AB
drawLine(
p.x + p.width + 5,
p.y + p.height / 2,
p.x + p.width + 50,
p.y + p.height / 2,
svg,
nodeId
);
//Draw Line DE
drawLine(c.x - 50, c.y + c.height / 2, c.x, c.y + c.height / 2, svg, nodeId);
//Draw Line DB
drawLine(
c.x - 50,
c.y + c.height / 2,
p.x + p.width + 50,
p.y + p.height / 2,
svg,
nodeId
);
}
function drawLine(x1, y1, x2, y2, svg, nodeId) {
svg
.append("line")
.attr("class", "L" + nodeId)
.attr("x1", x1)
.attr("y1", y1)
.attr("x2", x2)
.attr("y2", y2)
.attr("stroke-width", 1);
}
function drawPointer(svg, n, nodeId) {
var ptA = n.x + n.width - 0.45 + "," + (n.y + n.height / 2 - 5);
var ptB = n.x + n.width + 5 + "," + (n.y + n.height / 2);
var ptC = n.x + n.width - 0.45 + "," + (n.y + n.height / 2 + 5);
svg
.append("polyline")
.attr("points", ptA + " " + ptB + " " + ptC)
.attr("id", "P" + nodeId)
.attr("class", "P");
}
function updateAllNodeColorsById(nodeId, tree) {
//Apply the active class to the current node and pointer
applyClassByNodeId(nodeId, "active");
// color all ancestors
var node = findNodeById(tree[0], nodeId);
if (node.parent_id) {
var parent = findNodeById(tree[0], node.parent_id);
colorAllAncestors(tree, parent);
}
}
function applyClassByNodeId(nodeId, className) {
removeClassByNodeId(nodeId);
d3.select("#" + CONTAINER_ID + " " + "#N" + nodeId + " rect").attr(
"class",
className
);
d3.select("#" + CONTAINER_ID + " " + "#P" + nodeId).attr("class", className);
}
function removeClassByNodeId(nodeId) {
d3.select("#" + CONTAINER_ID + " " + "#N" + nodeId + " rect").attr(
"class",
null
);
d3.select("#" + CONTAINER_ID + " " + "#P" + nodeId).attr("class", null);
}
function colorAllAncestors(tree, node) {
applyClassByNodeId(node.node_id, "onPath");
if (node.parent_id) {
var parent = findNodeById(tree[0], node.parent_id);
colorAllAncestors(tree, parent);
}
}
function clearAllPaths() {
d3.selectAll("#" + CONTAINER_ID + " " + "rect").attr("class", null);
d3.selectAll("#" + CONTAINER_ID + " " + "polyline").attr("class", null);
d3.selectAll("#" + CONTAINER_ID + " " + ".indicators").remove();
d3.selectAll("#" + CONTAINER_ID + " " + "rect").attr("class", "rect");
d3.selectAll("#" + CONTAINER_ID + " " + "polyline").attr("class", "P");
}
function drawMetadataIndicators(svg, node, nodeBBox) {
var indicatorsGroup = svg.append("g").attr("id", "I" + node.node_id);
indicatorsGroup.attr("class", "indicators");
var circleRadius = 5;
var spacing = 7;
var x = nodeBBox.x + nodeBBox.width;
var y = nodeBBox.y - 2;
var childrenCircle = indicatorsGroup
.append("circle")
.attr("r", circleRadius)
.attr("class", "childsCircle");
var totalCircle = indicatorsGroup
.append("circle")
.attr("r", circleRadius)
.attr("class", "totalCircle");
var levelCircle = indicatorsGroup
.append("circle")
.attr("r", circleRadius)
.attr("class", "levelCircle");
var childrenText = indicatorsGroup.append("text").text(node.children_ahead);
var totalText = indicatorsGroup.append("text").text(node.children.length);
var levelText = indicatorsGroup.append("text").text(node.level_ahead);
// text lengths
var childrenTextLength = childrenText.node().getComputedTextLength();
var totalTextLength = totalText.node().getComputedTextLength();
var levelTextLength = levelText.node().getComputedTextLength();
levelText.attr("x", x - levelTextLength).attr("y", y);
levelCircle.attr("cx", x - levelTextLength - spacing).attr("cy", y - 4);
totalText
.attr("x", x - levelTextLength - 2 * circleRadius - 2 * spacing)
.attr("y", y);
totalCircle
.attr(
"cx",
x - levelTextLength - totalTextLength - 2 * circleRadius - 2 * spacing
)
.attr("cy", y - 4);
childrenText
.attr(
"x",
x -
levelTextLength -
totalTextLength -
childrenTextLength -
2 * circleRadius -
3 * spacing
)
.attr("y", y);
childrenCircle
.attr(
"cx",
x -
levelTextLength -
totalTextLength -
childrenTextLength -
3 * circleRadius -
3 * spacing
)
.attr("cy", y - 4);
}
function clearAttachmentPopups() {
d3.selectAll("#" + CONTAINER_ID + " " + "#attachment").remove();
}
function resizeSvg(nodeGroup, svg) {
var bBox = nodeGroup.node().getBBox();
var svgBBox = svg.node().getBBox();
var padding = 100;
var xLength = 0;
var yLength = 0;
if (svgBBox.width <= bBox.x + bBox.width) {
xLength = bBox.x + bBox.width - svgBBox.width;
}
if (svgBBox.height <= bBox.y + bBox.height) {
yLength = bBox.y + bBox.height - svgBBox.height;
}
svg
.attr("width", svgBBox.width + xLength + padding)
.attr("height", svgBBox.height + yLength + padding);
}
function removeColorAndIndicatorsFromNodes() {
d3.select(
"#" + CONTAINER_ID + " " + "#N" + treeState[0].node_id + " rect"
).attr("class", null);
d3.select("#" + CONTAINER_ID + " " + "#P" + treeState[0].node_id).attr(
"class",
null
);
d3.select(
"#" + CONTAINER_ID + " " + "#N" + treeState[0].node_id + " rect"
).attr("class", "rect");
d3.select("#" + CONTAINER_ID + " " + "#P" + treeState[0].node_id).attr(
"class",
"P"
);
d3.selectAll("#" + CONTAINER_ID + " " + ".indicators").remove();
}
function highlightNode(data, svgid) {
var traversal = data.traversal;
for (var i = 1; i < traversal.length; i++) {
d3.select("#N" + traversal[i]).on("click")();
}
// var lastNode = findNodeById(treeState[0], traversal[traversal.length - 1]);
// console.log(lastNode);
}