I'm creating a population pyramid with D3.js using with a template taken from the visualisation group at Stanford. I can't figure out why initially I get tooltips and markers on the graph but once I transition to a different years these are no longer available. Anyone know how I could fix this? Apologies for the length of the code, I'm pretty new to D3/JavaScript so just figured I'd put everything up rather than trying to pinpoint where the error is.
Thanks
Northern Ireland Teacher Population Pyramid
body {
font: 12px sans-serif;
margin: 0;
padding: 5px;
color: #888;
}
h1 {
padding-left: 10px;
margin-bottom: 2px;
color: #333;
}
.source {
padding-left: 10px;
}
.source a, .source a:hover {
color: #888;
}
.label {
position: absolute;
top: 60px;
left: 15px;
font-size: 48px;
font-weight: bold;
color: #dedede;
}
.break {
border-bottom: solid 1px #dedede;
margin: 10px 15px 2px 15px;
width: 545px;
}
.years, .controls {
padding-top: 10px;
padding-left: 15;
width: 540;
text-align: center;
font-size: 12px;
}
.years span, .controls span {
padding-left: 2px;
padding-right: 2px;
}
.years .title {
font-size: 13px;
font-variant: small-caps;
letter-spacing: 1;
}
.years a, .controls a {
color: #888;
text-decoration: none;
}
.years a:hover, .controls a:hover {
color: #000;
text-decoration: underline;
}
.years a.y1890 {
color: #bbb;
}
.years a.active {
color: #000;
}
.controls a {
font-variant: small-caps;
letter-spacing: 1;
text-decoration: none;
}
svg {
shape-rendering: crispEdges;
}
</style>
</head>
<body>
<h1>Northern Ireland Teachers Population Pyramid, 2010-2014</h1>
<div class="source">
Source: Teachers' Payroll and Pensions Administration System.
</div><script type="text/javascript">
document.onkeydown = function(event) {
var y = year;
switch (event.keyCode) {
case 37: // left arrow
y = Math.max(2010, year-1);
break;
case 39: // right arrow
y = Math.min(2014, year+1);
break;
case 32: // space bar
toggle();
return;
}
if (y != year) goto(y);
};
function isYear(d) { return d.year == year; }
function linkClass(y) { return "y"+y.toFixed(0) + (y==year?" active":""); }
function tooltipText(d) {
return d3.format(",")(d.people)
+ " " + (d.sex==1?"men":"women")
+ " aged " + (d.age==60?"60+":d.age+"-"+(d.age+4))
+ " in " + d.year;
}
function barWidth(d) { return x1(d.people); }
function goto(yr, dur) {
dur = dur || 300;
var old = year;
year = yr;
label.text(year);
div.selectAll("span.link a")
.attr("class", linkClass);
var fb = vis.selectAll("rect.female").data(fdat.filter(isYear), {
nodeKey: function(node) { return node.getAttribute("id"); },
dataKey: function(data) { return "f"+(data.year - data.age); }
});
fb.enter("svg:rect")
.attr("id", function(d) { return "f"+(d.year - d.age); })
.attr("class", "female")
.attr("fill", "pink")
.attr("transform", lTransform)
.attr("width", function(d) { return x1(d.people); })
.attr("y", yr>old ? 20 : -20)
.attr("height", y.rangeBand())
.attr("opacity", 0.0001)
.transition()
.duration(dur)
.attr("y", 0)
.attr("opacity", 1);
fb.exit().transition()
.duration(dur)
.attr("y", yr>old ? -20 : 30)
.attr("opacity", 0.0001)
.each("end", function() { d3.select(this).remove(); });
fb.transition()
.duration(dur)
.attr("transform", lTransform)
.attr("y", 0)
.attr("width", function(d) { return x1(d.people); })
.attr("opacity", 1);
fb.selectAll("title").text(tooltipText);
var mb = vis.selectAll("rect.male").data(mdat.filter(isYear), {
nodeKey: function(node) { return node.getAttribute("id"); },
dataKey: function(data) { return "m"+(data.year - data.age); }
});
mb.enter("svg:rect")
.attr("id", function(d) { return "m"+(d.year - d.age); })
.attr("class", "male")
.attr("fill", "steelblue")
.attr("transform", rTransform)
.attr("width", function(d) { return x1(d.people); })
.attr("y", yr>old ? 20 : -20)
.attr("height", y.rangeBand())
.attr("opacity", 0.0001)
.transition()
.duration(dur)
.attr("y", 0)
.attr("opacity", 1);
mb.exit().transition()
.duration(dur)
.attr("y", yr>old ? -20 : 30)
.attr("opacity",0.0001)
.each("end", function() { d3.select(this).remove(); });
mb.transition()
.duration(dur)
.attr("transform", rTransform)
.attr("y", 0)
.attr("width", function(d) { return x1(d.people); })
.attr("opacity", 1);
mb.select("title").text(tooltipText);
}
var timer = undefined;
function stop() {
clearInterval(timer);
timer = undefined;
ctrls.select("a.toggle").text("play");
}
function toggle() {
if (!timer) {
play();
} else {
stop();
}
}
function play(rev) {
rev = rev || false;
if (timer) { stop(); }
ctrls.select("a.toggle").text("stop");
var advance = function() {
var y = year + (rev?-1:1)*1;
if (y < 2010 || y > 2013) {
// stop at end points
stop();
return;
} else {
// else advance
goto(y, 800);
}
};
advance();
timer = setInterval(advance, 850);
}
var data = census,
maxp = data.reduce(function(a,b) { return Math.max(a,b.people); }, 0),
mdat = data.filter(function(d) { return d.sex==1; })
.sort(function(a,b) { return b.age - a.age; }),
fdat = data.filter(function(d) { return d.sex==2; })
.sort(function(a,b) { return b.age - a.age; });
var w = 250,
h = 9 * 40,
bins = d3.range(9),
year = 2010,
y = d3.scale.ordinal().domain(bins).rangeBands([0, h], 0.25),
x1 = d3.scale.linear().domain([0, maxp]).range([0, w]),
x2 = d3.scale.linear().domain([0, maxp]).range([w, 0]),
rf = "javascript:return false;";
var label = d3.select("body")
.append("div")
.attr("class", "label")
.text(year.toFixed(0));
var vis = d3.select("body")
.append("svg:svg")
.attr("width", 2*w + 40)
.attr("height", h + 40)
.append("svg:g")
.attr("transform", "translate(20,15)");
// pyramid bar chart
vis.append("svg:g")
.selectAll("text.ages")
.data(bins)
.enter("svg:text")
.filter(function(d) { return d%2==0; })
.attr("class", "ages")
.attr("x", w+15)
.attr("y", function(d) { return y(d) + y.rangeBand() + 7; })
.attr("fill", "#888")
.attr("text-anchor", "middle")
.attr("font-size", "12px")
.text(function(d) { return (60-d*5).toFixed(0); });
var rTransform = function(d,i) {
return "translate("+(w+30)+","+y(i).toFixed(2)+")";
}
var lTransform = function(d,i) {
return "translate("+x2(d.people).toFixed(2)+","+y(i).toFixed(2)+")";
}
var lEnter = function(d,i) {
return "translate("+w+","+y(i).toFixed(2)+")";
}
var mbars = vis.selectAll("rect.male")
.data(mdat.filter(isYear))
.enter("svg:rect")
.attr("id", function(d) { return "m"+(d.year - d.age); })
.attr("class", "male")
.attr("fill", "steelblue")
.attr("transform", rTransform)
.attr("width", barWidth)
.attr("height", y.rangeBand())
.attr("y", 0)
.attr("opacity", 1);
mbars.append("svg:title").text(tooltipText);
var fbars = vis.selectAll("rect.female")
.data(fdat.filter(isYear))
.enter("svg:rect")
.attr("id", function(d) { return "f"+(d.year - d.age); })
.attr("class", "female")
.attr("fill", "pink")
.attr("opacity", 1)
.attr("transform", lTransform)
.attr("width", barWidth)
.attr("height", y.rangeBand())
.attr("y", 0)
.attr("opacity", 1);
fbars.append("svg:title").text(tooltipText);
// animated intro for bars
mbars.attr("width", 0)
.transition()
.duration(100)
.delay(function(d,i) { return 30*i; })
.attr("width", barWidth);
fbars.attr("width", 0)
.attr("transform", lEnter)
.transition()
.duration(100)
.delay(function(d, i) { return 30*i; })
.attr("width", barWidth)
.attr("transform", lTransform);
// age label
vis.append("svg:text")
.attr("x", w+15)
.attr("y", h+8)
.attr("dy", ".71em")
.attr("fill", "#888")
.attr("text-anchor", "middle")
.attr("font-size", "13px")
.attr("font-variant", "small-caps")
.attr("letter-spacing", 1)
.text("age");
// gridlines and labels for right pyramid
var rules1 = vis.selectAll("g.rule1")
.data(x1.ticks(5))
.enter("svg:g")
.filter(function(d) { return d > 0; })
.attr("class", "rule1")
.attr("transform", function(d) { return "translate("+(w+30+x1(d))+",0)";});
rules1.append("svg:line")
.attr("y1", h - 2)
.attr("y2", h + 4)
.attr("stroke", "#bbb");
rules1.append("svg:line")
.attr("y1", 0)
.attr("y2", h)
.attr("stroke", "white")
.attr("stroke-opacity", .3);
rules1.append("svg:text")
.attr("y", h + 9)
.attr("dy", ".71em")
.attr("text-anchor", "middle")
.attr("font-size", "12px")
.attr("fill", "#bbb")
.text(function(d) { return (d).toFixed(0); });
// gridlines and labels for left pyramid
var rules2 = vis.selectAll("g.rule2")
.data(x2.ticks(5))
.enter("svg:g")
.filter(function(d) { return d > 0; })
.attr("class", "rule2")
.attr("transform", function(d) { return "translate("+(x2(d))+",0)";});
rules2.append("svg:line")
.attr("y1", h - 2)
.attr("y2", h + 4)
.attr("stroke", "#bbb");
rules2.append("svg:line")
.attr("y1", 0)
.attr("y2", h)
.attr("stroke", "white")
.attr("stroke-opacity", .3);
rules2.append("svg:text")
.attr("y", h + 9)
.attr("dy", ".71em")
.attr("text-anchor", "middle")
.attr("font-size", "12px")
.attr("fill", "#bbb")
.text(function(d) { return (d).toFixed(0); });
d3.select("body")
.append("div")
.attr("class", "break");
var div = d3.select("body")
.append("div")
.attr("class", "years");
div.append("span")
.attr("class", "title")
.text("year");
var ctrls = d3.select("body")
.append("div")
.attr("class", "controls");
ctrls.append("span").append("a")
.attr("class", "control back")
.attr("href", "javascript:play(true);")
.text("<< ");
ctrls.append("span").append("a")
.attr("class", "control toggle")
.attr("href", "javascript:toggle();")
.text("play");
ctrls.append("span").append("a")
.attr("class", "control forward")
.attr("href", "javascript:play();")
.text(" >>");
div.selectAll("span.link")
.data(d3.range(2010, 2014, 1))
.enter("span")
.attr("class", "link")
.append("a")
.attr("class", linkClass)
.attr("href", function(d) { return d==1890?null:"javascript:goto("+d+");"; })
.text(function(d) { return d.toFixed(0); });
</script>
The trick is to replace
var fb = vis.selectAll("rect.female").data(fdat.filter(isYear), {
nodeKey: function(node) { return node.getAttribute("id"); },
dataKey: function(data) { return "f"+(data.year - data.age); }
});
with
var fb = vis.selectAll("rect.female").data(fdat.filter(isYear));
(And the same for variable mb).
SVG elements are rendered in the order that you append them to the DOM. At the beginning, the grid lines are on top of the bars, but after you redraw the bars, they are on top of the grid lines. You need to append the lines after the bars. If you change the lines from white to another color you can see where they are -- that should help with debugging.
Related
I want make a bar graph where i can show a particular time period represented by bars.
$(document).ready(function() {
render_chart();
});
function render_chart() {
var dataset = {
"colors": [
"#1f77b4",
"#aec7e8",
"#ff7f0e",
],
"gates": [
"Test-Gate"
],
"operators": [
"Operator1",
"Operator2",
"Operator3",
],
"layers": [
[{
"fromHours": "14:20:00",
"toHours": "23:00:00",
"gate": "Test-Gate"
}],
[{
"fromHours": "08:30:00",
"toHours": "11:20:00",
"gate": "Test-Gate"
}],
[{
"fromHours": "16:00:00",
"toHours": "18:00:00",
"gate": "Test-Gate"
}]
]
};
n = dataset["operators"].length;
var today = new Date();
today.setHours(0, 0, 0, 0);
todayMillis = today.getTime();
var layersData = dataset["layers"];
console.log("BEFORE", layersData[0][0]);
layersData.forEach(function(layer) {
layer.forEach(function(innerLayer) {
var fromHourParts = innerLayer.fromHours.split(/:/);
var toHourParts = innerLayer.toHours.split(/:/);
innerLayer.fromHours = new Date();
innerLayer.fromHours.setTime(todayMillis + getTimePeriodMillis(fromHourParts));
innerLayer.toHours = new Date();
innerLayer.toHours.setTime(todayMillis + getTimePeriodMillis(toHourParts));
})
});
console.log("AFTER", dataset["layers"][0][0]);
function getTimePeriodMillis(parts) {
return (parseInt(parts[0], 10) * 60 * 60 * 1000) +
(parseInt(parts[1], 10) * 60 * 1000) +
(parseInt(parts[2], 10) * 1000);
}
function appendExtraZeroToSingleValues(inputValue) {
if (inputValue === 0) {
return inputValue + "0";
}
return inputValue;
}
var parseTime = d3.timeParse("%H:%M");
var margin = {
top: 50,
right: 50,
bottom: 50,
left: 100
},
width = 600 - margin.left - margin.right,
height = 300 - margin.top - margin.bottom;
var svg = d3.select("#groupchart").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 tomorrow = new Date();
tomorrow.setHours(0, 0, 0, 0);
tomorrow.setDate(today.getDate());
var xScale = d3.scaleTime()
.domain([new Date(), new Date()])
.nice(d3.timeDay, 1)
.range([0, width - margin.left]);
var yScale = d3.scaleBand()
.domain(dataset["gates"])
.rangeRound([0, height])
.padding(.08);
var xAxis = d3.axisBottom(xScale)
.ticks(24)
.tickSize(-height)
.tickFormat(d3.timeFormat("%H"));
console.log("X-DOMAIN", xScale.domain());
console.log("X-RANGE", xScale.range());
var yAxis = d3.axisLeft(yScale)
.tickSize(-(width - margin.left))
.tickPadding(5);
var layer = svg.selectAll(".layer")
.data(dataset["layers"])
.enter().append("g")
.attr("class", "layer");
var rect = layer.selectAll("rect")
.data(function(d, i) {
d.map(function(b) {
b.colorIndex = i;
return b;
});
return d;
})
.enter()
.append("rect")
.transition()
.duration(500)
.delay(function(d, i) {
return i * 10;
})
.attr("y", function(d, i, j) {
var k = Array.prototype.indexOf.call(j[i].parentNode.parentNode.childNodes, j[i].parentNode);
return yScale(d.gate) + yScale.bandwidth() / n * k;
})
.attr("height", yScale.bandwidth() / n)
.transition()
.attr("x", function(d) {
console.log(xScale(d.fromHours));
return xScale(d.fromHours);
})
.attr("width", function(d) {
console.log(xScale(d.toHours - d.fromHours));
return xScale(Math.abs(d.toHours - d.fromHours / 36e5));
})
.attr("class", "bar")
.style("fill", function(d) {
return dataset["colors"][d.colorIndex];
});
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.select("g")
.attr("class", "y axis")
.call(yAxis);
var legend = svg.append("g")
.attr("class", "legend");
legend.selectAll('text')
.data(dataset["operators"])
.enter()
.append("rect")
.attr("x", function(d, i) {
return (i * 120) + (width / 3);
})
.attr("y", width / 2.8)
.attr("width", 10)
.attr("height", 10)
.style("fill", function(d, i) {
return dataset["colors"][i];
})
legend.selectAll('text')
.data(dataset["operators"])
.enter()
.append("text")
.attr("x", function(d, i) {
return (i * 120) + (width / 3) + 12;
})
.attr("y", (width / 2.8) + 10)
.text(function(d) {
return d;
});
var tooltip = d3.select("body")
.append('div')
.attr('class', 'tooltip');
tooltip.append('div')
.attr('class', 'gate');
tooltip.append('div')
.attr('class', 'tempRange');
svg.selectAll("rect")
.on('mouseover', function(d, i) {
if (!d.gate) return null;
tooltip.select('.gate').html("<b>" + dataset["operators"][i] + "</b>");
tooltip.select('.tempRange').html(appendExtraZeroToSingleValues(d.fromHours.getHours()) + ":" +
appendExtraZeroToSingleValues(d.fromHours.getMinutes()) + " Hours to " +
appendExtraZeroToSingleValues(d.toHours.getHours()) + ":" +
appendExtraZeroToSingleValues(d.toHours.getMinutes()) + " Hours");
tooltip.style('display', 'block');
tooltip.style('opacity', 2);
})
.on('mousemove', function(d) {
if (!d.gate) return null;
tooltip.style('top', (d3.event.layerY + 10) + 'px')
.style('left', (d3.event.layerX - 25) + 'px');
})
.on('mouseout', function() {
tooltip.style('display', 'none');
tooltip.style('opacity', 0);
});
}
.axis .tick line {
stroke-width: 1;
stroke: rgba(0, 0, 0, 0.2);
}
.axis path,
.axis line {
fill: none;
font: 10px sans-serif;
stroke: #000;
shape-rendering: crispEdges;
}
.legend {
padding: 5px;
font-size: 15px;
font-family: 'Roboto', sans-serif;
background: yellow;
box-shadow: 2px 2px 1px #888;
}
.tooltip {
background: #eee;
box-shadow: 0 0 5px #999999;
color: #333;
font-size: 12px;
left: 130px;
padding: 10px;
position: absolute;
text-align: center;
top: 95px;
z-index: 10;
display: block;
opacity: 0;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Bar Graph</title>
<script
src="https://code.jquery.com/jquery-3.3.1.min.js"
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
crossorigin="anonymous"></script>
<script src="https://d3js.org/d3.v4.min.js"></script>
</head>
<body>
<div id="groupchart" class="chart"></div>
</body>
</html>
Tried like above, but cannot figure out how to do it properly. I messed up and bars does not end inside the graph somehow. The x axis domain is all messed up and also the width of the rects. It works fine if i take another scale. But in time-scale i cannot make it to work.
What i am doing wrong?
Instead of passing the difference of the values to the scale...
scale(a - b)
...pass each value to the scale and subtract them:
scale(a) - scale(b).
In your case:
return xScale(Math.abs(d.toHours)) - xScale(Math.abs(d.fromHours));
Here is the code with that change:
$(document).ready(function() {
render_chart();
});
function render_chart() {
var dataset = {
"colors": [
"#1f77b4",
"#aec7e8",
"#ff7f0e",
],
"gates": [
"Test-Gate"
],
"operators": [
"Operator1",
"Operator2",
"Operator3",
],
"layers": [
[{
"fromHours": "14:20:00",
"toHours": "23:00:00",
"gate": "Test-Gate"
}],
[{
"fromHours": "08:30:00",
"toHours": "11:20:00",
"gate": "Test-Gate"
}],
[{
"fromHours": "16:00:00",
"toHours": "18:00:00",
"gate": "Test-Gate"
}]
]
};
n = dataset["operators"].length;
var today = new Date();
today.setHours(0, 0, 0, 0);
todayMillis = today.getTime();
var layersData = dataset["layers"];
console.log("BEFORE", layersData[0][0]);
layersData.forEach(function(layer) {
layer.forEach(function(innerLayer) {
var fromHourParts = innerLayer.fromHours.split(/:/);
var toHourParts = innerLayer.toHours.split(/:/);
innerLayer.fromHours = new Date();
innerLayer.fromHours.setTime(todayMillis + getTimePeriodMillis(fromHourParts));
innerLayer.toHours = new Date();
innerLayer.toHours.setTime(todayMillis + getTimePeriodMillis(toHourParts));
})
});
console.log("AFTER", dataset["layers"][0][0]);
function getTimePeriodMillis(parts) {
return (parseInt(parts[0], 10) * 60 * 60 * 1000) +
(parseInt(parts[1], 10) * 60 * 1000) +
(parseInt(parts[2], 10) * 1000);
}
function appendExtraZeroToSingleValues(inputValue) {
if (inputValue === 0) {
return inputValue + "0";
}
return inputValue;
}
var parseTime = d3.timeParse("%H:%M");
var margin = {
top: 50,
right: 50,
bottom: 50,
left: 100
},
width = 600 - margin.left - margin.right,
height = 300 - margin.top - margin.bottom;
var svg = d3.select("#groupchart").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 tomorrow = new Date();
tomorrow.setHours(0, 0, 0, 0);
tomorrow.setDate(today.getDate());
var xScale = d3.scaleTime()
.domain([new Date(), new Date()])
.nice(d3.timeDay, 1)
.range([0, width - margin.left]);
var yScale = d3.scaleBand()
.domain(dataset["gates"])
.rangeRound([0, height])
.padding(.08);
var xAxis = d3.axisBottom(xScale)
.ticks(24)
.tickSize(-height)
.tickFormat(d3.timeFormat("%H"));
console.log("X-DOMAIN", xScale.domain());
console.log("X-RANGE", xScale.range());
var yAxis = d3.axisLeft(yScale)
.tickSize(-(width - margin.left))
.tickPadding(5);
var layer = svg.selectAll(".layer")
.data(dataset["layers"])
.enter().append("g")
.attr("class", "layer");
var rect = layer.selectAll("rect")
.data(function(d, i) {
d.map(function(b) {
b.colorIndex = i;
return b;
});
return d;
})
.enter()
.append("rect")
.transition()
.duration(500)
.delay(function(d, i) {
return i * 10;
})
.attr("y", function(d, i, j) {
var k = Array.prototype.indexOf.call(j[i].parentNode.parentNode.childNodes, j[i].parentNode);
return yScale(d.gate) + yScale.bandwidth() / n * k;
})
.attr("height", yScale.bandwidth() / n)
.transition()
.attr("x", function(d) {
console.log(xScale(d.fromHours));
return xScale(d.fromHours);
})
.attr("width", function(d) {
console.log(xScale(d.toHours - d.fromHours));
return xScale(Math.abs(d.toHours)) - xScale(Math.abs(d.fromHours));
})
.attr("class", "bar")
.style("fill", function(d) {
return dataset["colors"][d.colorIndex];
});
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.select("g")
.attr("class", "y axis")
.call(yAxis);
var legend = svg.append("g")
.attr("class", "legend");
legend.selectAll('text')
.data(dataset["operators"])
.enter()
.append("rect")
.attr("x", function(d, i) {
return (i * 120) + (width / 3);
})
.attr("y", width / 2.8)
.attr("width", 10)
.attr("height", 10)
.style("fill", function(d, i) {
return dataset["colors"][i];
})
legend.selectAll('text')
.data(dataset["operators"])
.enter()
.append("text")
.attr("x", function(d, i) {
return (i * 120) + (width / 3) + 12;
})
.attr("y", (width / 2.8) + 10)
.text(function(d) {
return d;
});
var tooltip = d3.select("body")
.append('div')
.attr('class', 'tooltip');
tooltip.append('div')
.attr('class', 'gate');
tooltip.append('div')
.attr('class', 'tempRange');
svg.selectAll("rect")
.on('mouseover', function(d, i) {
if (!d.gate) return null;
tooltip.select('.gate').html("<b>" + dataset["operators"][i] + "</b>");
tooltip.select('.tempRange').html(appendExtraZeroToSingleValues(d.fromHours.getHours()) + ":" +
appendExtraZeroToSingleValues(d.fromHours.getMinutes()) + " Hours to " +
appendExtraZeroToSingleValues(d.toHours.getHours()) + ":" +
appendExtraZeroToSingleValues(d.toHours.getMinutes()) + " Hours");
tooltip.style('display', 'block');
tooltip.style('opacity', 2);
})
.on('mousemove', function(d) {
if (!d.gate) return null;
tooltip.style('top', (d3.event.layerY + 10) + 'px')
.style('left', (d3.event.layerX - 25) + 'px');
})
.on('mouseout', function() {
tooltip.style('display', 'none');
tooltip.style('opacity', 0);
});
}
.axis .tick line {
stroke-width: 1;
stroke: rgba(0, 0, 0, 0.2);
}
.axis path,
.axis line {
fill: none;
font: 10px sans-serif;
stroke: #000;
shape-rendering: crispEdges;
}
.legend {
padding: 5px;
font-size: 15px;
font-family: 'Roboto', sans-serif;
background: yellow;
box-shadow: 2px 2px 1px #888;
}
.tooltip {
background: #eee;
box-shadow: 0 0 5px #999999;
color: #333;
font-size: 12px;
left: 130px;
padding: 10px;
position: absolute;
text-align: center;
top: 95px;
z-index: 10;
display: block;
opacity: 0;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Bar Graph</title>
<script
src="https://code.jquery.com/jquery-3.3.1.min.js"
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
crossorigin="anonymous"></script>
<script src="https://d3js.org/d3.v4.min.js"></script>
</head>
<body>
<div id="groupchart" class="chart"></div>
</body>
</html>
In my scatterplot, I am hovering on the points, to generate a tooltip. But, my X-cordinate values are not right, corresponding to the graph axis. Can a clue be provided for the same??
One thing to note- I have 2 dataset arrays- 1 corresponds to Y value, the other corresponds to Y-Error values.
var width = 700;
var height = 700;
var padding = 90;
var myData = [12, 18, 20, 9, 17, 25, 30];
var errData = [6, 9, 10, 4.5, 8.5, 12.5, 15];
var svg = d3.select("body").
append("svg")
.attr("width", width)
.attr("height", height);
var Mydiv = d3.select("body")
.append("div")
//.append("span")
//.text('X')
.data(myData)
.attr("class", "toolDiv")
//.attr("span", "close")
.style("opacity", "0");
var MyLine = d3.select("body")
.append("div")
.data(myData)
.attr("class", "error-line");
//.style("opacity", 1);
//var m1 = d3.max(myData + errData)
var yScale = d3.scaleLinear().domain([0, d3.max(myData)]).range([height / 2, 50]);
var xScale = d3.scaleLinear().domain([0, d3.max(myData)]).range([0, width - 100]);
var y_ErScale = d3.scaleLinear().domain([0, d3.max(errData)]).range([height / 2, 50]);
var x_ErScale = d3.scaleLinear().domain([0, d3.max(errData)]).range([0, width - 100]);
var valueline = d3.selectAll("scatter-dots")
.append("line")
.attr("x", function(d, i) {
return xScale(myData[i]);
})
.attr("y", function(d) {
return yScale(d);
});
//.curve();
svg.append("g")
.selectAll("scatter-dots")
.data(myData)
.enter()
.append("line")
.data(myData)
.attr("class", "line")
.attr("d", valueline);
// .style("opacity", 0);
var eBar = d3.select("body").append("svg");
//var x_min =
var x_axis = d3.axisBottom()
.scale(xScale);
var y_axis = d3.axisLeft()
.scale(yScale);
svg.append("g")
.attr("transform", "translate(50, 10)")
.call(y_axis);
var xAxisTranslate = height / 2 + 10;
svg.append("g")
.attr("transform", "translate(50, " + xAxisTranslate + ")")
.call(x_axis);
svg.append("text")
.attr("transform",
"translate(" + (width / 1.2) + " ," + (height - 280) + ")")
.style("text-anchor", "middle")
.style("left", "70px")
.text("Points");
svg.append("text")
.attr("transform",
"translate(" + (width / 2) + " ," + (height - 280) + ")")
.style("text-anchor", "middle")
.style("left", "70px")
.text("X-Axis");
svg.append("text")
.attr("transform",
"translate(" + (height / 40) + " ," + (width - 500) + ")rotate(-90)")
.style("text-anchor", "middle")
.style("left", "70px")
.text("Y-Axis");
// svg.append("text")
// .attr("transform",
// "translate(" + (height/10) + " ," + (width - 690) + ")rotate(-90)")
// .style("text-anchor", "middle")
// .style("left", "70px")
// .text("Y");
svg.append("g")
.selectAll("scatter-dots")
.data(myData)
.enter().append("svg:circle")
.attr("cx", function(d, i) {
return xScale(myData[i]);
})
.attr("cy", function(d) {
return yScale(d);
})
.attr("r", 3)
.style("opacity", 0.8)
.style("cursor", "help")
// .on("click", function(d, i){
// var active = Mydiv.active ? false : true ,
// newOpacity = active ? 0 : 0.9;
// Mydiv.transition()
// .duration(200)
// .style("opacity", newOpacity);
// Mydiv.html("X" + "-" + errData[i] + "<br/>" + "Y" + "-" + myData[i] )
// .style("left", (d3.event.pageX + 10) + "px")
// .style("top", (d3.event.pageY - 18) + "px");
// Mydiv.active = active;
// });
.on("mouseover", function(d, i) {
//console.log(this);
//console.log(d3.select(this));
//d3.select(this);
//console.log(d3.select(this));
Mydiv.transition()
.duration(200)
.style("opacity", 0.9);
Mydiv.html("X" + "-" + errData[i] + "<br/>" + "Y" + "-" + myData[i])
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px");
//div.html(yScale(d));
})
.on("mouseout", function(d) {
Mydiv.transition()
.duration(500)
.style("opacity", 0);
});
// var errorBar = eBar.append("path")
// .attr("d", yScale(errData))
// .attr("stroke", "red")
// .attr("stroke-width", 1.5);
// svg.append("g")
// .selectAll("error-bars")
// .data(errData)
// .enter().append("svg:path")
// .attr("cx", function (d,i) { return x_ErScale(errData[i]); } )
// .attr("cy", function (d) { return y_ErScale(d); } )
// .attr("stroke", "red")
// .attr("stroke-width", 1.5);
svg.append("g")
.selectAll("line")
.data(errData)
.enter()
.append("line")
.attr("class", "error-line")
.attr("x1", function(d) {
return x_ErScale(d);
})
.attr("y1", function(d) {
return y_ErScale(d) + 30;
})
.attr("x2", function(d) {
return x_ErScale(d);
})
.attr("y2", function(d) {
return y_ErScale(d) + 2;
})
.on("mouseover", function(d) {
MyLine.transition()
//.duration(200)
.style("opacity", "1");
//.style("fill-opacity", 0);
})
.on("mouseout", function(d) {
MyLine.transition()
//.duration(200)
.style("opacity", 0.0);
//.style("fill-opacity", 1);
});
svg.append("g").selectAll("line")
.data(errData).enter()
.append("line")
.attr("class", "error-cap")
.attr("x1", function(d) {
return x_ErScale(d) - 8;
})
.attr("y1", function(d) {
return y_ErScale(d) - 30;
})
.attr("x2", function(d) {
return x_ErScale(d) + 8;
})
.attr("y2", function(d) {
return y_ErScale(d) - 30;
});
svg.append("g")
.selectAll("line")
.data(errData)
.enter()
.append("line")
.attr("class", "error-line")
.attr("x1", function(d) {
return x_ErScale(d);
})
.attr("y1", function(d) {
return y_ErScale(d) - 30;
})
.attr("x2", function(d) {
return x_ErScale(d);
})
.attr("y2", function(d) {
return y_ErScale(d) - 2;
})
.on("mouseover", function(d) {
MyLine.transition()
//.duration(200)
.style("opacity", "1");
//.style("fill-opacity", 0);
})
.on("mouseout", function(d) {
MyLine.transition()
//.duration(200)
.style("opacity", "0.6");
//.style("fill-opacity", 1);
});
// .on("mouseover", function(d){
// MyLine.transition()
// .duration(200)
// .style("opacity", 0);
// })
// .on("mouseout", function(d){
// MyLine.transition()
// .duration(200)
// .style("opacity", 1);
// });
// Add Error Bottom Cap
svg.append("g").selectAll("line")
.data(errData).enter()
.append("line")
.attr("class", "error-cap")
.attr("x1", function(d) {
return x_ErScale(d) - 8;
})
.attr("y1", function(d) {
return y_ErScale(d) + 30;
})
.attr("x2", function(d) {
return x_ErScale(d) + 8;
})
.attr("y2", function(d) {
return y_ErScale(d) + 30;
});
//svg.selectAll("scatter-dots")
// .data(myData)
// .enter()
// .on("mouseover", function()
// {
// //console.log(this);
// //console.log(d3.select(this));
// //d3.select(this);
// //console.log(d3.select(this));
// //div.transition()
// //.duration(200)
// //.style("opacity", 0.8);
// //div.html(myData);
// });
//// .on("mouseout", function(d)
//// {
//// div.transition()
//// .duration(500)
//// .style("opacity", 0);
//// });
/*body {
font: 10px sans-serif;
}
*/
#d1 {
position: relative;
top: 100px;
}
#svg1 {
position: relative;
bottom: 80px;
}
.axis_path {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.axis_line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.dot {
stroke: #000;
cursor: pointer;
}
.error-line {
stroke: #b30059;
stroke-width: 2px;
opacity: 0.6;
stroke-dasharray: 2;
/*fill-opacity: 1;*/
/*opacity: 1;*/
}
.error-cap {
stroke: #b30059;
stroke-width: 2px;
stroke-type: solid;
}
.toolDiv {
position: absolute;
text-align: center;
width: 120px;
height: 28px;
padding: 2px;
font: 12px sans-serif;
background: lightsteelblue;
border: 0px;
border-radius: 8px;
pointer-events: none;
}
/*.toolDiv.image
{
position: static;
content: url(http://wfarm1.dataknet.com/static/resources/icons/set28/7f8535d7.png);
}*/
/*.close {
width: 10px;
height: 10px;
background: #fff;
position: absolute;
top: 0;
right: 0;
background: url('http://i.imgur.com/Idy9R0n.png') no-repeat 0 0;
cursor: pointer;
}
.close:hover {
background-position: -13px 0;
}*/
.line {
fill: none;
stroke: steelblue;
stroke-width: 2px;
}
<head>
<title>Scatter Plot Example</title>
<script src="https://d3js.org/d3.v4.min.js"></script>
<!-- <script src="http://labratrevenge.com/d3-tip/javascripts/d3.tip.v0.6.3.js"></script> -->
<link rel="stylesheet" type="text/css" href="scatter.css">
</head>
<body>
<script src="scatterplot.js" type="text/javascript"></script>
<div id="d1">
<svg id="svg1">
<div id="d2"></div>
</svg>
</div>
</body>
I've included a fiddle for reference.
I want to make a force graph with permanently visible text on the nodes and text on the links.
This snippet provides labels on the nodes.
The 2nd snippet from an answer to Show tool-tip on links of force directed graph in d3js provides mouseover labels on links and nodes.
I'm currently trying to extend the second to make the mouseover node labels permanent.
var width = 400;
var height = 125;
var margin = 20;
var pad = margin / 2;
var graph = { "nodes":[ { "name": "A"}, { "name": "B"}] };
drawGraph(graph);
function drawGraph(graph) {
var svg = d3.select("#force").append("svg")
.attr("width", width)
.attr("height", height);
// create an area within svg for plotting graph
var plot = svg.append("g")
.attr("id", "plot")
.attr("transform", "translate(" + pad + ", " + pad + ")");
var layout = d3.layout.force()
.size([width - margin, height - margin])
.charge(-120)
.nodes(graph.nodes)
.start();
drawNodes(graph.nodes);
// add ability to drag and update layout
d3.selectAll(".node").call(layout.drag);
layout.on("tick", function() {
d3.selectAll(".node")
.attr("cx", d => { return d.x; })
.attr("cy", d => { return d.y; });
});
}
// Draws nodes on plot
function drawNodes(nodes) {
d3.select("#plot").selectAll(".node")
.data(nodes)
.enter()
.append("circle")
.attr("class", "node")
.attr("id", (d, i) => { return d.name; })
.attr("cx", (d, i) => { return d.x; })
.attr("cy", (d, i) => { return d.y; })
.attr("r", 4)
.style("fill", "#EE77b4")
.on("mouseover", function(d, i) {
var x = d3.mouse(this)[0];
var y = d3.mouse(this)[1];
var tooltip = d3.select("#plot")
.append("text")
.text(d.name)
.attr("x", x)
.attr("y", y)
.attr("id", "tooltip");
})
.on("mouseout", function(d, i) {
d3.select("#tooltip").remove();
});
}
body {
font-family: 'Source Sans Pro', sans-serif;
font-weight: 300;
}
b {
font-weight: 900;
}
.outline {
fill: none;
stroke: #888888;
stroke-width: 1px;
}
#tooltip {
font-size: 10pt;
font-weight: 900;
fill: #000000;
stroke: #ffffff;
stroke-width: 0.25px;
}
.node {
stroke: #ffffff;
stroke-weight: 1px;
}
.highlight {
stroke: red;
stroke-weight: 4px;
stroke-opacity: 1.0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div align="center" id="force"></div>
I've tried replacing the mouseover function with:
.each( function(d, i) {
var x = d.x; //d3.mouse(this)[0];
var y = d.y; //d3.mouse(this)[1];
var tooltip = d3.select("#plot")
.append("text")
.text(d.name)
.attr("x", x)
.attr("y", y)
.attr("id", "tooltip");
})
but now the labels don't move so I added
d3.selectAll("text").attr( "x", d => { return d.x; })
.attr( "y", d => { return d.y; });
in layout.on("tick", function() ...
But now it's all in one place doesn't move and I get TypeError: d is undefined
Rewrite your code this way (pay attention on the comments):
layout.on("tick", function() {
tooltips // here we set new position for tooltips on every tick
.attr("x", (d, i) => { return d.x; })
.attr("y", (d, i) => { return d.y; });
d3.selectAll(".node")
.attr("cx", d => { return d.x; })
.attr("cy", d => { return d.y; });
});
...
function drawNodes(nodes) {
tooltips = d3.select("#plot").selectAll(".node")
.data(nodes)
.enter()
.append("circle")
.attr("class", "node")
.attr("id", (d, i) => { return d.name; })
.attr("cx", (d, i) => { return d.x; })
.attr("cy", (d, i) => { return d.y; })
.attr("r", 4)
.style("fill", "#EE77b4")
.select(function() { return this.parentNode }) // returns to parent node
.append('text') // append svg-text elements for tooltip
.data(nodes)
.text(function(d) { return d.name; }) // set text
.attr("x", (d, i) => { return d.x; }) // set initial x position
.attr("y", (d, i) => { return d.y; }) // set initial y position
.attr("id", function(d,i) { return "tooltip-" + i; }) // set unique id
.attr("class", "d3-tooltip");
}
Working demo:
var width = 400;
var height = 125;
var margin = 20;
var pad = margin / 2;
var tooltips = null;
var graph = { "nodes":[ { "name": "A"}, { "name": "B"}] };
drawGraph(graph);
function drawGraph(graph) {
var svg = d3.select("#force").append("svg")
.attr("width", width)
.attr("height", height);
// create an area within svg for plotting graph
var plot = svg.append("g")
.attr("id", "plot")
.attr("transform", "translate(" + pad + ", " + pad + ")");
var layout = d3.layout.force()
.size([width - margin, height - margin])
.charge(-120)
.nodes(graph.nodes)
.start();
drawNodes(graph.nodes);
// add ability to drag and update layout
d3.selectAll(".node").call(layout.drag);
layout.on("tick", function() {
tooltips
.attr("x", (d, i) => { return d.x; })
.attr("y", (d, i) => { return d.y; });
d3.selectAll(".node")
.attr("cx", d => { return d.x; })
.attr("cy", d => { return d.y; });
});
}
// Draws nodes on plot
function drawNodes(nodes) {
tooltips = d3.select("#plot").selectAll(".node")
.data(nodes)
.enter()
.append("circle")
.attr("class", "node")
.attr("id", (d, i) => { return d.name; })
.attr("cx", (d, i) => { return d.x; })
.attr("cy", (d, i) => { return d.y; })
.attr("r", 4)
.style("fill", "#EE77b4")
.select(function() { return this.parentNode })
.append('text')
.data(nodes)
.text(function(d) { return d.name; })
.attr("x", (d, i) => { return d.x; })
.attr("y", (d, i) => { return d.y; })
.attr("class", "d3-tooltip")
.attr("id", function(d,i) { return "tooltip-" + i; });
}
body {
font-family: 'Source Sans Pro', sans-serif;
font-weight: 300;
}
b {
font-weight: 900;
}
.outline {
fill: none;
stroke: #888888;
stroke-width: 1px;
}
.d3-tooltip {
font-size: 20pt;
font-family: 'Comic Sans MS';
font-weight: 900;
fill: #000000;
stroke: #ffffff;
stroke-width: 0.25px;
}
.node {
stroke: #ffffff;
stroke-weight: 1px;
}
.highlight {
stroke: red;
stroke-weight: 4px;
stroke-opacity: 1.0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div align="center" id="force"></div>
I tried to use the following code to show a d3.js tool-tip effect but I can't get it to work, why is the number not showing up:
<style>
svg {
width: 100%;
height: 100%;
position: center;
}
#hist_sexage {
background-color: lightgrey;
}
.rect:hover {
fill: yellow;
}
.tooltip {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
position: absolute;
display: none;
width: auto;
height: auto;
background: none repeat scroll 0 0 red;
border: 0 none;
border-radius: 8px 8px 8px 8px;
box-shadow: -3px 3px 15px #888888;
color: blue;
font: 12px sans-serif;
padding: 5px;
text-align: center;
}
</style>
<svg id="hist_sexage" width="950" height="500"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var margin = {top: 30, right: 30, bottom: 40, left: 30};
var width = document.getElementById("hist_sexage").getBoundingClientRect().width-50;
var height = 400;
var g = d3.select("#hist_sexage")
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var x0 = d3.scaleBand()
.rangeRound([0, width])
.paddingInner(0.2);
var x1 = d3.scaleBand()
.padding(0.1);
var y = d3.scaleLinear()
.rangeRound([height, 0]);
var z = d3.scaleOrdinal()
.range(["cornflowerblue", "orangered"]);
d3.csv("/blog/data/age_by_gender.csv",function(d, i, columns) {
for (var i = 1, n = columns.length; i < n; ++i) d[columns[i]] = +d[columns[i]];
return d;
},
function(error, data) {
if (error) throw error;
var keys = data.columns.slice(1);
x0.domain(data.map(function(d) { return d.Age_Group; }));
x1.domain(keys).rangeRound([0, x0.bandwidth()]);
y.domain([0, d3.max(data, function(d) { return d3.max(keys, function(key) { return d[key]; }); })]).nice();
g.append("g")
.selectAll("g")
.data(data)
.enter().append("g")
.attr("transform", function(d) { return "translate(" + x0(d.Age_Group) + ",0)"; })
.selectAll("rect")
.data(function(d) { return keys.map(function(key) { return {key: key, value: d[key]}; }); })
.enter().append("rect").attr("class", "rect")
.attr("x", function(d) { return x1(d.key); })
.attr("y", function(d) { return y(d.value); })
.attr("width", x1.bandwidth())
.attr("height", function(d) { return height - y(d.value); })
.attr("fill", function(d) { return z(d.key); })
.on("mouseover", function() { tooltip.style("display", null); })
.on("mousemove", function(d) {
var xPosition = d3.mouse(this)[0] - 5;
var yPosition = d3.mouse(this)[1] - 5;
tooltip.attr("transform", "translate(" + xPosition + "," + yPosition + ")");
tooltip.select("text").text(d.value);
console.log(d.value)
})
.on("mouseout", function() { tooltip.style("display", "none"); });
g.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x0))
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.1em")
.attr("transform", "rotate(-45)" );;
g.append("g")
.attr("class", "axis")
.call(d3.axisLeft(y).ticks(null, ".0%"))
.append("text")
.attr("x", 2)
.attr("y", y(y.ticks().pop()) + 0.5)
.attr("dy", "0.32em")
.attr("fill", "#000")
.attr("font-weight", "bold")
.attr("text-anchor", "start")
.text("Percentage");
var legend = g.append("g")
.attr("font-family", "sans-serif")
.attr("font-size", 10)
.attr("text-anchor", "end")
.selectAll("g")
.data(keys.slice().reverse())
.enter().append("g")
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });
legend.append("rect")
.attr("x", width - 19)
.attr("width", 19)
.attr("height", 19)
.attr("fill", z);
legend.append("text")
.attr("x", width - 24)
.attr("y", 9.5)
.attr("dy", "0.32em")
.text(function(d) { return d; });
});
// Prep the tooltip bits, initial display is hidden
var tooltip = d3.select("#hist_sexage").append("g")
.attr("class", "tooltip")
.style("display", "none");
tooltip.append("g:rect")
.attr("width", 60)
.attr("height", 20)
.attr("fill", "red")
.style("opacity", 0.5);
tooltip.append("g:text")
.attr("x", 30)
.attr("y", "1.2em")
.style("text-anchor", "middle")
.attr("font-size", "12px")
.attr("font-weight", "bold");
</script>
I am expecting to see it, if I move over the bar, it should show the number alongside with my cursor but it does not. Can you please help me troubleshoot this issue?
Thanks,
Well, you can't see the tooltip for a simple reason. You set its display to none...
.style("display", "none");
... but you forgot to change its style on mousemove. Therefore, its children will never be rendered.
Here is a working demo with fake data, changing the style in the mousemove:
var svg = d3.select("#hist_sexage");
var data = [30, 250, 120, 150, 90];
var rects = svg.selectAll("jamesWatson")
.data(data)
.enter()
.append("rect")
.attr("x", 0)
.attr("y", function(d, i) {
return i * 20
})
.attr("height", 15)
.attr("width", function(d) {
return d
})
.attr("fill", "teal")
.on("mousemove", function(d) {
var xPosition = d3.mouse(this)[0] + 10;
var yPosition = d3.mouse(this)[1] - 5;
tooltip.style("display", "inline")
.attr("transform", "translate(" + xPosition + "," + yPosition + ")");
tooltip.select("text").text(d);
})
.on("mouseout", function() {
tooltip.style("display", "none");
});
var tooltip = d3.select("#hist_sexage").append("g")
.attr("class", "tooltip")
.style("display", "none");
tooltip.append("g:rect")
.attr("width", 60)
.attr("height", 20)
.attr("fill", "red")
.style("opacity", 0.5);
tooltip.append("g:text")
.attr("x", 30)
.attr("y", "1.2em")
.style("text-anchor", "middle")
.attr("font-size", "12px")
.attr("font-weight", "bold");
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg id="hist_sexage"></svg>
I have a parallel coordinates visualization and I've added a table below that represents all the data in the visualization.
I want to filter the table based on the selection of the parallel coordinates visualization. So, just showing the data highlighted in the parallel coordinates visualization.
Any ideas on how to do that?
I've tried in the brush section to highlight the cells in the table in red adding a style in for the tags, but it doesn't seem to work. See code below:
// Add and store a brush for each axis.
g.append("g")
.attr("class", "brush")
.each(function(d) {
d3.select(this).call(y[d].brush = d3.svg.brush().y(y[d]).on("brushstart", brushstart).on("brush", brush));
d3.selectAll("td").style('bgcolor', 'red');
})
.selectAll("rect")
.attr("x", -8)
.attr("width", 16);
I've tried to add the highlight of the cells in the brush function. Here I achieve for all the cells to turn red, but I don't know how to highlight only the ones selected in the selection.
function brush() {
var actives = dimensions.filter(function(p) {
return !y[p].brush.empty();
}),
extents = actives.map(function(p) {
return y[p].brush.extent();
});
foreground.style("display", function(d) {
return actives.every(function(p, i) {
return extents[i][0] <= d[p] && d[p] <= extents[i][1];
}) ? null : "none";
});
d3.selectAll("td").style('background-color', 'red');
}
See code below, which you can find also in the following link GitHub:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
svg {
font: 10px sans-serif;
}
.background path {
fill: none;
stroke: #ddd;
shape-rendering: crispEdges;
}
.foreground path {
fill: none;
stroke: steelblue;
stroke-width: 2;
}
.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;
}
.tooltip {
background-color: rgba(220,220,220,0.5);
color: #333;
margin: 10px;
height: 25px;
padding-right: 10px;
padding-left: 10px;
padding-top: 10px;
-webkit-border-radius:10px;
-moz-border-radius:10px;
border-radius:10px;
}
td, th {
padding: 1px 4px;
}
</style>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
// Fuente original: https://bl.ocks.org/jasondavies/1341281
var margin = {
top: 30,
right: 10,
bottom: 10,
left: 10
},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.scale.ordinal().rangePoints([0, width], 1),
y = {},
dragging = {};
var line = d3.svg.line(),
axis = d3.svg.axis().orient("left"),
background,
foreground;
var svg = d3.select("body").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 notas = [
{
"Nombre": "Emily",
"Matematicas": "10",
"Ciencias": "10",
"Historia": "8",
"Geografia": "8",
"Lengua": "10"
},
{
"Nombre": "Cooper",
"Matematicas": "10",
"Ciencias": "7",
"Historia": "2",
"Geografia": "8",
"Lengua": "10"
}];
var tooltip = d3.select("body")
.append("div")
.style("position", "absolute")
.style("z-index", "10")
.style("visibility", "hidden")
.text("a simple tooltip")
.attr("class","tooltip");
// Extract the list of dimensions and create a scale for each.
x.domain(dimensions = d3.keys(notas[0]).filter(function(d) {
return d != "Nombre" && (y[d] = d3.scale.linear()
.domain(d3.extent(notas, function(p) {
return +p[d];
}))
.range([height, 0]));
}));
// Add grey background lines for context.
background = svg.append("g")
.attr("class", "background")
.selectAll("path")
.data(notas)
.enter().append("path")
.attr("d", path);
// Add blue foreground lines for focus.
foreground = svg.append("g")
.attr("class", "foreground")
.selectAll("path")
.data(notas)
.enter().append("path")
.attr("d", path)
.on("mouseover", function(n){
d3.select(this)
.transition().duration(100)
.style({'stroke' : '#F00'});
tooltip.text(n.Nombre);
return tooltip.style("visibility", "visible");
})
.on("mousemove", function(){
return tooltip
.style("top", (event.pageY-10)+"px")
.style("left",(event.pageX+10)+"px");
})
.on("mouseout", function(d){
d3.select(this)
.transition().duration(100)
.style({'stroke': 'steelblue' })
.style({'stroke-width' : '2'});
return tooltip.style("visibility", "hidden");
});
// 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.behavior.drag()
.origin(function(d) {
return {
x: x(d)
};
})
.on("dragstart", 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("dragend", 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.
g.append("g")
.attr("class", "axis")
.each(function(d) {
d3.select(this).call(axis.scale(y[d]));
})
.append("text")
.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) {
d3.select(this).call(y[d].brush = d3.svg.brush().y(y[d]).on("brushstart", brushstart).on("brush", brush));
})
.selectAll("rect")
.attr("x", -8)
.attr("width", 16);
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])];
}));
}
function brushstart() {
d3.event.sourceEvent.stopPropagation();
}
// Handles a brush event, toggling the display of foreground lines.
function brush() {
var actives = dimensions.filter(function(p) {
return !y[p].brush.empty();
}),
extents = actives.map(function(p) {
return y[p].brush.extent();
});
foreground.style("display", function(d) {
return actives.every(function(p, i) {
return extents[i][0] <= d[p] && d[p] <= extents[i][1];
}) ? null : "none";
});
}
// The table generation function
function tabulate(data, columns) {
var table = d3.select("body").append("table")
.attr("style", "margin-left: 250px"),
thead = table.append("thead"),
tbody = table.append("tbody");
// append the header row
thead.append("tr")
.selectAll("th")
.data(columns)
.enter()
.append("th")
.text(function(column) { return column; });
// create a row for each object in the data
var rows = tbody.selectAll("tr")
.data(notas)
.enter()
.append("tr");
// create a cell in each row for each column
var cells = rows.selectAll("td")
.data(function(row) {
return columns.map(function(column) {
return {column: column, value: row[column]};
});
})
.enter()
.append("td")
.attr("style", "font-family: Courier") // sets the font style
.html(function(d) { return d.value; });
return table;
}
// render the table
var peopleTable = tabulate(notas, ["Nombre", "Matematicas","Ciencias", "Historia","Geografia", "Lengua"]);
</script>
</body>
</html>
I've added also the code in JSFiddle but it doesn't display anything. I'm not sure what I'm doing wrong. I'm not familiar with JSFiddle, so I might be doing something wrong.
I've tried also to use divgrid (DivGrid) and with the following code I can see how when I attempt to brush the table gets updated. The problem is that it doesn't get updated with the data I need. I just have it to show 5 records.
<script src="divgrid.js"></script>
...
// Handles a brush event, toggling the display of foreground lines.
function brush() {
var actives = dimensions.filter(function(p) {
return !y[p].brush.empty();
}),
extents = actives.map(function(p) {
return y[p].brush.extent();
});
foreground.style("display", function(d) {
return actives.every(function(p, i) {
return extents[i][0] <= d[p] && d[p] <= extents[i][1];
}) ? null : "none";
});
//d3.selectAll("td").style('background-color', 'red');
var grid = d3.divgrid();
d3.select('body')
.datum(notas.slice(0,5))
.call(grid);
}
The problems I have (I think) is that (1) the update of the table is not really in the right place as it is only updating when starting to brush, not when brushing over the lines and (2) I need to find a way to pickup the data from the lines that are selected. I've added to code in github as a reference (GitHub)
Any ideas?
Thanks