Switch between datasets in a d3 barchart - d3.js

I need to switch between two datasets from the same csv file,
I have a small dataset of predicted v actual league positions from last years English premier League.
dataset1 = Actual League Position
dataset2 = Predicted league Position
My render data function does not seem to be working as my second dataset (i.e. Predicted) is not displayed when i click the radio button - see attached pic - only the actual position dataset is being displayed
I've provided a link to my code: github link to my code
Copy of relevant code below
function render(data){
var bars = g.selectAll("bar")
.data(data)
//enter
bars.enter().append("rect")
.attr("class", "bar")
.attr("x1", 0)
.attr("x2", 0)
.attr("y", function(d) { return y(d.Team); })
.attr("height", y.bandwidth())
.attr("width", function(d) { return x2(d.Predicted_Finish); })
.style("fill", "#a02f2b")
//exit
bars.exit()
.transition()
.duration(300)
.remove()
}
function init()
{
//setup the svg
var svg = d3.select("svg"),
margin = {top: 20, right: 10, bottom: 65, left: 110}//position of axes
frame
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom;
//setup our ui
d3.select("#Actual")
.on("click", function(d,i) {
console.log(Actual);
render(Actual)
})
d3.select("#Predicted")
.on("click", function(d,i) {
console.log(Predicted);
render(Predicted)
})
render(Actual)
}
init();

This code can be significantly simplified.
First, couple of format problems:
Improperly placed <body>, and no </body> or </html>
<form> around your buttons is not needed (it's causing a submit)
Second, your code can be restructured. You don't need a full enter, update, exit pattern here since your data doesn't really change. You just want to toggle between two variables in your single dataset. With that in mind, here's how it ends up looking:
<head>
<meta charset="utf-8">
<title>CSS Example</title>
<link href="https://fonts.googleapis.com/css?family=Passion+One" rel="stylesheet">
<style>
.my-text {
font-size: 1.95em;
font-family: 'Passion One', cursive;
fill: #000000;
}
.bar {
fill: #71df3e;
}
.bar:hover {
fill: white;
}
.axis--x path {
display: none;
}
body {
background-color: orange;
}
.axisx text {
fill: black;
}
.axisy text {
fill: black;
}
.axisx line {
stroke: black;
}
.axisy line {
stroke: black;
}
.axisx path {
stroke: black;
}
.axisy path {
stroke: black;
}
</style>
</head>
<body>
<div id="buttons">
<button id="Actual">Actual</button>
<button id="Predicted">Predicted</button>
</div>
<svg width="1200" height="500">
<text class="my-text" x="330" y="20">EPL PREDICTIONS VERSUS REALITY</text>
</svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
//define svg canvas
var svg = d3.select("svg"),
margin = {
top: 20,
right: 10,
bottom: 65,
left: 110
} //position of axes frame
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom;
//next our graph
var g = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
d3.csv("data.csv", function(d) {
d.Actual_Finish = +d.Actual_Finish;
d.Predicted_Finish = +d.Predicted_Finish;
return d;
}, function(error, data)
{
if (error) throw error;
data = data;
//define our x and y axis scales and variables, remembering we have 2 x variables
x1 = d3.scaleLinear().rangeRound([800, 1])
//experiment with the max numbers to bring the x scale within the margin
.domain([0, d3.max(data, function(d) {
return d.Actual_Finish;
})]);
y = d3.scaleBand().rangeRound([0, height])
.padding(0.5).domain(data.map(function(d) {
return d.Team;
}));
//append x axis to svg
g.append("g")
.style("font", "14px arial") //font and size of x axis labels
.attr("class", "axisx")
.call(d3.axisBottom(x1).ticks(20))
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x1))
.append("text")
.attr("x", 450) //position of x1 axis label: x co-ordinate
.attr("y", 35) //position of x axis label: y co-ordinate
.attr("dx", "1.0em") //also position of X axis label
.attr("text-anchor", "middle")
.text("League Position");
//append y axis to svg
g.append("g") //append y axis to svg
.style("font", "14px arial") //font and size of y axis labels
.attr("class", "axisy")
.call(d3.axisLeft(y).ticks(20)) //no. of ticks on y axis
.append("text")
.attr("transform", "rotate(-360)") //rotate the axis label text by -90
.attr("y", -20) //position of y axis label
.attr("dy", "1.0em") //sets the unit amount the y axis label moves above
.attr("text-anchor", "end")
.text("Team");
var bars = g.selectAll('.bar')
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("x1", 0)
.attr("x2", 0)
.attr("height", y.bandwidth())
.style("fill", "#a02f2b");
render('Actual_Finish')
function render(which) {
bars.attr("y", function(d) {
return y(d.Team);
})
.attr("width", function(d) {
return x1(d[which]);
});
}
d3.select("#Actual")
.on("click", function(d, i) {
render('Actual_Finish')
});
d3.select("#Predicted")
.on("click", function(d, i) {
render('Predicted_Finish')
});
});
</script>
</body>
</html>
Running code can be seen here.

