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 - di - 1) * size + "," + dj * size + ")"; }); cell.call(brush); cell.each(plot); // Titles for the diagonal. cell.filter(function(d) { return di === dj; }).append("text") .attr("x", padding) .attr("y", padding) .attr("dy", ".71em") .text(function(d) { return dx; }); function plot(p) { var cell = d3.select(this); x.domain(domainByTrait[px]); y.domain(domainByTrait[py]); 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[px]); }) .attr("cy", function(d) { return y(d[py]); }) .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[px]); y.domain(domainByTrait[py]); 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[px] || d[px] > e[1][0] || e[0][1] > d[py] || d[py] > 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>
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.