Related
I've been trying to build a multiline chart of three lines and display tooltip along with circles on data points when hovered over. However the circle is not following the line and at certain data points and it comes out of the line. Setting the element focus, for the particular line by adjusting transform, translate for the specific line solves the issue but then messes up the other lines. How to pass all the datasets instead of one dataset on focus element? I tried sorting all the datasets in one common dataset consisting of all the objects and pass it on transform of focus but that is also making the circle shift away from the lines at certain datapoints
drawAUCROCGraphExpanded(fprTpr_train, fprTpr_test, fprTpr_oot, fprTpr_default, trainroc, testroc, ootroc) {
var margin = { top: 20, right: 0, bottom: 20, left: 0 },
width = 560 - margin.left - margin.right,
height = 395 - margin.top - margin.bottom;
var x = d3.scaleLinear().range([0, width]);
var y = d3.scaleLinear().range([height, 0]);
// Draw the X-axis on the DOM
this.aucRocSvgExpanded.append('g')
.attr('transform', 'translate(0,' + height + ')')
.call(d3.axisBottom(x).ticks(5));
// Draw the Y-axis on the DOM
this.aucRocSvgExpanded.append('g')
.attr('transform', 'translate(0, 0)')
.call(d3.axisLeft(y).ticks(10));
var valueline = d3.line()
.curve(d3.curveMonotoneX)
.x(function (d: any) { return x(d.fpr); })
.y(function (d: any) { return y(d.tpr); });
var valueline2 = d3.line()
.curve(d3.curveMonotoneX)
.x(function (d: any) { return x(d.fpr); })
.y(function (d: any) { return y(d.tpr); });
var valueline3 = d3.line()
.curve(d3.curveMonotoneX)
.x(function (d: any) { return x(d.fpr); })
.y(function (d: any) { return y(d.tpr); });
var valueline4 = d3.line()
.curve(d3.curveMonotoneX)
.x(function (d: any) { return x(d.fpr); })
.y(function (d: any) { return y(d.tpr); });
x.domain([0, 1]);
y.domain([0, 1]);
this.aucRocSvgExpanded.append("path")
.attr("style", 'stroke: #c1c2c2; stroke-width: 2; fill: none; stroke-dasharray: 3, 4.5;')
.attr("d", valueline(fprTpr_default))
this.aucRocSvgExpanded.append("path")
.attr("style", 'stroke: #2E4A71; stroke-width: 2; fill: none;')
//.style('fill', d => fprTpr_flag == true ? '#e6e8ed;' : null )
.attr("d", valueline2(fprTpr_train))
.attr("class", "line")
.attr("width", width)
.attr("height", height)
if (fprTpr_test.length > 0) {
this.aucRocSvgExpanded.append("path")
.attr("style", 'stroke: #517BAD; stroke-width: 2; fill: none;')
//.style('fill', d => fprTpr_flag == true ? '#e6e8ed;' : null )
.attr("d", valueline3(fprTpr_test))
.attr("class", "line")
.attr("width", width)
.attr("height", height)
}
if (fprTpr_oot.length > 0) {
this.aucRocSvgExpanded.append("path")
.attr("style", 'stroke: #74A0D8; stroke-width: 2; fill: none;')
.attr("d", valueline4(fprTpr_oot))
.attr("class", "line")
.attr("width", width)
.attr("height", height)
}
var focus = this.aucRocSvgExpanded.append("g")
.attr("class", "focus")
.style("display", "none")
// append the x line
focus.append("line")
.attr("class", "x")
.style("stroke", "blue")
.style("stroke-dasharray", "3,3")
.style("opacity", 0.5)
.attr("y1", 0)
.attr("y2", height);
// append the circles at the intersection
focus.append("circle")
.attr("id", "train_circle")
.style("fill", "#2E4A71")
.style("stroke", "#2E4A71")
.attr("r", 4);
if (fprTpr_test.length > 0) {
focus.append("circle")
.attr("id", "test_circle")
.style("fill", "#517BAD")
.style("stroke", "#517BAD")
.attr("r", 4);
}
if (fprTpr_oot.length > 0) {
focus.append("circle")
.attr("id", "oot_circle")
.style("fill", "#74A0D8")
.style("stroke", "#74A0D8")
.attr("r", 4);
}
var bisectData = d3.bisector((d: any) => d.fpr).right,
sortData_train = fprTpr_train.sort(function (a, b) { return d3.ascending(a.fpr, b.fpr) }),
sortData_test = fprTpr_test.sort(function (a, b) { return d3.ascending(a.fpr, b.fpr) }),
sortData_oot = fprTpr_oot.sort(function (a, b) { return d3.ascending(a.fpr, b.fpr) })
const mouseover = () => {
focus.style("display", null);
this.roc_tooltipExpanded.style("display", "block")
}
const mouseout = () => {
focus.style("display", "none");
this.roc_tooltipExpanded.style("display", "none")
}
const mousemove = (event) => {
var x0 = x.invert(d3.pointer(event)[0]),
i = bisectData(sortData_train, x0, 1),
j = bisectData(sortData_test, x0, 1),
k = bisectData(sortData_oot, x0, 1),
a = sortData_train[i].fpr,
d = sortData_train[i].tpr
if (fprTpr_test.length > 0) {
var b = sortData_test[j].fpr,
e = sortData_test[j].tpr
}
if (fprTpr_oot.length > 0) {
var c = sortData_oot[k].fpr,
f = sortData_oot[k].tpr
}
if (fprTpr_oot.length > 0) {
focus.attr("transform", "translate(" + x(sortData_oot[k].fpr) + "," + 0 + ")");
} else if (fprTpr_test.length > 0) {
focus.attr("transform", "translate(" + x(sortData_test[j].fpr) + "," + 0 + ")"); //selecting one of this fixes issue for the particular line but messes up others
} else {
focus.attr("transform", "translate(" + x(sortData_train[i].fpr) + "," + 0 + ")");
}
var roc_label;
if (fprTpr_oot.length > 0 && fprTpr_test.length > 0) {
roc_label = `<div style="height: 20px; padding: 2px 4px; background-color: #E6E8ED;"">AUC:</div><div style="background-color: #fff; padding: 2px 4px"><div style = "color: #2E4A71;"><span>Train:</span> ${trainroc} - <b>${d}</b></div><div style="color: #517BAD;"><span style="padding: 0px 7px 0px 0px;">Test:</span> ${testroc} - <b>${e}</b></div><div style="color: #74A0D8;"><span style="padding: 0px 6px 0px 0px;">OOT:</span> ${ootroc} - <b>${f}</b></div></div>`;
} else if (fprTpr_test.length > 0 && fprTpr_oot.length <= 0) {
roc_label = `<div style="height: 20px; padding: 2px 4px; background-color: #E6E8ED;"">AUC:</div><div style="background-color: #fff; padding: 2px 4px"><div style = "color: #2E4A71;><span>Train:</span> ${trainroc} - <b>${d}</b></div><div style="color: #517BAD;><span style="padding: 0px 7px 0px 0px;">Test:</span> ${testroc} - <b>${e}</b></div></div>`;
} else if (fprTpr_test.length <= 0 && fprTpr_oot.length > 0) {
roc_label = `<div style="height: 20px; padding: 2px 4px; background-color: #E6E8ED;"">AUC:</div><div style="background-color: #fff; padding: 2px 4px"><div style = "color: #2E4A71;><span>Train:</span> ${trainroc} - <b>${d}</b></div><div style="color: #74A0D8;"><span style="padding: 0px 6px 0px 0px;">OOT:</span> ${ootroc} - <b>${f}</b></div></div>`;
} else {
roc_label = `<div style="height: 20px; padding: 2px 4px; background-color: #E6E8ED;"">AUC:</div><div style="background-color: #fff; padding: 2px 4px"><div style = "color: #2E4A71;><span>Train:</span> ${trainroc} - <b>${d}</b></div></div>`;
}
this.roc_tooltipExpanded
.html(roc_label)
// .style("left", (d3.pointer(event)[0]+40) + "px")
.style("right", (d3.pointer(event)[0]) + "px")
.style("top", (d3.pointer(event)[1]) + "px")
.style("pointer-events", "none")
.style("z-index", "1")
.style("display", "block")
focus.select("circle#train_circle").attr("transform", "translate(" + x(0) + "," + y(sortData_train[i].tpr) + ")")
if (fprTpr_test.length > 0) {
focus.select("circle#test_circle").attr("transform", "translate(" + x(0) + "," + y(sortData_test[j].tpr) + ")")
}
if (fprTpr_oot.length > 0) {
focus.select("circle#oot_circle").attr("transform", "translate(" + x(0) + "," + y(sortData_oot[k].tpr) + ")")
}
}
this.aucRocSvgExpanded.append("g")
.append("rect")
.attr("class", "rect")
.attr("width", width)
.attr("height", height)
.style("fill", "transparent")
.on("mouseover", mouseover)
.on("mouseout", mouseout)
.on("mousemove", mousemove);
this.aucRocSvgExpanded.append("g")
.append("text")
.attr('y', 390)
.attr('x', 280)
.style("font-size", "14px")
.style("font-weight", "600")
.style("text-anchor", "middle")
.text("FPR");
this.aucRocSvgExpanded.append("g")
.append("text")
.attr("transform", "rotate(-90)")
.attr('y', -35)
.attr('x', -180)
.style("font-size", "14px")
.style("font-weight", "600")
.style("text-anchor", "middle")
.text("TPR");
}
I am working on the below layout ( decision tree) using D3 where I need to draw diamond shapes for the nodes that are "decisions" in a flow chart and rest of the nodes are actions ( rectangles).
Logically, all the nodes that have children are diamond shapes. Below is a UX Visualisation.
I've come up with a jsfiddle for the top to bottom D3 chart here : https://jsfiddle.net/p6vrmnu0/3/
But all the svg elements are currently rectangles and all the lines are now connected by "curved" links, I would like svgs to be diamonds for "decision" nodes and the links to be similar to the above image originating from the diamond's two corners and ending at at the top of the rhombus if the next is decision or top of rectangle if the next is an action.
var margin = {
top: 20,
right: 120,
bottom: 20,
left: 120
},
width = 960 - margin.right - margin.left,
height = 800 - margin.top - margin.bottom;
var emptyDecisionBox = {
"name": "newDecision",
"id": "newId",
"value": "notSure",
"condition": "true",
};
var selectedNode;
var root = {
"name": "Root",
"children": [{
"name": "analytics",
"type": "decision",
"value": "a+b",
"children": [{
"name": "distinction",
"type": "action",
"condition": "true",
"value": "5",
}, {
"name": "nonDistinction",
"type": "action",
"condition": "false",
"value": "4"
}],
},
{
"name": "division",
"type": "action",
"value": "a-b",
"children": [],
}
]
};
var i = 0,
duration = 750,
rectW = 60,
rectH = 30;
var tree = d3.layout.tree().nodeSize([70, 40]);
var diagonal = d3.svg.diagonal()
.projection(function(d) {
return [d.x + rectW / 2, d.y + rectH / 2];
});
var svg = d3.select("body").append("svg").attr("width", 1000).attr("height", 1000)
.call(zm = d3.behavior.zoom().scaleExtent([1, 3]).on("zoom", redraw)).append("g")
.attr("transform", "translate(" + 350 + "," + 20 + ")");
//necessary so that zoom knows where to zoom and unzoom from
zm.translate([350, 20]);
// new part
var plusButton = svg
.append('g')
.classed('button', true)
.classed('hide', true)
.on('click', function() {
console.log("CLICKED");
});
plusButton
.append('rect')
.attr('transform', 'translate(-8, -8)') // center the button inside the `g`
.attr('width', 16)
.attr('height', 16)
.attr('rx', 2);
plusButton
.append('path')
.attr('d', 'M-6 0 H6 M0 -6 V6');
var rectangleShape = svg.append('g')
.classed('button', true)
.classed('hide', true)
.on('click', function() {
removeAllButtonElements();
})
rectangleShape
.append('rect')
.attr('width', 40)
.attr('height', 20)
.style('fill', 'orange');
var diamondImage = svg.append('g')
.classed('button', true)
.classed('hide', true)
.classed('scale', true)
.on('click', function() {
addElement(selectedNode);
console.log("Clicked on Diamond");
console.log("set hide to true");
removeAllButtonElements();
});
diamondImage
.append('path')
.attr('d', 'M 20 0 40 20 20 40 0 20 Z')
.style("fill", 'orange');
var rectangleShapeFalse = svg.append('g')
.classed('button', true)
.classed('hide', true)
.on('click', function() {
console.log("rectangle clicked");
removeAllButtonElements();
})
rectangleShapeFalse
.append('rect')
.attr('width', 40)
.attr('height', 20)
.style('fill', 'orange');
var diamondImageFalse = svg.append('g')
.classed('button', true)
.classed('hide', true)
.classed('scale', true)
.on('click', function() {
console.log("Clicked on Diamond");
console.log("set hide to true");
removeAllButtonElements();
});
diamondImageFalse
.append('path')
.attr('d', 'M 20 0 40 20 20 40 0 20 Z')
.style("fill", 'orange');
function removeAllButtonElements() {
plusButton.classed('hide', true);
diamondImage.classed('hide', true);
rectangleShape.classed('hide', true);
diamondImageFalse.classed('hide', true);
rectangleShapeFalse.classed('hide', true);
}
// new part ends.
root.x0 = 0;
root.y0 = height / 2;
function collapse(d) {
if (d.children) {
d._children = d.children;
d._children.forEach(collapse);
d.children = null;
}
}
/* root.children.forEach(collapse); */
update(root);
d3.select("#body").style("height", "800px");
function update(source) {
// Compute the new tree layout.
var nodes = tree.nodes(root).reverse(),
links = tree.links(nodes);
// Normalize for fixed-depth.
nodes.forEach(function(d) {
d.y = d.depth * 180;
});
// Update the nodes…
var node = svg.selectAll("g.node")
.data(nodes, function(d) {
return d.id || (d.id = ++i);
});
// Enter any new nodes at the parent's previous position.
var nodeEnter = node.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) {
return "translate(" + source.x0 + "," + source.y0 + ")";
})
.on("click", click);
nodeEnter.append("rect")
.attr("width", rectW)
.attr("height", rectH)
.attr("stroke", "black")
.attr("stroke-width", 1)
.style("fill", function(d) {
return d._children ? "lightsteelblue" : "#fff";
});
/* nodeEnter.append('path').attr("d", d3.svg.symbol().type( function(d) { return "circle" }) );
*/
nodeEnter.append("text")
.attr("x", rectW / 2)
.attr("y", rectH / 2)
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.text(function(d) {
return d.name;
});
// Transition nodes to their new position.
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
nodeUpdate.select("rect")
.attr("width", rectW)
.attr("height", rectH)
.attr("stroke", "black")
.attr("stroke-width", 1)
.style("fill", function(d) {
return d._children ? "lightsteelblue" : "#fff";
});
nodeUpdate.select("text")
.style("fill-opacity", 1);
// Transition exiting nodes to the parent's new position.
var nodeExit = node.exit().transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + source.x + "," + source.y + ")";
})
.remove();
nodeExit.select("rect")
.attr("width", rectW)
.attr("height", rectH)
//.attr("width", bbox.getBBox().width)""
//.attr("height", bbox.getBBox().height)
.attr("stroke", "black")
.attr("stroke-width", 1);
nodeExit.select("text");
// Update the links…
var link = svg.selectAll("path.link")
.data(links, function(d) {
return d.target.id;
});
// Enter any new links at the parent's previous position.
link.enter().insert("path", "g")
.attr("class", "link")
.attr("x", rectW / 2)
.attr("y", rectH / 2)
.attr("d", function(d) {
var o = {
x: source.x0,
y: source.y0
};
return diagonal({
source: o,
target: o
});
})
.on('mouseenter', function(d, i) {
// Use the native SVG interface to get the bounding box to
// calculate the center of the path
var bbox = this.getBBox();
var x = bbox.x + bbox.width / 2,
y = bbox.y + bbox.height / 2;
plusButton
.attr('transform', 'translate(' + x + ', ' + y + ')')
.classed('hide', false);
})
.on('mouseleave', function(d, i) {
plusButton
.classed('hide', true);
});
// Transition links to their new position.
link.transition()
.duration(duration)
.attr("d", diagonal);
// Transition exiting nodes to the parent's new position.
link.exit().transition()
.duration(duration)
.attr("d", function(d) {
var o = {
x: source.x,
y: source.y
};
return diagonal({
source: o,
target: o
});
})
.remove();
// Stash the old positions for transition.
nodes.forEach(function(d) {
d.x0 = d.x;
d.y0 = d.y;
});
}
// Toggle children on click.
/* function click(d) {
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
update(d);
} */
function click(d) {
console.log(d);
selectedNode = d;
/* if(d.children && d.children.length < 1){
return;
} */
var x = d.x;
var y = d.y + 40;
/* plusButton
.attr('transform', 'translate(' + x + ', ' + y + ')')
.classed('hide', false);
plusButton
.attr('transform', 'translate(' + m + ', ' + h + ')')
.classed('hide', false); */
var m = d.x + 50;
var h = d.y + 20;
diamondImage
.attr('transform', 'translate(' + m + ', ' + h + ')')
.classed('hide', false);
var m = d.x + 60;
var h = d.y - 10;
rectangleShape
.attr('transform', 'translate(' + m + ', ' + h + ')')
.classed('hide', false);
var m = d.x - 40;
var h = d.y + 20;
diamondImageFalse
.attr('transform', 'translate(' + m + ', ' + h + ')')
.classed('hide', false);
var m = d.x - 40;
var h = d.y - 10;
rectangleShapeFalse
.attr('transform', 'translate(' + m + ', ' + h + ')')
.classed('hide', false);
}
//Redraw for zoom
function redraw() {
//console.log("here", d3.event.translate, d3.event.scale);
svg.attr("transform",
"translate(" + d3.event.translate + ")" +
" scale(" + d3.event.scale + ")");
}
function addElement(d) {
console.log(d);
d.children = [];
d.children.push(emptyDecisionBox);
update(root);
}
.node {
cursor: pointer;
}
.node circle {
fill: #fff;
stroke: steelblue;
stroke-width: 1.5px;
}
.node text {
font: 10px sans-serif;
}
.link {
fill: none;
stroke: #ccc;
stroke-width: 1.5px;
}
body {
overflow: hidden;
}
.button>path {
stroke: blue;
stroke-width: 1.5;
}
.button>rect {
fill: #ddd;
stroke: grey;
stroke-width: 1px;
}
.hide {
display: none;
opacity: 0 !important;
pointer-events: none;
}
.link:hover {
cursor: pointer;
stroke-width: 8px;
}
.scale {
/* transform: scale(0.4); */
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
<div class="tree-diagram"></div>
For the line shape, I recommend to just write a shape function yourself. I wrote one that goes horizontal, then makes a small curve, then goes down. It doesn't take the width of the shape into account, but that shouldn't matter, since it's behind the box after all.
For the box shapes, I'd do something similar. Instead of using rects, use a path, and write the shape of the path using an if statement. This is always a very valuable resource.
Just to get you started:
function drawDiamond(centroid) {
// Start at the top
var result = 'M' + centroid.x + ',' + (centroid.y - rectH / 2);
// Go right
result += 'L' + (centroid.x + rectW / 2) + ',' + centroid.y;
// Bottom
result += 'L' + centroid.x + ',' + (centroid.y + rectH / 2);
// Left
result += 'L' + (centroid.x - rectW / 2) + ',' + centroid.y;
// Close the shape
result += 'Z';
return result;
}
function drawRect(centroid) {
// Start at the top left
var result = 'M' + (centroid.x - rectW / 2) + ',' + (centroid.y - rectH / 2);
// Go right
result += 'h' + rectW;
// Go down
result += 'v' + rectH;
// Left
result += 'h-' + rectW;
// Close the shape
result += 'Z';
return result;
}
var margin = {
top: 20,
right: 120,
bottom: 20,
left: 120
},
width = 960 - margin.right - margin.left,
height = 800 - margin.top - margin.bottom;
var emptyDecisionBox = {
"name": "newDecision",
"id": "newId",
"value": "notSure",
"condition": "true",
};
var selectedNode;
var root = {
"name": "Root",
"children": [{
"name": "analytics",
"type": "decision",
"value": "a+b",
"children": [{
"name": "distinction",
"type": "action",
"condition": "true",
"value": "5",
}, {
"name": "nonDistinction",
"type": "action",
"condition": "false",
"value": "4"
}],
},
{
"name": "division",
"type": "action",
"value": "a-b",
"children": [],
}
]
};
var i = 0,
duration = 750,
rectW = 60,
rectH = 30;
var tree = d3.layout.tree().nodeSize([70, 40]);
var linkFunc = function(d) {
var source = {
x: d.source.x,
y: d.source.y + (rectH / 2)
};
var target = {
x: d.target.x + (rectW / 2),
y: d.target.y
};
// This is where the line bends
var inflection = {
x: target.x,
y: source.y
};
var radius = 5;
var result = "M" + source.x + ',' + source.y;
if (source.x < target.x) {
// Child is to the right of the parent
result += ' H' + (inflection.x - radius);
} else {
result += ' H' + (inflection.x + radius);
}
// Curve the line at the bend slightly
result += ' Q' + inflection.x + ',' + inflection.y + ' ' + inflection.x + ',' + (inflection.y + radius);
result += 'V' + target.y;
return result;
}
var svg = d3.select("body").append("svg").attr("width", 1000).attr("height", 1000)
.call(zm = d3.behavior.zoom().scaleExtent([1, 3]).on("zoom", redraw)).append("g")
.attr("transform", "translate(" + 350 + "," + 20 + ")");
//necessary so that zoom knows where to zoom and unzoom from
zm.translate([350, 20]);
// new part
var plusButton = svg
.append('g')
.classed('button', true)
.classed('hide', true)
.on('click', function() {
console.log("CLICKED");
});
plusButton
.append('rect')
.attr('transform', 'translate(-8, -8)') // center the button inside the `g`
.attr('width', 16)
.attr('height', 16)
.attr('rx', 2);
plusButton
.append('path')
.attr('d', 'M-6 0 H6 M0 -6 V6');
var rectangleShape = svg.append('g')
.classed('button', true)
.classed('hide', true)
.on('click', function() {
removeAllButtonElements();
})
rectangleShape
.append('rect')
.attr('width', 40)
.attr('height', 20)
.style('fill', 'orange');
var diamondImage = svg.append('g')
.classed('button', true)
.classed('hide', true)
.classed('scale', true)
.on('click', function() {
addElement(selectedNode);
console.log("Clicked on Diamond");
console.log("set hide to true");
removeAllButtonElements();
});
diamondImage
.append('path')
.attr('d', 'M 20 0 40 20 20 40 0 20 Z')
.style("fill", 'orange');
var rectangleShapeFalse = svg.append('g')
.classed('button', true)
.classed('hide', true)
.on('click', function() {
console.log("rectangle clicked");
removeAllButtonElements();
})
rectangleShapeFalse
.append('rect')
.attr('width', 40)
.attr('height', 20)
.style('fill', 'orange');
var diamondImageFalse = svg.append('g')
.classed('button', true)
.classed('hide', true)
.classed('scale', true)
.on('click', function() {
console.log("Clicked on Diamond");
console.log("set hide to true");
removeAllButtonElements();
});
diamondImageFalse
.append('path')
.attr('d', 'M 20 0 40 20 20 40 0 20 Z')
.style("fill", 'orange');
function removeAllButtonElements() {
plusButton.classed('hide', true);
diamondImage.classed('hide', true);
rectangleShape.classed('hide', true);
diamondImageFalse.classed('hide', true);
rectangleShapeFalse.classed('hide', true);
}
// new part ends.
root.x0 = 0;
root.y0 = height / 2;
function collapse(d) {
if (d.children) {
d._children = d.children;
d._children.forEach(collapse);
d.children = null;
}
}
/* root.children.forEach(collapse); */
update(root);
d3.select("#body").style("height", "800px");
function update(source) {
// Compute the new tree layout.
var nodes = tree.nodes(root).reverse(),
links = tree.links(nodes);
// Normalize for fixed-depth.
nodes.forEach(function(d) {
d.y = d.depth * 180;
});
// Update the nodes…
var node = svg.selectAll("g.node")
.data(nodes, function(d) {
return d.id || (d.id = ++i);
});
// Enter any new nodes at the parent's previous position.
var nodeEnter = node.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) {
return "translate(" + source.x0 + "," + source.y0 + ")";
})
.on("click", click);
nodeEnter.append("rect")
.attr("width", rectW)
.attr("height", rectH)
.attr("stroke", "black")
.attr("stroke-width", 1)
.style("fill", function(d) {
return d._children ? "lightsteelblue" : "#fff";
});
/* nodeEnter.append('path').attr("d", d3.svg.symbol().type( function(d) { return "circle" }) );
*/
nodeEnter.append("text")
.attr("x", rectW / 2)
.attr("y", rectH / 2)
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.text(function(d) {
return d.name;
});
// Transition nodes to their new position.
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
nodeUpdate.select("rect")
.attr("width", rectW)
.attr("height", rectH)
.attr("stroke", "black")
.attr("stroke-width", 1)
.style("fill", function(d) {
return d._children ? "lightsteelblue" : "#fff";
});
nodeUpdate.select("text")
.style("fill-opacity", 1);
// Transition exiting nodes to the parent's new position.
var nodeExit = node.exit().transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + source.x + "," + source.y + ")";
})
.remove();
nodeExit.select("rect")
.attr("width", rectW)
.attr("height", rectH)
//.attr("width", bbox.getBBox().width)""
//.attr("height", bbox.getBBox().height)
.attr("stroke", "black")
.attr("stroke-width", 1);
nodeExit.select("text");
// Update the links…
var link = svg.selectAll("path.link")
.data(links, function(d) {
return d.target.id;
});
// Enter any new links at the parent's previous position.
link.enter().insert("path", "g")
.attr("class", "link")
.attr("x", rectW / 2)
.attr("y", rectH / 2)
.attr("d", linkFunc)
.on('mouseenter', function(d, i) {
// Use the native SVG interface to get the bounding box to
// calculate the center of the path
var bbox = this.getBBox();
var x = bbox.x + bbox.width / 2,
y = bbox.y + bbox.height / 2;
plusButton
.attr('transform', 'translate(' + x + ', ' + y + ')')
.classed('hide', false);
})
.on('mouseleave', function(d, i) {
plusButton
.classed('hide', true);
});
// Transition links to their new position.
link.transition()
.duration(duration)
.attr("d", linkFunc);
// Transition exiting nodes to the parent's new position.
link.exit().transition()
.duration(duration)
.attr("d", linkFunc)
.remove();
// Stash the old positions for transition.
nodes.forEach(function(d) {
d.x0 = d.x;
d.y0 = d.y;
});
}
function click(d) {
console.log(d);
selectedNode = d;
var x = d.x;
var y = d.y + 40;
var m = d.x + 50;
var h = d.y + 20;
diamondImage
.attr('transform', 'translate(' + m + ', ' + h + ')')
.classed('hide', false);
var m = d.x + 60;
var h = d.y - 10;
rectangleShape
.attr('transform', 'translate(' + m + ', ' + h + ')')
.classed('hide', false);
var m = d.x - 40;
var h = d.y + 20;
diamondImageFalse
.attr('transform', 'translate(' + m + ', ' + h + ')')
.classed('hide', false);
var m = d.x - 40;
var h = d.y - 10;
rectangleShapeFalse
.attr('transform', 'translate(' + m + ', ' + h + ')')
.classed('hide', false);
}
//Redraw for zoom
function redraw() {
//console.log("here", d3.event.translate, d3.event.scale);
svg.attr("transform",
"translate(" + d3.event.translate + ")" +
" scale(" + d3.event.scale + ")");
}
function addElement(d) {
console.log(d);
d.children = [];
d.children.push(emptyDecisionBox);
update(root);
}
.node {
cursor: pointer;
}
.node circle {
fill: #fff;
stroke: steelblue;
stroke-width: 1.5px;
}
.node text {
font: 10px sans-serif;
}
.link {
fill: none;
stroke: #ccc;
stroke-width: 1.5px;
}
body {
overflow: hidden;
}
.button>path {
stroke: blue;
stroke-width: 1.5;
}
.button>rect {
fill: #ddd;
stroke: grey;
stroke-width: 1px;
}
.hide {
display: none;
opacity: 0 !important;
pointer-events: none;
}
.link:hover {
cursor: pointer;
stroke-width: 8px;
}
.scale {
/* transform: scale(0.4); */
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
<div class="tree-diagram"></div>
Now, there are some problems with your code in general.
You set the attributes of items you're going to delete, or items that have already been set. Remember that every node that is on the page has already been entered once, so when you set their width/height/stroke, there is no reason to ever set it again, unless it changes. Similarly, there is no reason to use nodeUpdate.select('rect') and then set all the values all over again, just remove that code, it does nothing and leads to clutter.
You also don't select by class, but by tag. That will give you problems as soon as you start with paths. Use classes instead!
Here is a simple parallel coordinate in d3 V4
http://plnkr.co/edit/Ejg7CI7STPqXKB2tot51?p=preview
It is similar to https://bl.ocks.org/jasondavies/1341281 , which is in V3.
Following are the steps to reproduce the issue:
Step1. Brush some area (say 0.8 to 0.4) in column1....
Step2. Brush some area (say 0.7 to 0.4) in column3....
Step3. Now drag the axis column3 to the position of column2. (So basically axis ordering will get changed, from Column1, 2 , 3, 4 .. it will change to column1, 3 ,2, 4.
Step4. Brush column3 (which is now next to column1) again. You will see no paths are being drawn.
Any help would be appreciated.
Thanks
<!DOCTYPE html>
<meta charset="utf-8">
<style>
svg {
font: 10px sans-serif;
}
.background path {
fill: none;
stroke: #ddd;
stroke-opacity: .4;
shape-rendering: crispEdges;
}
.foreground path {
fill: none;
stroke: steelblue;
stroke-opacity: .7;
}
.brush .extent {
fill-opacity: .3;
stroke: #fff;
shape-rendering: crispEdges;
}
.axis line,
.axis path {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.axis text {
text-shadow: 0 1px 0 #fff, 1px 0 0 #fff, 0 -1px 0 #fff, -1px 0 0 #fff;
cursor: move;
}
</style>
<body>
<script src="http://d3js.org/d3.v4.min.js"></script>
<script>
var margin = {top: 30, right: 10, bottom: 10, left: 10},
width = 600 - margin.left - margin.right,
height = 200 - margin.top - margin.bottom;
var x = d3.scalePoint().rangeRound([0, width]).padding(1),
y = {},
dragging = {};
var line = d3.line(),
//axis = d3.axisLeft(x),
background,
foreground,
extents;
var container = d3.select("body").append("div")
.attr("class", "parcoords")
.style("width", width + margin.left + margin.right + "px")
.style("height", height + margin.top + margin.bottom + "px");
var svg = container.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var quant_p = function(v){return (parseFloat(v) == v) || (v == "")};
d3.json("convertcsvSO.json", function(error, cars) {
dimensions = d3.keys(cars[0]);
x.domain(dimensions);
dimensions.forEach(function(d) {
var vals = cars.map(function(p) {return p[d];});
if (vals.every(quant_p)){
y[d] = d3.scaleLinear()
.domain(d3.extent(cars, function(p) {
return +p[d]; }))
.range([height, 0])
}
else{
vals.sort();
y[d] = d3.scalePoint()
.domain(vals.filter(function(v, i) {return vals.indexOf(v) == i;}))
.range([height, 0],1);
}
})
extents = dimensions.map(function(p) { return [0,0]; });
// Add grey background lines for context.
background = svg.append("g")
.attr("class", "background")
.selectAll("path")
.data(cars)
.enter().append("path")
.attr("d", path);
// Add blue foreground lines for focus.
foreground = svg.append("g")
.attr("class", "foreground")
.selectAll("path")
.data(cars)
.enter().append("path")
.attr("d", path);
// Add a group element for each dimension.
var g = svg.selectAll(".dimension")
.data(dimensions)
.enter().append("g")
.attr("class", "dimension")
.attr("transform", function(d) { return "translate(" + x(d) + ")"; })
.call(d3.drag()
.subject(function(d) { return {x: x(d)}; })
.on("start", function(d) {
dragging[d] = x(d);
background.attr("visibility", "hidden");
})
.on("drag", function(d) {
dragging[d] = Math.min(width, Math.max(0, d3.event.x));
foreground.attr("d", path);
dimensions.sort(function(a, b) { return position(a) - position(b); });
x.domain(dimensions);
g.attr("transform", function(d) { return "translate(" + position(d) + ")"; })
})
.on("end", function(d) {
delete dragging[d];
transition(d3.select(this)).attr("transform", "translate(" + x(d) + ")");
transition(foreground).attr("d", path);
background
.attr("d", path)
.transition()
.delay(500)
.duration(0)
.attr("visibility", null);
}));
// Add an axis and title.
var g = svg.selectAll(".dimension");
g.append("g")
.attr("class", "axis")
.each(function(d) { d3.select(this).call(d3.axisLeft(y[d]));})
//text does not show up because previous line breaks somehow
.append("text")
.attr("fill", "black")
.style("text-anchor", "middle")
.attr("y", -9)
.text(function(d) { return d; });
// Add and store a brush for each axis.
g.append("g")
.attr("class", "brush")
.each(function(d) {
if(y[d].name == 'r'){
// console.log(this);
d3.select(this).call(y[d].brush = d3.brushY().extent([[-8, 0], [8,height]]).on("start", brushstart).on("brush", brush_parallel_chart));
}
})
.selectAll("rect")
.attr("x", -8)
.attr("width", 16);
}); // closing
function position(d) {
var v = dragging[d];
return v == null ? x(d) : v;
}
function transition(g) {
return g.transition().duration(500);
}
// Returns the path for a given data point.
function path(d) {
return line(dimensions.map(function(p) { return [position(p), y[p](d[p])]; }));
}
// brush start function
function brushstart() {
d3.event.sourceEvent.stopPropagation();
}
// Handles a brush event, toggling the display of foreground lines.
function brush_parallel_chart() {
for(var i=0;i<dimensions.length;++i){
if(d3.event.target==y[dimensions[i]].brush) {
extents[i]=d3.event.selection.map(y[dimensions[i]].invert,y[dimensions[i]]);
}
}
foreground.style("display", function(d) {
return dimensions.every(function(p, i) {
if(extents[i][0]==0 && extents[i][0]==0) {
return true;
}
return extents[i][1] <= d[p] && d[p] <= extents[i][0];
}) ? null : "none";
});
}
</script>
In the drag callbacks, the dimensions are being sorted BUT the extents aren't. I've added a few lines that sorts extents array based on the new dimensions (by using origDimensions which is the original array)
Here's a fork of your plunkr: http://plnkr.co/edit/DquAXNv2mbbok7ssNuoX?p=preview
Relevant code:
var origDimensions = dimensions.slice(0);
And within the dragend callback:
// one way of sorting the extents array based on dimensions
var new_extents = [];
for(var i=0;i<dimensions.length;++i){
new_extents.push(extents[origDimensions.indexOf(dimensions[i])]);
}
extents = new_extents;
origDimensions = dimensions.slice(0); // setting origDimensions to the new array
Hope this helps. (and btw seems like the brushstart is empty which leads to showing NO curves on brush reset - try resetting brush on any axis).
I have a scatter plot matrix for which I need a tooltip. I tried using the following code, but then, it gives me tooltips at random points and not at the exact cells.
Can someone tell me where am I going wrong ? Or is not possible to generate a tooltip for my data?
<!DOCTYPE html>
<meta charset="utf-8">
<style>
svg {
font: 10px sans-serif;
padding: 10px;
}
.axis,
.frame {
shape-rendering: crispEdges;
}
.axis line {
stroke: #ddd;
}
.axis path {
display: none;
}
.frame {
fill: none;
stroke: #aaa;
}
circle {
fill-opacity: .7;
}
circle.hidden {
fill: #ccc !important;
}
.extent {
fill: #000;
fill-opacity: .125;
stroke: #fff;
}
</style>
<body>
<div id="chart3"> </div>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://labratrevenge.com/d3-tip/javascripts/d3.tip.v0.6.3.js"></script>
<script>
var width = 419,
size = 130,
padding = 19.5,
height = 313;
var x = d3.scale.linear().domain([0,100])
.range([padding / 2, size - padding / 2]);
var y = d3.scale.linear().domain([0,1])
.range([size - padding / 2, padding / 2]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.ticks(5);
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.ticks(5);
var color = d3.scale.ordinal()
.domain(['no chemo', 'induction', 'induction+chemoRT', 'concurrent'])
.range(['#ffae19', '#4ca64c', '#4682B4', '#c51b8a']);
var tip = d3.tip()
.attr('class', 'd3-tip')
.offset([50,70])
.html(function (d) {
var coordinates = d3.mouse(this);
xValue = x.invert(coordinates[0]);
yValue = y.invert(coordinates[1]);
return "<strong> Age Of Patient " + d3.format(".2f")(xValue * 100)+
" <br/> Probability of Survival : " + d3.format(".2f")(yValue*100) + " % </strong>";
});
d3.csv("SurvivalProbability.csv", function (error, data) {
if (error)
throw error;
var domainByTrait = {},
traits = d3.keys(data[0]).filter(function (d) {
return (d == 'AgeAtTx' || d == 'Probability of Survival')
}),
n = traits.length;
traits.forEach(function (trait) {
domainByTrait[trait] = d3.extent(data, function (d) {
return d[trait];
});
});
xAxis.tickSize(size * n);
yAxis.tickSize(-size * n);
var brush = d3.svg.brush()
.x(x)
.y(y)
.on("brushstart", brushstart)
.on("brush", brushmove)
.on("brushend", brushend);
var svg = d3.select("#chart3").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + padding + "," + padding / 2 + ")");
svg.call(tip);
svg.selectAll(".x.axis")
.data(traits)
.enter().append("g")
.attr("class", "x axis")
.attr("transform", function (d, i) {
return "translate(" + (n - i - 1) * size + ",0)";
})
.each(function (d) {
x.domain(domainByTrait[d]);
d3.select(this).call(xAxis);
});
svg.selectAll(".y.axis")
.data(traits)
.enter().append("g")
.attr("class", "y axis")
.attr("transform", function (d, i) {
return "translate(0," + i * size + ")";
})
.each(function (d) {
y.domain(domainByTrait[d]);
d3.select(this).call(yAxis);
});
var cell = svg.selectAll(".cell")
.data(cross(traits, traits))
.enter().append("g")
.attr("class", "cell")
.attr("transform", function (d) {
return "translate(" + (n - d.i - 1) * size + "," + d.j * size + ")";
})
.each(plot)
.on('mouseover', tip.show)
.on('mouseout', tip.hide);
// Titles for the diagonal.
cell.filter(function (d) {
return d.i === d.j;
}).append("text")
.attr("x", padding)
.attr("y", padding)
.attr("dy", ".71em")
.text(function (d) {
return d.x;
});
cell.call(brush);
function plot(p) {
var cell = d3.select(this);
x.domain(domainByTrait[p.x]);
y.domain(domainByTrait[p.y]);
cell.append("rect")
.attr("class", "frame")
.attr("x", padding / 2)
.attr("y", padding / 2)
.attr("width", size - padding)
.attr("height", size - padding);
cell.selectAll("circle")
.data(data)
.enter().append("circle")
.attr("cx", function (d) {
return x(d[p.x]);
})
.attr("cy", function (d) {
return y(d[p.y]);
})
.attr("r", 5)
.style("fill", function (d) {
return color(d.Chemotherapy);
});
}
var brushCell;
// Clear the previously-active brush, if any.
function brushstart(p) {
if (brushCell !== this) {
d3.select(brushCell).call(brush.clear());
x.domain(domainByTrait[p.x]);
y.domain(domainByTrait[p.y]);
brushCell = this;
}
}
// Highlight the selected circles.
function brushmove(p) {
var e = brush.extent();
svg.selectAll("circle").classed("hidden", function (d) {
return e[0][0] > d[p.x] || d[p.x] > e[1][0]
|| e[0][1] > d[p.y] || d[p.y] > e[1][1];
});
}
// If the brush is empty, select all circles.
function brushend() {
if (brush.empty())
svg.selectAll(".hidden").classed("hidden", false);
}
function cross(a, b) {
var c = [], n = a.length, m = b.length, i, j;
for (i = - 1; ++i < n; )
for (j = - 1; ++j < m; )
c.push({x: a[i], i: i, y: b[j], j: j});
return c;
}
d3.select(self.frameElement).style("height", size * n + padding + 20 + "px");
var legendRectSize = 10;
var legendSpacing = 10;
var legend = svg.append("g")
.selectAll("g")
.data(color.domain())
.enter()
.append('g')
.attr('class', 'legend')
.attr('transform', function (d, i) {
var height = legendRectSize;
var x = 2 * size;
var y = (i * height) + 120;
return 'translate(' + x + ',' + y + ')';
});
legend.append('rect')
.attr('width', legendRectSize)
.attr('height', legendRectSize)
.style('fill', color)
.style('stroke', color);
legend.append('text')
.attr('x', legendRectSize + legendSpacing)
.attr('y', legendSpacing)
.text(function (d) {
return d;
});
});
</script>
A screenshot of my data - Survival Probability.csv
Ethnicity,AgeAtTx,Site,Tcategory,Nodal_Disease,ecog,Chemotherapy,Local_Therapy,Probability of Survival,KM OS,OS (months),sex
white,65.93972603,supraglottic,T3,N+,0,no chemo,LP/RT alone,0.366190068,0,112.9,Female
white,69.42465753,supraglottic,T3,N+,0,induction,PLRT,0.396018836,0,24.1,Male
white,68.14246575,supraglottic,T3,N0,3,no chemo,LP/RT alone,0.439289384,0,3.566666667,Female
white,40.30410959,supraglottic,T3,N+,1,no chemo,LP/RT alone,0.512773973,1,226.3,Male
white,47.96438356,supraglottic,T3,N+,0,no chemo,PLRT,0.472208904,0,9.6,Female
white,70.3369863,supraglottic,T3,N+,0,no chemo,LP/RT alone,0.324965753,0,25.26666667,Male
white,60.50136986,supraglottic,T3,N+,2,no chemo,LP/RT alone,0.323424658,0,9.5,Female
white,60.72328767,supraglottic,T3,N+,1,no chemo,LP/RT alone,0.321344178,0,15.03333333,Male
white,59.36986301,supraglottic,T3,N0,1,induction,LP/chemoRT,0.646532534,0,4.5,Male
other,57.64931507,supraglottic,T3,N+,1,concurrent,LP/chemoRT,0.662662671,1,52.73333333,Male
This is an interesting situation. It boils down essentially to element append order and mouse-events. First, let's fix the obvious. You want a tooltip on each circle, so you shouldn't be calling tip.show when you mouse over a cell, but on the circles:
cell.selectAll("circle")
.data(data)
.enter().append("circle")
.attr("cx", function(d) {
return x(d[p.x]);
})
.attr("cy", function(d) {
return y(d[p.y]);
})
.attr("r", 5)
.style("fill", function(d) {
return color(d.Chemotherapy);
})
.on('mouseover', tip.show)
.on('mouseout', tip.hide);
But you'll notice with this change, we don't receive the events on our circles. This is because svg.brush is placing a rect over each cell so that you can select with the extent, and it's receiving the mouse events. So to fix that we change the order of drawing to brush then circle:
var cell = svg.selectAll(".cell")
.data(cross(traits, traits))
.enter().append("g")
.attr("class", "cell")
.attr("transform", function(d) {
return "translate(" + (n - d.i - 1) * size + "," + d.j * size + ")";
});
// add the brush stuff
cell.call(brush);
// now the circles
cell.each(plot);
But we still have a problem. We've got one more rect on top of our circles, the frame rect. Since we don't care about mouse events on it just do a simple:
.style("pointer-events", "none");
Putting this all together:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
svg {
font: 10px sans-serif;
padding: 10px;
}
.axis,
.frame {
shape-rendering: crispEdges;
}
.axis line {
stroke: #ddd;
}
.axis path {
display: none;
}
.frame {
fill: none;
stroke: #aaa;
}
circle {
fill-opacity: .7;
}
circle.hidden {
fill: #ccc !important;
}
.extent {
fill: #000;
fill-opacity: .125;
stroke: #fff;
}
</style>
<body>
<div id="chart3"> </div>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://labratrevenge.com/d3-tip/javascripts/d3.tip.v0.6.3.js"></script>
<script>
var width = 419,
size = 130,
padding = 19.5,
height = 313;
var x = d3.scale.linear().domain([0, 100])
.range([padding / 2, size - padding / 2]);
var y = d3.scale.linear().domain([0, 1])
.range([size - padding / 2, padding / 2]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.ticks(5);
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.ticks(5);
var color = d3.scale.ordinal()
.domain(['no chemo', 'induction', 'induction+chemoRT', 'concurrent'])
.range(['#ffae19', '#4ca64c', '#4682B4', '#c51b8a']);
var tip = d3.tip()
.attr('class', 'd3-tip')
.offset([50, 70])
.html(function(d) {
console.log(d)
var coordinates = d3.mouse(this);
xValue = x.invert(coordinates[0]);
yValue = y.invert(coordinates[1]);
return "<strong> Age Of Patient " + d3.format(".2f")(xValue * 100) +
" <br/> Probability of Survival : " + d3.format(".2f")(yValue * 100) + " % </strong>";
});
//d3.csv("data.csv", function(error, data) {
// if (error)
// throw error;
var data = [{"Ethnicity":"white","AgeAtTx":"65.93972603","Site":"supraglottic","Tcategory":"T3","Nodal_Disease":"N+","ecog":"0","Chemotherapy":"no chemo","Local_Therapy":"LP/RT alone","Probability of Survival":"0.366190068","KM OS":"0","OS (months)":"112.9","sex":"Female"},{"Ethnicity":"white","AgeAtTx":"69.42465753","Site":"supraglottic","Tcategory":"T3","Nodal_Disease":"N+","ecog":"0","Chemotherapy":"induction","Local_Therapy":"PLRT","Probability of Survival":"0.396018836","KM OS":"0","OS (months)":"24.1","sex":"Male"},{"Ethnicity":"white","AgeAtTx":"68.14246575","Site":"supraglottic","Tcategory":"T3","Nodal_Disease":"N0","ecog":"3","Chemotherapy":"no chemo","Local_Therapy":"LP/RT alone","Probability of Survival":"0.439289384","KM OS":"0","OS (months)":"3.566666667","sex":"Female"},{"Ethnicity":"white","AgeAtTx":"40.30410959","Site":"supraglottic","Tcategory":"T3","Nodal_Disease":"N+","ecog":"1","Chemotherapy":"no chemo","Local_Therapy":"LP/RT alone","Probability of Survival":"0.512773973","KM OS":"1","OS (months)":"226.3","sex":"Male"},{"Ethnicity":"white","AgeAtTx":"47.96438356","Site":"supraglottic","Tcategory":"T3","Nodal_Disease":"N+","ecog":"0","Chemotherapy":"no chemo","Local_Therapy":"PLRT","Probability of Survival":"0.472208904","KM OS":"0","OS (months)":"9.6","sex":"Female"},{"Ethnicity":"white","AgeAtTx":"70.3369863","Site":"supraglottic","Tcategory":"T3","Nodal_Disease":"N+","ecog":"0","Chemotherapy":"no chemo","Local_Therapy":"LP/RT alone","Probability of Survival":"0.324965753","KM OS":"0","OS (months)":"25.26666667","sex":"Male"},{"Ethnicity":"white","AgeAtTx":"60.50136986","Site":"supraglottic","Tcategory":"T3","Nodal_Disease":"N+","ecog":"2","Chemotherapy":"no chemo","Local_Therapy":"LP/RT alone","Probability of Survival":"0.323424658","KM OS":"0","OS (months)":"9.5","sex":"Female"},{"Ethnicity":"white","AgeAtTx":"60.72328767","Site":"supraglottic","Tcategory":"T3","Nodal_Disease":"N+","ecog":"1","Chemotherapy":"no chemo","Local_Therapy":"LP/RT alone","Probability of Survival":"0.321344178","KM OS":"0","OS (months)":"15.03333333","sex":"Male"},{"Ethnicity":"white","AgeAtTx":"59.36986301","Site":"supraglottic","Tcategory":"T3","Nodal_Disease":"N0","ecog":"1","Chemotherapy":"induction","Local_Therapy":"LP/chemoRT","Probability of Survival":"0.646532534","KM OS":"0","OS (months)":"4.5","sex":"Male"},{"Ethnicity":"other","AgeAtTx":"57.64931507","Site":"supraglottic","Tcategory":"T3","Nodal_Disease":"N+","ecog":"1","Chemotherapy":"concurrent","Local_Therapy":"LP/chemoRT","Probability of Survival":"0.662662671","KM OS":"1","OS (months)":"52.73333333","sex":"Male"}];
var domainByTrait = {},
traits = d3.keys(data[0]).filter(function(d) {
return (d == 'AgeAtTx' || d == 'Probability of Survival')
}),
n = traits.length;
traits.forEach(function(trait) {
domainByTrait[trait] = d3.extent(data, function(d) {
return d[trait];
});
});
xAxis.tickSize(size * n);
yAxis.tickSize(-size * n);
var brush = d3.svg.brush()
.x(x)
.y(y)
.on("brushstart", brushstart)
.on("brush", brushmove)
.on("brushend", brushend);
var svg = d3.select("#chart3").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + padding + "," + padding / 2 + ")");
svg.call(tip);
svg.selectAll(".x.axis")
.data(traits)
.enter().append("g")
.attr("class", "x axis")
.attr("transform", function(d, i) {
return "translate(" + (n - i - 1) * size + ",0)";
})
.each(function(d) {
x.domain(domainByTrait[d]);
d3.select(this).call(xAxis);
});
svg.selectAll(".y.axis")
.data(traits)
.enter().append("g")
.attr("class", "y axis")
.attr("transform", function(d, i) {
return "translate(0," + i * size + ")";
})
.each(function(d) {
y.domain(domainByTrait[d]);
d3.select(this).call(yAxis);
});
var cell = svg.selectAll(".cell")
.data(cross(traits, traits))
.enter().append("g")
.attr("class", "cell")
.attr("transform", function(d) {
return "translate(" + (n - d.i - 1) * size + "," + d.j * size + ")";
});
cell.call(brush);
cell.each(plot);
// Titles for the diagonal.
cell.filter(function(d) {
return d.i === d.j;
}).append("text")
.attr("x", padding)
.attr("y", padding)
.attr("dy", ".71em")
.text(function(d) {
return d.x;
});
function plot(p) {
var cell = d3.select(this);
x.domain(domainByTrait[p.x]);
y.domain(domainByTrait[p.y]);
cell.append("rect")
.attr("class", "frame")
.attr("x", padding / 2)
.attr("y", padding / 2)
.attr("width", size - padding)
.attr("height", size - padding)
.style("pointer-events", "none");
cell.selectAll("circle")
.data(data)
.enter().append("circle")
.attr("cx", function(d) {
return x(d[p.x]);
})
.attr("cy", function(d) {
return y(d[p.y]);
})
.attr("r", 5)
.style("fill", function(d) {
return color(d.Chemotherapy);
})
.on('mouseover', tip.show)
.on('mouseout', tip.hide);
}
var brushCell;
// Clear the previously-active brush, if any.
function brushstart(p) {
if (brushCell !== this) {
d3.select(brushCell).call(brush.clear());
x.domain(domainByTrait[p.x]);
y.domain(domainByTrait[p.y]);
brushCell = this;
}
}
// Highlight the selected circles.
function brushmove(p) {
var e = brush.extent();
svg.selectAll("circle").classed("hidden", function(d) {
return e[0][0] > d[p.x] || d[p.x] > e[1][0] || e[0][1] > d[p.y] || d[p.y] > e[1][1];
});
}
// If the brush is empty, select all circles.
function brushend() {
if (brush.empty())
svg.selectAll(".hidden").classed("hidden", false);
}
function cross(a, b) {
var c = [],
n = a.length,
m = b.length,
i, j;
for (i = -1; ++i < n;)
for (j = -1; ++j < m;)
c.push({
x: a[i],
i: i,
y: b[j],
j: j
});
return c;
}
var legendRectSize = 10;
var legendSpacing = 10;
var legend = svg.append("g")
.selectAll("g")
.data(color.domain())
.enter()
.append('g')
.attr('class', 'legend')
.attr('transform', function(d, i) {
var height = legendRectSize;
var x = 2 * size;
var y = (i * height) + 120;
return 'translate(' + x + ',' + y + ')';
});
legend.append('rect')
.attr('width', legendRectSize)
.attr('height', legendRectSize)
.style('fill', color)
.style('stroke', color);
legend.append('text')
.attr('x', legendRectSize + legendSpacing)
.attr('y', legendSpacing)
.text(function(d) {
return d;
});
//});
</script>
Here is my sample js file i need to draw a chord but in eclipse shape rather then a circle. Secondly i need to know that what is a matrix use while creating chord diagram and are we able to draw a chord with simple json file (without using matrix) as mentioned http://www.delimited.io/blog/2013/12/8/chord-diagrams-in-d3 here. Because in every example of chord some matrix is give to draw it. I am new to d3 i need to learn lot of things. Can any one help really appreciate it
var outerRadius = 500 / 2,
innerRadius = outerRadius - 100;
var fill = d3.scale.category20c();
var chord = d3.layout.chord()
.padding(.04)
.sortSubgroups(d3.descending)
.sortChords(d3.descending);
var arc = d3.svg.arc()
.innerRadius(innerRadius)
.outerRadius(innerRadius + 20);
var svg = d3.select('#content').append("svg")
.attr("width", outerRadius * 2)
.attr("height", outerRadius * 2)
.append("g")
.attr("transform", "translate(" + outerRadius + "," + outerRadius +
")");
d3.json("readme.json", function(error, imports) {
if (error) throw error;
var indexByName = d3.map(),
nameByIndex = d3.map(),
matrix = [],
n = 0;
// Returns the Flare package name for the given class name.
function name(name) {
return name.substring(0, name.lastIndexOf(".")).substring(6);
}
// Compute a unique index for each package name.
imports.forEach(function(d) {
if (!indexByName.has(d = name(d.name))) {
nameByIndex.set(n, d);
indexByName.set(d, n++);
}
});
// Construct a square matrix counting package imports.
imports.forEach(function(d) {
var source = indexByName.get(name(d.name)),
row = matrix[source];
if (!row) {
row = matrix[source] = [];
for (var i = -1; ++i < n;) row[i] = 0;
}
d.imports.forEach(function(d) { row[indexByName.get(name(d))]++; });
});
chord.matrix(matrix);
var g = svg.selectAll("g.group")
.data(chord.groups())
.enter().append("svg:g")
.attr("class", "group")
.on("mouseover", fade(.02))
.on("mouseout", fade(.80));
// .on("mouseover", mouseover);
//.on("mouseout", fade(1));
g.append("svg:path")
.style("stroke", "none")
.style("fill", function(d) { return fill(d.index); })
.attr("d", arc);
/* g.append("path")
.style("fill", function(d) { return fill(d.index); })
.style("stroke", function(d) { return fill(d.index); })
.attr("d", arc);*/
g.append("text")
.each(function(d) { d.angle = (d.startAngle + d.endAngle) / 2; })
.attr("dy", ".35em")
.attr("transform", function(d) {
return "rotate(" + (d.angle * 180 / Math.PI - 90) + ")"
+ "translate(" + (innerRadius + 26) + ")"
+ (d.angle > Math.PI ? "rotate(180)" : "");
})
.style("text-anchor", function(d) { return d.angle > Math.PI ? "end" : null; })
.text(function(d) { return nameByIndex.get(d.index); });
svg.selectAll(".chord")
.data(chord.chords)
.enter().append("path")
.attr("class", "chord")
.style("stroke", function(d) { return
d3.rgb(fill(d.source.index)).darker(); })
.style("fill", function(d) { return fill(d.source.index); })
//.style("opacity", 1)
.attr("d", d3.svg.chord().radius(innerRadius));
});
d3.select(self.frameElement).style("height", outerRadius * 5 + "px");
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
#tooltip {
color: white;
opacity: .9;
background: #333;
padding: 5px;
border: 1px solid lightgrey;
border-radius: 5px;
position: absolute;
z-index: 10;
visibility: hidden;
white-space: nowrap;
pointer-events: none;
}
#circle circle {
fill: none;
pointer-events: all;
}
path.group {
fill-opacity: .8;
}
path.chord {
fill-opacity: .8;
stroke: #000;
stroke-width: .25px;
}
#circle:hover path.fade {
display: none;
}
</style>
</head>
<body>
<div id="tooltip"></div>
<script src="lib/d3.js"></script>
<script src="lib/underscore.js"></script>
<script src="js/mapper.js"></script>
<script>
//*******************************************************************
// CREATE MATRIX AND MAP
//*******************************************************************
d3.csv('data/CNV.csv', function (error, data) {
var mpr = chordMpr(data);
mpr
.addValuesToMap('chr_no')
.addValuesToMap('MUT')
mpr .setFilter(function (row, a, b) {
return (row.chr_no === a.name && row.MUT === b.name) ||
(row.chr_no === b.name && row.MUT === a.name)
})
.setAccessor(function (recs, a, b) {
if (!recs[0]) return 0;
return recs[0].MUT === a.name ? +recs[0].chr_start :
+recs[0].chr_stop ;
});
drawChords(mpr.getMatrix(), mpr.getMap());
});
//*******************************************************************
// DRAW THE CHORD DIAGRAM
//*******************************************************************
function drawChords (matrix, mmap) {
var w = 980, h = 800, r1 = h / 2, r0 = r1 - 110;
var fill = d3.scale.ordinal()
.range(['#c7b570','#c6cdc7','#335c64','#768935',
'#507282','#5c4a56','#aa7455','#574109','#837722',
'#73342d','#0a5564','#9c8f57','#7895a4','#4a5456',
'#b0a690','#0a3542',]);
var chord = d3.layout.chord()
.padding(.04)
.sortSubgroups(d3.descending)
.sortChords(d3.descending);
var arc = d3.svg.arc()
.innerRadius(r0)
.outerRadius(r0 + 20);
var svg = d3.select("body").append("svg:svg")
.attr("width", w)
.attr("height", h)
.append("svg:g")
.attr("id", "circle")
.attr("transform", "translate(" + w / 2 + "," + h / 2 + ")");
svg.append("circle")
.attr("r", r0 + 20);
var rdr = chordRdr(matrix, mmap);
chord.matrix(matrix);
var g = svg.selectAll("g.group")
.data(chord.groups())
.enter().append("svg:g")
.attr("class", "group")
.on("mouseover", mouseover)
.on("mouseout", function (d) {
d3.select("#tooltip").style("visibility", "hidden") });
g.append("svg:path")
.style("stroke", "black")
.style("fill", function(d) { return fill(rdr(d).gname); })
.attr("d", arc);
g.append("svg:text")
.each(function(d) { d.angle = (d.startAngle + d.endAngle) / 2;
})
.attr("dy", ".35em")
.style("font-family", "helvetica, arial, sans-serif")
.style("font-size", "9px")
.attr("text-anchor", function(d) { return d.angle > Math.PI ?
"end" : null; })
.attr("transform", function(d) {
return "rotate(" + (d.angle * 180 / Math.PI - 90) + ")"
+ "translate(" + (r0 + 26) + ")"
+ (d.angle > Math.PI ? "rotate(180)" : "");
})
.text(function(d) { return rdr(d).gname; });
var chordPaths = svg.selectAll("path.chord")
.data(chord.chords())
.enter().append("svg:path")
.attr("class", "chord")
.style("stroke", function(d) { return
d3.rgb(fill(rdr(d).sname)).darker(); })
.style("fill", function(d) { return fill(rdr(d).sname); })
.attr("d", d3.svg.chord().radius(r0))
.on("mouseover", function (d) {
d3.select("#tooltip")
.style("visibility", "visible")
.html(chordTip(rdr(d)))
.style("top", function () { return (d3.event.pageY -
170)+"px"})
.style("left", function () { return (d3.event.pageX -
100)+"px";})
})
.on("mouseout", function (d) {
d3.select("#tooltip").style("visibility", "hidden") });
function chordTip (d) {
var p = d3.format(".0%"), q = d3.format("0d")
return "Choromosome information:<br/>"
+ q(d.sname) + " overlap with " + d.tname +" " +
"<br/>chromosome starts at"+" "+ d.sdata +" " +
"<br/>chromosome ends at"+ " "+ d.tdata
}
}
function mouseover(d, i) {
d3.select("#tooltip")
.style("visibility", "visible")
.html(groupTip(rdr(d)))
.style("top", function () { return (d3.event.pageY - 80)+"px"})
.style("left", function () { return (d3.event.pageX - 130)+"px";})
chordPaths.classed("fade", function(p) {
return p.source.index != i
&& p.target.index != i;
});
}
}
</script>