Related

Understanding sorting bar charts in d3

I am reading Interactive Data Visualization for the Web book, and in the Interactivity chapter where we sort the bar chart, the author writes the following code
d3.select("body").append("button")
.text("Sort") //I modified the first two lines
.on("click", function(){
svg.selectAll("rect")//the bars were added before
.sort(function(a, b){
return d3.ascending(a.value, b.value);
})
.attr("x", function(d, i){
return xScale(i);//xScale is defined earlier
})
})
What I don't understand is, when we call the sort function after selecting the bars, what happens? Do the values in the original data set is sorted in ascending order? and why do we call the x attribute again? Suppose I have the value
dataset = [{key: 0, value: 10},
{key: 1, value: 5},
{key: 2, value: 7}]
and the data join happens on the key value. How to understand the sort function in this case? Why the bars are sorted? To me, it seems that, after sorting the dataset becomes
dataset = [{key: 1, value: 5},
{key: 2, value: 7}
{key: 0, value: 10}]
and the x attribute says that the second element to move to the first position, the third element to move to the second position, and the first element to move to the third position. Is this a correct understanding?
If you read the documentation about d3 Selection Sort
Returns a new selection that contains a copy of each group in this
selection sorted according to the compare function. After sorting,
re-inserts elements to match the resulting order (per
selection.order).
D3 binds data to nodes on your html tree. When you use selectAll('rect') then .sort(), it generates a new selection with ordered nodes, it also change the nodes position on your html dom. See also order
The nodes are rearranged but your rectangles x position are still with the old position. So you have to update the x attributes for the sorted elements.
.attr("x", function(d, i){
return xScale(i);//xScale is defined earlier
})
Is updating the x positions folowing h
const data = [10,20,25,4,30]
const xScale = d3.scaleBand()
.domain(d3.range(data.length))
.rangeRound([0, 500])
.paddingInner(0.05);
const yScale = d3.scaleLinear()
.domain([0, d3.max(data)])
.range([0, 200]);
const svg = d3.select("body").append("svg")
.attr("width", "500px")
.attr("height","200px")
.style("width", "100%")
.style("height", "auto");
// normal
svg.append("g")
.attr("class", "x-axis")
.attr("transform", "translate(0,20)")
svg.selectAll('.bar')
.data(data)
.enter()
.append('rect')
.attr("x", (d, i) => xScale(i))
.attr("y", (d, i) => 200 - yScale(d))
.attr("width", xScale.bandwidth())
.attr("height", d=> yScale(d));
setTimeout(() => {
console.log('Data on nodes before D3 sort')
console.log([...document.querySelectorAll('rect')].map(e => e.__data__))
svg.selectAll('rect')
.sort((a, b) => d3.ascending(a, b))
.transition()
.duration(500)
.attr("x", (d, i)=> xScale(i));
console.log('Data on nodes after D3 sort')
console.log([...document.querySelectorAll('rect')].map(e => e.__data__))
}, 2500)
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
In your code selection.sort() reorders the elements, no position is changed by that. Just after the x attribute is reset based on the scale, the visualization is changed. Note that in your example the scale is called with i being the argument. So after the order of the elements change, elements will have a different index.
Some more aboout the topic:
selection.sort()
does reorder the selection elements - not the data. See also: https://github.com/d3/d3-selection#selection_sort.
Reordering elements in the DOM does not change any position of the elements (just overlaps are affected based on the order).
Sorting the elements can be useful when you transition your elements (Mike Bostocks Sortable Bar Chart example):
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = 400 - margin.left - margin.right,
height = 200 - margin.top - margin.bottom;
var formatPercent = d3.format(".0%");
var x = d3.scale.ordinal()
.rangeRoundBands([0, width], .1, 1);
var y = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.tickFormat(formatPercent);
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 + ")");
d3.csv("https://gist.githubusercontent.com/mbostock/81aa27912ad9b1ed577016797a780b2c/raw/3a807eb0cbb0f5904053ac2f9edf765e2f87a2f5/alphabet.csv", function(error, data) {
data.forEach(function(d) {
d.frequency = +d.frequency;
});
x.domain(data.map(function(d) { return d.letter; }));
y.domain([0, d3.max(data, function(d) { return d.frequency; })]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Frequency");
svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("x", function(d) { return x(d.letter); })
.attr("width", x.rangeBand())
.attr("y", function(d) { return y(d.frequency); })
.attr("height", function(d) { return height - y(d.frequency); });
d3.select("input").on("change", change);
var sortTimeout = setTimeout(function() {
d3.select("input").property("checked", true).each(change);
}, 2000);
function change() {
clearTimeout(sortTimeout);
// Copy-on-write since tweens are evaluated after a delay.
var x0 = x.domain(data.sort(this.checked
? function(a, b) { return b.frequency - a.frequency; }
: function(a, b) { return d3.ascending(a.letter, b.letter); })
.map(function(d) { return d.letter; }))
.copy();
svg.selectAll(".bar")
.sort(function(a, b) { return x0(a.letter) - x0(b.letter); });
var transition = svg.transition().duration(750),
delay = function(d, i) { return i * 50; };
transition.selectAll(".bar")
.delay(delay)
.attr("x", function(d) { return x0(d.letter); });
transition.select(".x.axis")
.call(xAxis)
.selectAll("g")
.delay(delay);
}
});
body {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
position: relative;
width: 400px;
}
.axis text {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.bar {
fill: steelblue;
fill-opacity: .9;
}
.x.axis path {
display: none;
}
label {
position: absolute;
top: 10px;
right: 10px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
<label><input type="checkbox"> Sort values</label>
If you click on the sort checkbox, three things happen (see function change):
1) compute the new positions (in this case, the domain of the scale is changed here):
var x0 = x.domain(data.sort(this.checked
? function(a, b) { return b.frequency - a.frequency; }
: function(a, b) { return d3.ascending(a.letter, b.letter); })
.map(function(d) { return d.letter; }))
.copy();
2) resort the selection - just changing the order of the elements, no position has changed!
svg.selectAll(".bar")
.sort(function(a, b) { return x0(a.letter) - x0(b.letter); });
3) transitioning the selection to the new positions:
var transition = svg.transition().duration(750),
delay = function(d, i) { return i * 50; };
transition.selectAll(".bar")
.delay(delay)
.attr("x", function(d) { return x0(d.letter); });
transition.select(".x.axis")
.call(xAxis)
.selectAll("g")
.delay(delay);
The benefit of step 2 (sorting the elements) is that the transition starts with the element which will be placed to the very left. The next transitioning element is the one which will end up next to it, ...and so forth.
See yourself when you run the code snippet und click on sort values.
To see how it looks like without this effect, see the following where I just commented out step 2:
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = 400 - margin.left - margin.right,
height = 200 - margin.top - margin.bottom;
var formatPercent = d3.format(".0%");
var x = d3.scale.ordinal()
.rangeRoundBands([0, width], .1, 1);
var y = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.tickFormat(formatPercent);
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 + ")");
d3.csv("https://gist.githubusercontent.com/mbostock/81aa27912ad9b1ed577016797a780b2c/raw/3a807eb0cbb0f5904053ac2f9edf765e2f87a2f5/alphabet.csv", function(error, data) {
data.forEach(function(d) {
d.frequency = +d.frequency;
});
x.domain(data.map(function(d) { return d.letter; }));
y.domain([0, d3.max(data, function(d) { return d.frequency; })]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Frequency");
svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("x", function(d) { return x(d.letter); })
.attr("width", x.rangeBand())
.attr("y", function(d) { return y(d.frequency); })
.attr("height", function(d) { return height - y(d.frequency); });
d3.select("input").on("change", change);
var sortTimeout = setTimeout(function() {
d3.select("input").property("checked", true).each(change);
}, 2000);
function change() {
clearTimeout(sortTimeout);
// Copy-on-write since tweens are evaluated after a delay.
var x0 = x.domain(data.sort(this.checked
? function(a, b) { return b.frequency - a.frequency; }
: function(a, b) { return d3.ascending(a.letter, b.letter); })
.map(function(d) { return d.letter; }))
.copy();
// svg.selectAll(".bar")
// .sort(function(a, b) { return x0(a.letter) - x0(b.letter); });
var transition = svg.transition().duration(750),
delay = function(d, i) { return i * 50; };
transition.selectAll(".bar")
.delay(delay)
.attr("x", function(d) { return x0(d.letter); });
transition.select(".x.axis")
.call(xAxis)
.selectAll("g")
.delay(delay);
}
});
body {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
position: relative;
width: 400px;
}
.axis text {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.bar {
fill: steelblue;
fill-opacity: .9;
}
.x.axis path {
display: none;
}
label {
position: absolute;
top: 10px;
right: 10px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
<label><input type="checkbox"> Sort values</label>

Trying to add an id to a circle

I am doing research for a professor at my school and I am coding in d3. I am creating a graph that has tool tips and you can toggle the graph by clicking on the legend. However, when I toggle the lines disappears but I can't get the tool tips associated to that line. I know I need to somehow add an id to these circles but I have no idea how. I have included my code below to show what I have so far. Any help would be appreciated!
<!DOCTYPE html>
<meta charset="utf-8">
<style> /* set the CSS */
body { font: 12px Arial;}
path {
stroke: white;
stroke-width: 2;
fill: none;
}
.axis path,
.axis line {
fill: none;
stroke: grey;
stroke-width: 1;
shape-rendering: crispEdges;
}
div.tooltip {
position: absolute;
text-align: center;
width: 170px;
height: 500px;
padding: 1px;
font: 12px sans-serif;
background: lightsteelblue;
border: 0px;
border-radius: 5px;
pointer-events: none;
}
.legend {
font-size: 16px;
font-weight: bold;
text-anchor: middle;
}
</style>
<body>
<!-- load the d3.js library -->
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
// Set the dimensions of the canvas / graph
var margin = {top: 20, right: 150, bottom: 60, left: 80},
width = 1160 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
// Parse the date / time
// Set the ranges
var x = d3.scaleTime().range([0, width-100]);
var formatxAxis=d3.format('.0f');
var y = d3.scaleLinear().range([height, 0]);
// Define the axe
var xAxis = d3.axisBottom().scale(x)
.tickFormat(formatxAxis).ticks(20);
var yAxis = d3.axisLeft().scale(y)
.ticks(5);
// Define the line
var valueline = d3.line()
.curve(d3.curveMonotoneX)
.x(function(d) { return x(d.year); })
.y(function(d) { return y(d.count); });
// Define the div for the tooltip
var div = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
// Adds the svg canvas
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 + ")");
// Get the data
d3.json("satisfaction.json", function(error, data) {
data.forEach(function(d) {
d.year = +d.year;
d.count = +d.count;
});
// Scale the range of the data
x.domain(d3.extent(data, function(d) { return d.year; }));
y.domain([0, d3.max(data, function(d) { return d.count; })]);
//nest the entries by symbol
var dataNest = d3.nest()
.key(function(d) {
return d.word;
})
.entries(data);
//define colors for the lines
var color = d3.scaleOrdinal(d3.schemeCategory10);
// spacing for the legend
legendSpace = width/dataNest.length;
var circleid = svg.selectAll("circle")
.data(dataNest)
.enter()
.append("g")
.attr("id", function(d){
return "circle" + d.key.replace(/\s+/g, '');
});
// Loop through each symbol / key
dataNest.forEach(function(d,i) {
svg.append("path")
.attr("class", "line")
.style("stroke", function() { // Add the colours dynamically
return d.color = color(d.key); })
.attr("id", 'tag'+d.key.replace(/\s+/g, '')) // assign ID
.attr("d", valueline(d.values));
// Add the Legend
svg.append("text")
.attr("x", width - margin.left + 50)
.attr("y", legendSpace/4 + i*(legendSpace/6))
.attr("class", "legend") // style the legend
.style("fill", function() { // Add the colours dynamically
return d.color = color(d.key); })
.on("click", function(){
// Determine if current line is visible
var active = d.active ? false : true,
newOpacity = active ? 0 : 1;
// Hide or show the elements based on the ID
d3.select("#tag"+d.key.replace(/\s+/g, ''))
.transition().duration(100)
.style("opacity", newOpacity);
// Update whether or not the elements are active
d.active = active;
// Hide or show the elements based on the ID
d3.select("circle" + d.key.replace(/\s+/g, ''))
.transition().duration(100)
.style("opacity", newOpacity);
})
.text(d.key);
});
// Add the scatterplot
svg.selectAll("dot")
.data(data)
.enter().append("circle")
.attr("r", 5)
.attr("cx", function(d) { return x(d.year); })
.attr("cy", function(d) { return y(d.count); })
.on("click", function(d) {
div.transition()
.duration(200)
.style("opacity", .9);
div .html(d.word + "<br/>" + d.count + "<br/>" + d.songs)
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 25) + "px");
})
.on("dblclick", function(d) {
div.transition()
.duration(500)
.style("opacity", 0);
})
;
// Add the X Axis
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
// Add the Y Axis
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
});
</script>
</body>
Right now, you are trying to set your id before you have added the circles to the graph. Instead, you can set the id when you're appending the circles to your graph.
// Add the scatterplot
svg.selectAll("dot")
.data(data)
.enter().append("circle")
.attr("id", function(d){ return "circle" + d.key.replace(/\s+/g, ''); })
// ^---- add the id here when you're appending the circles
.attr("r", 5)
.attr("cx", function(d) { return x(d.year); })
.attr("cy", function(d) { return y(d.count); })
.on("click", function(d) {
div.transition()
.duration(200)
.style("opacity", .9);
div .html(d.word + "<br/>" + d.count + "<br/>" + d.songs)
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 25) + "px");
})
.on("dblclick", function(d) {
div.transition()
.duration(500)
.style("opacity", 0);
})
;

How can I add a background colour behind PNG, when image URL is pulled from my D3.js dataset?

I am making a scatterplot, and pulling in an image fill for each circle on the plot. The problem is that the images are PNG's with transparent backgrounds. This means my overlapping circles show through each other:
Seen here - http://i.stack.imgur.com/bphon.png
I have tried setting a background colour with the CSS, but it seems to be completely overwritten by the .style("fill") in the JS. And I am looking to pull in 30ish images, so I don't want to have to save them all to be able to load the images with my CSS.
So, my question is, is there a way to put a white background behind my PNGs, while pulling those PNGs from URL's contained in my dataset?
Thanks for the help
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title></title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<style type="text/css">
.axis path,
.axis line {
fill: none;
stroke: black;
shape-rendering: crispEdges;
}
.axis-text {
font-family: sans-serif;
font-size: 11px;
background-color: white;
font-weight: bold;
}
.teamcircle {
background-color: white;
}
</style>
</head>
<body>
<div>
<input type="button" id="playerbtn" value="See Player View">
<input type="button" id="teambtn" value="See Team View">
</div>
<div id="data">
<div id="player-circles">
</div>
</div>
<script type="text/javascript">
//Width and height
var margin = {top: 50, right: 20, bottom: 30, left: 40};
var w = 960 - margin.left - margin.right;
var h = 500 - margin.top - margin.bottom;
//Create scale functions
var xScale = d3.scale.linear()
.range([0, w]);
var yScale = d3.scale.linear()
.range([h, 0]);
// var color = d3.scale.color();
// Define the Axis
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(yScale)
.orient("left");
//Create SVG element
var svg = d3.select("body")
.append("svg")
.attr("width", w + margin.left + margin.right)
.attr("height", h + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
//Load the TEAM data set
var teamdata = d3.tsv("team.tsv", function(error, teamdata) {
if (error) throw error;
teamdata.forEach(function(d) {
d.entriesper60 = +d.entriesper60;
d.carryinpercent = +d.carryinpercent;
});
xScale.domain(d3.extent(teamdata, function(d) { return d.carryinpercent; })).nice();
yScale.domain(d3.extent(teamdata, function(d) { return d.entriesper60; })).nice();
//Create X axis
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + h + ")")
.call(xAxis)
.append("text")
.attr("class", "axis-text")
.attr("x", w)
.attr("y", -6)
.style("text-anchor", "end")
.text("Carry-in %");
//Create Y axis
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("class", "axis-text")
.attr("y", -20)
.attr("z", 0)
.style("text-anchor", "middle")
.text("Entries/60")
// DEFS & Pattern for images
svg.append("defs")
.selectAll("pattern")
.data(teamdata)
.enter()
.append("pattern")
.attr('id', function(d, i) {
return d.name;
})
// .attr('patternUnits', 'userSpaceOnUse')
.attr('width', 20)
.attr('height', 20)
.append("image")
.attr("xlink:href", function(d) {
return d.image + d.name;
})
.attr('width', 20)
.attr('height', 20)
.attr("transform", "translate(2, 2)");
// Create TEAM Circles
svg.selectAll("circle")
.data(teamdata)
.enter()
.append("circle")
.attr("class", "teamcircle")
.style("stroke", function(d) { return d.hex; })
.style("stroke-width", 2)
.style("stroke-opacity", .8)
.attr("r", 12)
.attr("cx", function(d) { return xScale(d.carryinpercent); })
.attr("cy", function(d) { return yScale(d.entriesper60); })
.attr("fill", function(d) { return "url(#" + d.name + ")";
});
});
</script>
</body>
</html>
The best way I can think of is to create a group for every circle, and create a circle with a white background first. Something like this:
var teamCircle = svg.selectAll("g.teamcircle")
.data(teamdata)
.enter()
.append("g")
.attr("class", "teamcircle")
.transform(function(d){return "translate(" + xScale(d.carryinpercent) + "," + yScale(d.entriesper60) + ")"});
teamCircle.append("circle")
.attr("fill", "white")
.attr("r", 12)
teamCircle.append("circle")
.style("stroke", function(d) { return d.hex; })
.style("stroke-width", 2)
.style("stroke-opacity", .8)
.attr("r", 12)
.attr("fill", function(d) { return "url(#" + d.name + ")";

D3 change line stroke-width by hovering over legend

I made a two-line chart with a legend. I want to highlight the stroke-width of each of the lines to 4px based on the color of the legend circle when the user hovers over it. So, user hovers over the blue legend circle, the blue line stroke-width changes to 4px. Same for the red one if he hovers over the red circle on the legend. Is this possible to do based on my code? Here is the whole code:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>D3 Test</title>
<script type="text/javascript" src="../d3/d3.js"></script>
<script type="text/javascript" src="../d3/d3-tip.js"></script>
<style type="text/css">
body{
font: 16px Calibri;
}
.line1{
fill: none;
stroke: steelblue;
stroke-width: 2px;
}
.line1:hover{
stroke-width: 3.5px;
}
.line2{
fill: none;
stroke: red;
stroke-width: 2px;
}
.line2:hover{
stroke-width: 3.5px;
}
.axis path,
.axis line{
fill:none;
stroke: black;
stroke-width: 1px;
shape-rendering: crispEdges;
}
.axis text{
font-family: sans-serif;
font-size: 14px;
stroke: black;
stroke-width: 0.5px;
}
.legend .series {
cursor: pointer;
}
.legend circle {
stroke-width: 2px;
}
.legend .disabled circle {
fill-opacity: 0;
}
</style>
<!--...this code will be used on an external html file and instered-->
<html>
<body>
<div id="dropdown">
<select id = "opts">
<option value = "ds1">Atlas</option>
<option value = "ds2">BioSQL</option>
<option value = "ds3">Coppermine</option>
<option value = "ds4">Ensembl</option>
<option value = "ds5">Mediawiki</option>
<option value = "ds6">Opencart</option>
<option value = "ds7">PhpBB</option>
<option value = "ds8">Typo3</option>
</select>
</div>
<script type="text/javascript">
console.log("worked");
var ds1="../CSV/atlas/results/metrics.csv";
var ds2="../CSV/biosql/results/metrics.csv";
var ds3="../CSV/coppermine/results/metrics.csv";
var ds4="../CSV/ensembl/results/metrics.csv";
var ds5="../CSV/mediawiki/results/metrics.csv";
var ds6="../CSV/opencart/results/metrics.csv";
var ds7="../CSV/phpbb/results/metrics.csv";
var ds8="../CSV/typo3/results/metrics.csv";
</script>
</body>
</html> <!--...............................................................-->
<div id="area1"></div>
<div id="area2"></div>
</head>
<body>
<script type="text/javascript">
var margin = {top: 60, right: 20, bottom: 40, left: 40},
margin2 = {top: 430, right: 20, bottom: 0, left: 50},
width = 800 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
height2 = 870 - margin2.top - margin2.bottom;
var parseDate = d3.time.format("%Y").parse;
var color = d3.scale.category20()
.range(["#1f77b4", "#d62728", "#98df8a"]);
var x = d3.scale.linear()
.range([0, width]);
var x1 = d3.time.scale()
.nice(d3.time.year)
.range([0, width]);
var y = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.ticks(5);
var x1Axis = d3.svg.axis()
.scale(x1)
.orient("bottom")
.ticks(5);
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var svg1 = d3.select("#area1").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height2 + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var line1 = d3.svg.line()
.x(function(d) { return x(d.trID);})
.y(function (d) {return y(d.newT);})
.interpolate("basis");
var line2 = d3.svg.line()
.x(function(d) { return x1(d.time);})
.y(function (d) {return y(d.newT);})
.interpolate("basis");
var dsv = d3.dsv(";", "text/plain"); //setting the delimiter
var dataset = [] //defining the data array
var datapath="../CSV/atlas/results/metrics.csv";
dsv(datapath, function(data){ //------------select the file to load the csv------------
var label = document.getElementById('opts')[document.getElementById('opts').selectedIndex].innerHTML;//takes the name of the f
console.log(label);
dataset= data.map(function(d){ //parse
return { //insert parsed data in the array
trID: +d["trID"],
newT: +d["#newT"],
time: +d["time"]
};
});
dataset.forEach(function(d){
d.time = new Date(d.time*1000);
});
console.log(dataset);
x.domain(d3.extent(dataset, function(d) { return d.trID; }));
x1.domain(d3.extent(dataset, function(d) { return d.time; }));
// y.domain([0, d3.max(dataset.map( function(d) {return d.newT}))]);
y.domain(d3.extent(dataset, function(d) { return d.newT; }));
//------------------creating the lines---------------------
svg1.append("path")
.datum(dataset)
.attr("class", "line1")
.attr("d", line1);
svg1.append("path")
.datum(dataset)
.attr("class", "line2")
// .style("stroke-dasharray",("5,5"))
.attr("d", line2);
//----------------appending Legend--------------------------
var legend = svg1.selectAll(".legend")
.data((["Duration/Time","Duration/ID"]).slice().reverse())
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });
legend.append("circle")
.attr("r", 7)
.attr("cx", 45)
.attr("cy", 10)
.style("fill", color);
legend.append("text")
.attr("x", 54)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "begin")
.style("font-family", "Calibri")
.text(function(d) { return d; });
//-----------------------------------------------------------
svg1.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.append("text")
.attr("class", "label")
.attr("x", width)
.attr("y", -6)
.style("text-anchor", "end")
.text("trID");
svg1.append("g")
.attr("class", "x1 axis")
.attr("transform", "translate(0," + height2 + ")")
.call(x1Axis)
.append("text")
.attr("class", "label")
.attr("x", width)
.attr("y", -6)
.style("text-anchor", "end")
.text("time");
svg1.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("class", "label")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("num of tables");
svg1.append("text")
.attr("class","simpletext")
.attr("x", (width/2))
.attr("y", 0 - (margin.top/2))
.attr("text-anchor", "middle")
.style("font-size", "20px")
.style("text-decoration", "underline")
.text(label);
});
d3.select('#opts')
.on('change', function(){
var dataset=[]
var datapath = eval(d3.select(this).property('value'));
label = document.getElementById('opts')[document.getElementById('opts').selectedIndex].innerHTML;
dsv(datapath, function(data){ //------------select the file to load the csv------------
dataset= data.map(function(d){ //parse
return { //insert parsed data in the array
trID: +d["trID"],
newT: +d["#newT"],
time: +d["time"]
};
});
dataset.forEach(function(d){
d.time = new Date(d.time*1000);
});
x.domain(d3.extent(dataset, function(d) { return d.trID; }));
x1.domain(d3.extent(dataset, function(d) { return d.time; }));
// y.domain([0, d3.max(dataset.map( function(d) {return d.newT}))]);
y.domain(d3.extent(dataset, function(d) { return d.newT; }));
d3.selectAll(".line1")
.transition()
.duration(1000)
.attr("d", line1(dataset));
d3.selectAll(".line2")
.transition()
.duration(1000)
.attr("d", line2(dataset));
//Update Axis
//Update X axis
svg1.select(".x.axis")
.transition()
.duration(1000)
.call(xAxis);
svg1.select(".x1.axis")
.transition()
.duration(1000)
.call(x1Axis);
//Update Y axis
svg1.select(".y.axis")
.transition()
.duration(1000)
.call(yAxis);
svg1.selectAll("path")
.data(dataset)
.exit()
.remove();
console.log(label);
svg1.selectAll(".simpletext")
.transition()
.text(label);
/* .attr("x", (width/2))
.attr("y", 0 - (margin.top/2))
.attr("text-anchor", "middle")
.style("font-size", "20px")
.style("text-decoration", "underline")
.text(label);*/
});
});
</script>
</body>
</html>
I define my color as this:
var color = d3.scale.category20()
.range(["#1f77b4", "#d62728", "#98df8a"]);
And each of my lines as this:
var line1 = d3.svg.line()
.x(function(d) { return x(d.trID);})
.y(function (d) {return y(d.newT);})
.interpolate("basis");
var line2 = d3.svg.line()
.x(function(d) { return x1(d.time);})
.y(function (d) {return y(d.newT);})
.interpolate("basis");
Of course it's possible.
So first it has nothing to do with line1 and line2 - they are about data of the line, not actual visual element. You need to change style of the path elements .line1 and .line2.
You can add event listeners when creating the legend.
//----------------appending Legend--------------------------
var legend = svg1.selectAll(".legend")
.data((["Duration/Time","Duration/ID"]).slice().reverse())
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; })
.on("mouseover", function(d) {
if (d === "Duration/ID") {
// line1's legend is hovered
svg1.selectAll(".line1").style("stroke-width", "4px");
}
else if (d === "Duration/Time") {
// line2's legend is hovered
svg1.selectAll(".line2").style("stroke-width", "4px");
}
})
.on("mouseout", function() {
// revert the styles
svg1.selectAll(".line1").style("stroke-width", "2px");
svg1.selectAll(".line2").style("stroke-width", "2px");
});
This is just a demonstration. Of course it would be more consistent to define the styles in css and change class on events, and you may not want to hardcode legend names when you have more/dynamic series.

Color d3 area based on computed value

I've created a line graph with d3.nest() objects, but I need the fill color for each segment to be based on another variable. I thought I could just do it based on the gradient from the data file, but it actually needs to be computed over the distance of the segment. Right now, everything is coming back as one color when it should be
if gradient < -10
color = red
if gradient < -5
color = green
if gradient < 0
color = white
if gradient < 5
color = yellow
if gradient < 10
color = black
else
color = blue
Here's a Plunk
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
font: 12px Arial;
}
text.shadow {
stroke: #fff;
stroke-width: 2.5px;
opacity: 0.9;
}
path {
stroke: steelblue;
stroke-width: 2;
fill: none;
}
.axis path,
.axis line {
fill: none;
stroke: grey;
stroke-width: 1;
shape-rendering: crispEdges;
}
.grid .tick {
stroke: lightgrey;
stroke-opacity: 0.7;
shape-rendering: crispEdges;
}
.grid path {
stroke-width: 0;
}
.area {
stroke-width: 0;
}
</style>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
var margin = {top: 30, right: 20, bottom: 35, left: 50},
width = 600 - margin.left - margin.right,
height = 270 - margin.top - margin.bottom;
var color = d3.scale.ordinal()
.domain([-10,-5,0,5,10])
.range(['red','green','white','yellow','black','blue']);
var x = d3.scale.linear().range([0, width]);
var y = d3.scale.linear().range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.ticks(5);
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.ticks(5);
var area = d3.svg.area()
.x(function(d) { return x(d.distance); })
.y0(height)
.y1(function(d) { return y(d.elevation); });
var valueline = d3.svg.line()
.x(function(d) { return x(d.distance); })
.y(function(d) { return y(d.elevation); });
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 + ")");
// function for the x grid lines
function make_x_axis() {
return d3.svg.axis()
.scale(x)
.orient("bottom")
.ticks(5)
}
// function for the y grid lines
function make_y_axis() {
return d3.svg.axis()
.scale(y)
.orient("left")
.ticks(5)
}
// Get the data
d3.csv("data.csv", function(error, data) {
data.forEach(function(d) {
d.distance = +d.distance;
d.elevation = +d.elevation;
d.gradient = +d.gradient;
});
var dataGroup = d3.nest()
.key(function(d) {
return d.grade;
})
.entries(data);
dataGroup.forEach(function(group, i) {
if(i < dataGroup.length - 1) {
group.values.push(dataGroup[i+1].values[0])
}
})
// Scale the range of the data
x.domain(d3.extent(data, function(d) { return d.distance; }));
y.domain([0, d3.max(data, function(d) { return d.elevation; })]);
dataGroup.forEach(function(d, i){
svg.append("path")
.datum(d.values)
.attr("class", "area")
.attr("d", area);
});
svg.selectAll(".area")
.style("fill", function(d) { return color(d.gradient); });
// Draw the x Grid lines
svg.append("g")
.attr("class", "grid")
.attr("transform", "translate(0," + height + ")")
.call(make_x_axis()
.tickSize(-height, 0, 0)
.tickFormat("")
)
// Draw the y Grid lines
svg.append("g")
.attr("class", "grid")
.call(make_y_axis()
.tickSize(-width, 0, 0)
.tickFormat("")
)
// Add the valueline path.
svg.append("path")
.attr("d", valueline(data));
// Add the X Axis
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
// Add the Y Axis
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
// Add the text label for the X axis
svg.append("text")
.attr("transform",
"translate(" + (width/2) + " ," +
(height+margin.bottom) + ")")
.style("text-anchor", "middle")
.text("Distance");
// Add the text label for the Y axis
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("x", margin.top - (height / 2))
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("");
// Add the title
svg.append("text")
.attr("x", (width / 2))
.attr("y", 0 - (margin.top / 2))
.attr("text-anchor", "middle")
.style("font-size", "16px")
.style("text-decoration", "underline")
.text("Elevation Graph");
});
</script>
</body>
The issue is with this call:
svg.selectAll(".area")
.style("fill", function(d) { return color(d.gradient); });
Here, "d" is an array of objects of length one or two depending on the index. I don't know which object's gradient you wish to use, but my naive solution without understanding your data would be to use d[0]. Here's the Plunk.

Resources