Adding circles to multi line graph with dropdown on d3.js - javascript

I am trying to add circles to the data points on the following line graph example: https://bl.ocks.org/ProQuestionAsker/8382f70af7f4a7355827c6dc4ee8817d
To generate the circles I have used the following:
svg.selectAll("dot")
.data(data)
.enter().append("circle")
.attr("r", 3)
.attr("color", "pink")
.attr("cx", function(d) { return x(d.Month); })
.attr("cy", function(d) { return y(+d.Sales); });
However, as seen here, all the circles for each every fruit. I would like only the circles for selected fruits to appear, as per the lines.
Many thanks
James

You see the circles for each and every fruit because you're not filtering the data based on the dropdown selection.
Here's a snippet doing that data filtering and appending the dots:
var dataAsCsv = `Month,Sales,Fruit,Year
Jan,87,strawberry,2016
Feb,3,strawberry,2016
Mar,89,strawberry,2016
Apr,56,strawberry,2016
May,1,strawberry,2016
Jun,17,strawberry,2016
Jul,59,strawberry,2016
Aug,43,strawberry,2016
Sep,16,strawberry,2016
Oct,94,strawberry,2016
Nov,99,strawberry,2016
Dec,53,strawberry,2016
Jan,93,grape,2016
Feb,8,grape,2016
Mar,95,grape,2016
Apr,62,grape,2016
May,5,grape,2016
Jun,24,grape,2016
Jul,62,grape,2016
Aug,49,grape,2016
Sep,18,grape,2016
Oct,101,grape,2016
Nov,103,grape,2016
Dec,53,grape,2016
Jan,94,blueberry,2016
Feb,15,blueberry,2016
Mar,95,blueberry,2016
Apr,64,blueberry,2016
May,11,blueberry,2016
Jun,33,blueberry,2016
Jul,64,blueberry,2016
Aug,53,blueberry,2016
Sep,27,blueberry,2016
Oct,103,blueberry,2016
Nov,108,blueberry,2016
Dec,62,blueberry,2016
Jan,80,strawberry,2015
Feb,0,strawberry,2015
Mar,71,strawberry,2015
Apr,51,strawberry,2015
May,3,strawberry,2015
Jun,11,strawberry,2015
Jul,56,strawberry,2015
Aug,34,strawberry,2015
Sep,12,strawberry,2015
Oct,75,strawberry,2015
Nov,94,strawberry,2015
Dec,46,strawberry,2015
Jan,76,grape,2015
Feb,0,grape,2015
Mar,78,grape,2015
Apr,58,grape,2015
May,10,grape,2015
Jun,22,grape,2015
Jul,47,grape,2015
Aug,36,grape,2015
Sep,18,grape,2015
Oct,86,grape,2015
Nov,98,grape,2015
Dec,40,grape,2015
Jan,79,blueberry,2015
Feb,0,blueberry,2015
Mar,78,blueberry,2015
Apr,49,blueberry,2015
May,5,blueberry,2015
Jun,31,blueberry,2015
Jul,62,blueberry,2015
Aug,49,blueberry,2015
Sep,7,blueberry,2015
Oct,86,blueberry,2015
Nov,100,blueberry,2015
Dec,46,blueberry,2015`;
// Set the margins
var margin = {top: 60, right: 100, bottom: 20, left: 80},
width = 850 - margin.left - margin.right,
height = 370 - margin.top - margin.bottom;
// Parse the month variable
var parseMonth = d3.timeParse("%b");
var formatMonth = d3.timeFormat("%b");
var formatYear = d3.timeFormat("%Y");
var parseYear = d3.timeParse("%Y");
// Set the ranges
var x = d3.scaleTime().domain([parseMonth("Jan"), parseMonth("Dec")]).range([0, width]);
var y = d3.scaleLinear().range([height, 0]);
// Define the line
var valueLine = d3.line()
.x(function(d) { return x(d.Month); })
.y(function(d) { return y(+d.Sales); })
// Create the svg canvas in the "graph" div
var svg = d3.select("#graph")
.append("svg")
.style("width", width + margin.left + margin.right + "px")
.style("height", height + margin.top + margin.bottom + "px")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform","translate(" + margin.left + "," + margin.top + ")")
.attr("class", "svg");
var data = d3.csvParse(dataAsCsv);
// Format the data
data.forEach(function(d) {
d.Month = parseMonth(d.Month);
d.Sales = +d.Sales;
d.Fruit = d.Fruit;
d.Year = formatYear(parseYear(+d.Year));
});
var nest = d3.nest()
.key(function(d){
return d.Fruit;
})
.key(function(d){
return d.Year;
})
.entries(data)
// Scale the range of the data
x.domain(d3.extent(data, function(d) { return d.Month; }));
y.domain([0, d3.max(data, function(d) { return d.Sales; })]);
// Set up the x axis
var xaxis = svg.append("g")
.attr("transform", "translate(0," + height + ")")
.attr("class", "x axis")
.call(d3.axisBottom(x)
.ticks(d3.timeMonth)
.tickSize(0, 0)
.tickFormat(d3.timeFormat("%B"))
.tickSizeInner(0)
.tickPadding(10));
// Add the Y Axis
var yaxis = svg.append("g")
.attr("class", "y axis")
.call(d3.axisLeft(y)
.ticks(5)
.tickSizeInner(0)
.tickPadding(6)
.tickSize(0, 0));
// Add a label to the y axis
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 0 - 60)
.attr("x", 0 - (height / 2))
.attr("dy", "1em")
.style("text-anchor", "middle")
.text("Monthly Sales")
.attr("class", "y axis label");
svg.append('g').classed('data-points', true);
// Create a dropdown
var fruitMenu = d3.select("#fruitDropdown")
fruitMenu
.append("select")
.selectAll("option")
.data(nest)
.enter()
.append("option")
.attr("value", function(d){
return d.key;
})
.text(function(d){
return d.key;
})
// Function to create the initial graph
var initialGraph = function(fruit){
// Filter the data to include only fruit of interest
var selectFruit = nest.filter(function(d){
return d.key == fruit;
})
var selectFruitGroups = svg.selectAll(".fruitGroups")
.data(selectFruit, function(d){
return d ? d.key : this.key;
})
.enter()
.append("g")
.attr("class", "fruitGroups")
var initialPath = selectFruitGroups.selectAll(".line")
.data(function(d) { return d.values; })
.enter()
.append("path")
initialPath
.attr("d", function(d){
return valueLine(d.values)
})
.attr("class", "line")
svg.select('g.data-points').selectAll("dot")
.data(data.filter(function(d) {
return d.Fruit === fruit;
}))
.enter().append("circle").classed('dot', true)
.attr("r", 3)
.style("fill", "pink").style('stroke', '#000')
.attr("cx", function(d) { return x(d.Month); })
.attr("cy", function(d) { return y(+d.Sales); });
}
// Create initial graph
initialGraph("strawberry")
// Update the data
var updateGraph = function(fruit){
// Filter the data to include only fruit of interest
var selectFruit = nest.filter(function(d){
return d.key == fruit;
})
// Select all of the grouped elements and update the data
var selectFruitGroups = svg.selectAll(".fruitGroups")
.data(selectFruit)
// Select all the lines and transition to new positions
selectFruitGroups.selectAll("path.line")
.data(function(d){
return (d.values);
})
.transition()
.duration(1000)
.attr("d", function(d){
return valueLine(d.values)
});
var circles = svg.select('g.data-points').selectAll(".dot")
.data(data.filter(function(d) {
return d.Fruit === fruit;
}));
circles
.enter().append("circle")
.merge(circles).classed('data-point', true)
.attr("r", 3)
.style("fill", "pink").style('stroke', '#000')
.transition().duration(1000)
.attr("cx", function(d) { return x(d.Month); })
.attr("cy", function(d) { return y(+d.Sales); });
}
// Run update function when dropdown selection changes
fruitMenu.on('change', function(){
// Find which fruit was selected from the dropdown
var selectedFruit = d3.select(this)
.select("select")
.property("value")
// Run update function with the selected fruit
updateGraph(selectedFruit)
});
.line {
fill: none;
stroke: #EF5285;
stroke-width: 2px;
}
<script src="https://d3js.org/d3.v4.js"></script>
<div id = "fruitDropdown"></div>
<div id="graph"></div>
Important code changes:
Instead of appending circles directly to the SVG, I've created a group <g class="data-points"></g> that holds all the dots.
svg.append('g').classed('data-points', true);
Enter/update/exit all dots within the above group in both functions i.e. initialGraph and updateGraph
InitialGraph:
svg.select('g.data-points').selectAll("dot")
.data(data.filter(function(d) {
return d.Fruit === fruit;
}))
.enter().append("circle").classed('dot', true)
.attr("r", 3)
.style("fill", "pink").style('stroke', '#000')
.attr("cx", function(d) { return x(d.Month); })
.attr("cy", function(d) { return y(+d.Sales); });
UpdateGraph:
var circles = svg.select('g.data-points').selectAll(".dot")
.data(data.filter(function(d) {
return d.Fruit === fruit;
}));
circles
.enter().append("circle")
.merge(circles).classed('data-point', true)
.attr("r", 3)
.style("fill", "pink").style('stroke', '#000')
.transition().duration(1000)
.attr("cx", function(d) { return x(d.Month); })
.attr("cy", function(d) { return y(+d.Sales); });
Observe the data filtering based on the fruit selected bound to the circles and the transition applied in order to match the transition for the lines.
Always use style for applying fill and not attr. It's a good practice. Adding color:pink wouldn't change the color of the circles but fill would. In addition, I've added a stroke in order to make them visible even with a pink color. You can always change that though.
I would suggest you to add a code snippet every time you ask a question and not provide links. That would be easier for anyone to debug and help fix errors.
Hope this helps. :)

Related

How to get data labels to show in D3 stacked bar chart?

I'm trying to get data labels to show inside of each bar of my stacked bar chart. When I view source, I can see the <text> elements in each bar with the correct number, but they aren't visible in the bar itself
<!DOCTYPE html>
<meta charset="utf-8">
<!-- Load d3.js -->
<script src="https://d3js.org/d3.v4.js"></script>
<!-- Create a div where the graph will take place -->
<div id="my_dataviz"></div>
<div id="legend"></div>
<style>
</style>
<script>
// set the dimensions and margins of the graph
var margin = { top: 10, right: 30, bottom: 20, left: 50 },
width = 460 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
// append the svg object to the body of the page
var svg = d3.select("#my_dataviz")
.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 + ")");
// Parse the Data
d3.csv("https://raw.githubusercontent.com/JakeRatliff/dh-valve-data/main/Valves%20Data%20-%20Sheet1.csv", function(data) {
// List of subgroups = header of the csv files = soil condition here
var subgroups = data.columns.slice(1)
subgroups.pop()
console.log(subgroups)
// List of groups = species here = value of the first column called group -> I show them on the X axis
var groups = d3.map(data, function(d) { return (d.Year) }).keys()
console.log(groups)
// Add X axis
var x = d3.scaleBand()
.domain(groups)
.range([0, width])
.padding([0.2])
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x).tickSizeOuter(0));
// Add Y axis
var y = d3.scaleLinear()
//.domain([0, 60])
.domain([0, d3.max(data, function(d) { return +d.Total; })])
.range([height, 0]);
svg.append("g")
.call(d3.axisLeft(y));
// color palette = one color per subgroup
var color = d3.scaleOrdinal()
.domain(subgroups)
.range(['#00539B', '#E0750B'])
//stack the data? --> stack per subgroup
var stackedData = d3.stack()
.keys(subgroups)
(data)
// Show the bars
svg.append("g")
.selectAll("g")
// Enter in the stack data = loop key per key = group per group
.data(stackedData)
.enter().append("g")
.attr("fill", function(d) { return color(d.key); })
.selectAll("rect")
// enter a second time = loop subgroup per subgroup to add all rectangles
.data(function(d) { return d; })
.enter().append("rect")
.attr("x", function(d) { return x(d.data.Year); })
.attr("y", function(d) { return y(d[1]); })
.attr("height", function(d) { return y(d[0]) - y(d[1]); })
.attr("width", x.bandwidth())
.attr("class", "bar")
.append("text")
.attr("x", function(d) { return x(d.data.Year); })
.attr("y", function(d) { return y(d[1]); })
.text(function(d) { return d[1] })
})
</script>
Here is a codepen if that is preferred:
https://codepen.io/jake2134/pen/QWMQJOB
Thanks in advance for any help. I've Google around but the results seem to be outdated.
Use a variable to create a group, then append twice to it.
var bar_groups = svg.append("g")
.selectAll("g")
// Enter in the stack data = loop key per key = group per group
.data(stackedData)
.enter().append("g")
.attr("fill", function(d) { return color(d.key); })
var bars = bar_groups.selectAll("g")
// enter a second time = loop subgroup per subgroup to add all rectangles
.data(function(d) { return d; })
.enter().append("g")
bars.append('rect')
.attr("x", function(d) { return x(d.data.Year); })
.attr("y", function(d) { return y(d[1]); })
.attr("height", function(d) { return y(d[0]) - y(d[1]); })
.attr("width", x.bandwidth())
.attr("class", "bar")
bars.append("text")
.attr("x", function(d) { return x(d.data.Year); })
.attr("y", function(d) { return y(d[1]); })
.text(function(d) { return d[1] })

Make circles match same color filtering as multi-line on d3.js?

I have a multi-line graph that updates when filtered by each fruit. Each line color corresponds to a different year of sales. With the help of Shashank, the circles on the line for each data-point have been added to the group instead of appending directly to the SVG. Here's a snippet showing this:
var dataAsCsv = `Month,Sales,Fruit,Year
Jan,87,strawberry,2016
Feb,3,strawberry,2016
Mar,89,strawberry,2016
Apr,56,strawberry,2016
May,1,strawberry,2016
Jun,17,strawberry,2016
Jul,59,strawberry,2016
Aug,43,strawberry,2016
Sep,16,strawberry,2016
Oct,94,strawberry,2016
Nov,99,strawberry,2016
Dec,53,strawberry,2016
Jan,93,grape,2016
Feb,8,grape,2016
Mar,95,grape,2016
Apr,62,grape,2016
May,5,grape,2016
Jun,24,grape,2016
Jul,62,grape,2016
Aug,49,grape,2016
Sep,18,grape,2016
Oct,101,grape,2016
Nov,103,grape,2016
Dec,53,grape,2016
Jan,94,blueberry,2016
Feb,15,blueberry,2016
Mar,95,blueberry,2016
Apr,64,blueberry,2016
May,11,blueberry,2016
Jun,33,blueberry,2016
Jul,64,blueberry,2016
Aug,53,blueberry,2016
Sep,27,blueberry,2016
Oct,103,blueberry,2016
Nov,108,blueberry,2016
Dec,62,blueberry,2016
Jan,80,strawberry,2015
Feb,0,strawberry,2015
Mar,71,strawberry,2015
Apr,51,strawberry,2015
May,3,strawberry,2015
Jun,11,strawberry,2015
Jul,56,strawberry,2015
Aug,34,strawberry,2015
Sep,12,strawberry,2015
Oct,75,strawberry,2015
Nov,94,strawberry,2015
Dec,46,strawberry,2015
Jan,76,grape,2015
Feb,0,grape,2015
Mar,78,grape,2015
Apr,58,grape,2015
May,10,grape,2015
Jun,22,grape,2015
Jul,47,grape,2015
Aug,36,grape,2015
Sep,18,grape,2015
Oct,86,grape,2015
Nov,98,grape,2015
Dec,40,grape,2015
Jan,79,blueberry,2015
Feb,0,blueberry,2015
Mar,78,blueberry,2015
Apr,49,blueberry,2015
May,5,blueberry,2015
Jun,31,blueberry,2015
Jul,62,blueberry,2015
Aug,49,blueberry,2015
Sep,7,blueberry,2015
Oct,86,blueberry,2015
Nov,100,blueberry,2015
Dec,46,blueberry,2015`;
// Set the margins
var margin = {top: 60, right: 100, bottom: 20, left: 80},
width = 850 - margin.left - margin.right,
height = 370 - margin.top - margin.bottom;
//Legend sizing
var legendRectSize = 18;
var legendSpacing = 4;
// Parse the month variable
var parseMonth = d3.timeParse("%b");
var formatMonth = d3.timeFormat("%b");
var formatYear = d3.timeFormat("%Y");
var parseYear = d3.timeParse("%Y");
// Set the ranges
var x = d3.scaleTime().domain([parseMonth("Jan"), parseMonth("Dec")]).range([0, width]);
var y = d3.scaleLinear().range([height, 0]);
var colors = d3.scaleOrdinal()
.domain(["2016", "2015"])
.range(["#00BFFF", "#87CEFA"]);
// Define the line
var valueLine = d3.line()
.x(function(d) { return x(d.Month); })
.y(function(d) { return y(+d.Sales); })
// Define the div for the tooltip
var div = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
// Create the svg canvas in the "graph" div
var svg = d3.select("#graph")
.append("svg")
.style("width", width + margin.left + margin.right + "px")
.style("height", height + margin.top + margin.bottom + "px")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform","translate(" + margin.left + "," + margin.top + ")")
.attr("class", "svg");
var data = d3.csvParse(dataAsCsv);
// Format the data
data.forEach(function(d) {
d.Month = parseMonth(d.Month);
d.Sales = +d.Sales;
d.Fruit = d.Fruit;
d.Year = formatYear(parseYear(+d.Year));
});
var nest = d3.nest()
.key(function(d){
return d.Fruit;
})
.key(function(d){
return d.Year;
})
.entries(data)
// Scale the range of the data
x.domain(d3.extent(data, function(d) { return d.Month; }));
y.domain([0, d3.max(data, function(d) { return d.Sales; })]);
// Set up the x axis
var xaxis = svg.append("g")
.attr("transform", "translate(0," + height + ")")
.attr("class", "x axis")
.style("font-family", "Courier New")
.call(d3.axisBottom(x)
.ticks(d3.timeMonth)
.tickSize(0, 0)
.tickFormat(d3.timeFormat("%B"))
.tickSizeInner(0)
.tickPadding(10));
// Add the Y Axis
var yaxis = svg.append("g")
.attr("class", "y axis")
.style("font-family", "Courier New")
.call(d3.axisLeft(y)
.ticks(5)
.tickSizeInner(0)
.tickPadding(6)
.tickSize(0, 0));
// Add a label to the y axis
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 0 - 60)
.attr("x", 0 - (height / 2))
.attr("dy", "1em")
.style("text-anchor", "middle")
.text("Monthly Sales")
.attr("class", "y axis label")
.style("font-family", "Courier New")
.style("font-size", "11px");
//Add Title
svg.append("text")
.attr("y", 0)
.attr("x", width/2)
.attr("dy", "1em")
.style("text-anchor", "middle")
.text("Fruit Sales")
.attr("class", "y axis label")
.style("font-family", "Courier New")
.style("font-size", "20px");
svg.append('g').classed('data-points', true);
// Create a dropdown
var fruitMenu = d3.select("#fruitDropdown")
fruitMenu
.append("select")
.selectAll("option")
.data(nest)
.enter()
.append("option")
.attr("value", function(d){
return d.key;
})
.text(function(d){
return d.key;
})
// Function to create the initial graph
var initialGraph = function(fruit){
// Filter the data to include only fruit of interest
var selectFruit = nest.filter(function(d){
return d.key == fruit;
})
var selectFruitGroups = svg.selectAll(".fruitGroups")
.data(selectFruit, function(d){
return d ? d.key : this.key;
})
.enter()
.append("g")
.attr("class", "fruitGroups")
var initialPath = selectFruitGroups.selectAll(".line")
.data(function(d) { return d.values; })
.enter()
.append("path")
.attr("stroke", function(d){ return colors(d.key)});
initialPath
.attr("d", function(d){
return valueLine(d.values)
})
.attr("class", "line")
svg.select('g.data-points').selectAll("dot")
.data(data.filter(function(d) {
return d.Fruit === fruit;
}))
.enter().append("circle").classed('dot', true)
.attr("r", 3)
.attr("fill", function(d){ return colors(d.key)})
.attr("cx", function(d) { return x(d.Month); })
.attr("cy", function(d) { return y(+d.Sales); })
.on("mouseover", function(d) {
div.transition()
.duration(200)
.style("opacity", .9);
div .html("Sales:" + " " + d.Sales)
.style("font-family", "Courier New")
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px");
})
.on("mouseout", function(d) {
div.transition()
.duration(500)
.style("opacity", 0);
});
}
// Create initial graph
initialGraph("strawberry")
// Update the data
var updateGraph = function(fruit){
// Filter the data to include only fruit of interest
var selectFruit = nest.filter(function(d){
return d.key == fruit;
})
// Select all of the grouped elements and update the data
var selectFruitGroups = svg.selectAll(".fruitGroups")
.data(selectFruit)
// Select all the lines and transition to new positions
selectFruitGroups.selectAll("path.line")
.data(function(d){
return (d.values);
})
.transition()
.duration(1000)
.attr("d", function(d){
return valueLine(d.values)
});
var circles = svg.select('g.data-points').selectAll(".dot")
.data(data.filter(function(d) {
return d.Fruit === fruit;
}));
circles
.enter().append("circle")
.merge(circles).classed('data-point', true)
.attr("r", 3)
.attr("fill", function(d){ return colors(d.key)})
.transition().duration(1000)
.attr("cx", function(d) { return x(d.Month); })
.attr("cy", function(d) { return y(+d.Sales); });
}
var legend = svg.selectAll('.legend')
.data(colors.domain()) // NEW
.enter() // NEW
.append('g') // NEW
.attr('class', 'legend') // NEW
.attr('transform', function(d, i) { // NEW
var height1 = legendRectSize + legendSpacing; // NEW
var offset = height1 * colors.domain().length /2; // NEW
var horz = 37 * legendRectSize; // NEW
var vert = i * height1 - offset; // NEW
return 'translate(' + horz + ',' + vert + ')'; // NEW
}); // NEW
legend.append('rect') // NEW
.attr('width', legendRectSize) // NEW
.attr('height', legendRectSize) // NEW
.style('fill', colors) // NEW
.style('stroke', colors); // NEW
legend.append('text') // NEW
.attr('x', legendRectSize + legendSpacing) // NEW
.attr('y', legendRectSize - legendSpacing)
.style("font-family", "Courier New")
.text(function(d) { return d; });
// Run update function when dropdown selection changes
fruitMenu.on('change', function(){
// Find which fruit was selected from the dropdown
var selectedFruit = d3.select(this)
.select("select")
.property("value")
// Run update function with the selected fruit
updateGraph(selectedFruit)
});
.line {
fill: none;
stroke-width: 2px;
}
div.tooltip {
position: absolute;
text-align: center;
width: 60px;
height: 28px;
padding: 2px;
font: 12px sans-serif;
background: lightsteelblue;
border: 0px;
border-radius: 8px;
pointer-events: none;
}
.dot:hover {
fill: black;
}
.legend { /* NEW */
font-size: 10px; /* NEW */
} /* NEW */
rect { /* NEW */
stroke-width: 2; /* NEW */
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>D3 Page Template</title>
<script src="https://d3js.org/d3.v4.js"></script>
<link rel="stylesheet" href="style.css">
</head>
<body>
<g class="data-points"></g>
<div id = "fruitDropdown"></div>
<div id="graph"></div>
<script src="Example11.js"></script>
</body>
</html>
How can I ensure the circles take the same color as the line whilst ensuring that they update when a different fruit is selected? I'm hoping this solution will also provide a fix for the extra rectangle in the legend.
Many thanks,
James
Changing fill for the circles from colors(d.key) to colors(d.Year) did the trick buddy.
Code change and the snippet:
.attr("fill", function(d){ return colors(d.Year)})
var dataAsCsv = `Month,Sales,Fruit,Year
Jan,87,strawberry,2016
Feb,3,strawberry,2016
Mar,89,strawberry,2016
Apr,56,strawberry,2016
May,1,strawberry,2016
Jun,17,strawberry,2016
Jul,59,strawberry,2016
Aug,43,strawberry,2016
Sep,16,strawberry,2016
Oct,94,strawberry,2016
Nov,99,strawberry,2016
Dec,53,strawberry,2016
Jan,93,grape,2016
Feb,8,grape,2016
Mar,95,grape,2016
Apr,62,grape,2016
May,5,grape,2016
Jun,24,grape,2016
Jul,62,grape,2016
Aug,49,grape,2016
Sep,18,grape,2016
Oct,101,grape,2016
Nov,103,grape,2016
Dec,53,grape,2016
Jan,94,blueberry,2016
Feb,15,blueberry,2016
Mar,95,blueberry,2016
Apr,64,blueberry,2016
May,11,blueberry,2016
Jun,33,blueberry,2016
Jul,64,blueberry,2016
Aug,53,blueberry,2016
Sep,27,blueberry,2016
Oct,103,blueberry,2016
Nov,108,blueberry,2016
Dec,62,blueberry,2016
Jan,80,strawberry,2015
Feb,0,strawberry,2015
Mar,71,strawberry,2015
Apr,51,strawberry,2015
May,3,strawberry,2015
Jun,11,strawberry,2015
Jul,56,strawberry,2015
Aug,34,strawberry,2015
Sep,12,strawberry,2015
Oct,75,strawberry,2015
Nov,94,strawberry,2015
Dec,46,strawberry,2015
Jan,76,grape,2015
Feb,0,grape,2015
Mar,78,grape,2015
Apr,58,grape,2015
May,10,grape,2015
Jun,22,grape,2015
Jul,47,grape,2015
Aug,36,grape,2015
Sep,18,grape,2015
Oct,86,grape,2015
Nov,98,grape,2015
Dec,40,grape,2015
Jan,79,blueberry,2015
Feb,0,blueberry,2015
Mar,78,blueberry,2015
Apr,49,blueberry,2015
May,5,blueberry,2015
Jun,31,blueberry,2015
Jul,62,blueberry,2015
Aug,49,blueberry,2015
Sep,7,blueberry,2015
Oct,86,blueberry,2015
Nov,100,blueberry,2015
Dec,46,blueberry,2015`;
// Set the margins
var margin = {top: 60, right: 100, bottom: 20, left: 80},
width = 850 - margin.left - margin.right,
height = 370 - margin.top - margin.bottom;
//Legend sizing
var legendRectSize = 18;
var legendSpacing = 4;
// Parse the month variable
var parseMonth = d3.timeParse("%b");
var formatMonth = d3.timeFormat("%b");
var formatYear = d3.timeFormat("%Y");
var parseYear = d3.timeParse("%Y");
// Set the ranges
var x = d3.scaleTime().domain([parseMonth("Jan"), parseMonth("Dec")]).range([0, width]);
var y = d3.scaleLinear().range([height, 0]);
var colors = d3.scaleOrdinal()
.domain(["2016", "2015"])
.range(["#00BFFF", "#87CEFA"]);
// Define the line
var valueLine = d3.line()
.x(function(d) { return x(d.Month); })
.y(function(d) { return y(+d.Sales); })
// Define the div for the tooltip
var div = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
// Create the svg canvas in the "graph" div
var svg = d3.select("#graph")
.append("svg")
.style("width", width + margin.left + margin.right + "px")
.style("height", height + margin.top + margin.bottom + "px")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform","translate(" + margin.left + "," + margin.top + ")")
.attr("class", "svg");
var data = d3.csvParse(dataAsCsv);
// Format the data
data.forEach(function(d) {
d.Month = parseMonth(d.Month);
d.Sales = +d.Sales;
d.Fruit = d.Fruit;
d.Year = formatYear(parseYear(+d.Year));
});
var nest = d3.nest()
.key(function(d){
return d.Fruit;
})
.key(function(d){
return d.Year;
})
.entries(data)
// Scale the range of the data
x.domain(d3.extent(data, function(d) { return d.Month; }));
y.domain([0, d3.max(data, function(d) { return d.Sales; })]);
// Set up the x axis
var xaxis = svg.append("g")
.attr("transform", "translate(0," + height + ")")
.attr("class", "x axis")
.style("font-family", "Courier New")
.call(d3.axisBottom(x)
.ticks(d3.timeMonth)
.tickSize(0, 0)
.tickFormat(d3.timeFormat("%B"))
.tickSizeInner(0)
.tickPadding(10));
// Add the Y Axis
var yaxis = svg.append("g")
.attr("class", "y axis")
.style("font-family", "Courier New")
.call(d3.axisLeft(y)
.ticks(5)
.tickSizeInner(0)
.tickPadding(6)
.tickSize(0, 0));
// Add a label to the y axis
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 0 - 60)
.attr("x", 0 - (height / 2))
.attr("dy", "1em")
.style("text-anchor", "middle")
.text("Monthly Sales")
.attr("class", "y axis label")
.style("font-family", "Courier New")
.style("font-size", "11px");
//Add Title
svg.append("text")
.attr("y", 0)
.attr("x", width/2)
.attr("dy", "1em")
.style("text-anchor", "middle")
.text("Fruit Sales")
.attr("class", "y axis label")
.style("font-family", "Courier New")
.style("font-size", "20px");
svg.append('g').classed('data-points', true);
// Create a dropdown
var fruitMenu = d3.select("#fruitDropdown")
fruitMenu
.append("select")
.selectAll("option")
.data(nest)
.enter()
.append("option")
.attr("value", function(d){
return d.key;
})
.text(function(d){
return d.key;
})
// Function to create the initial graph
var initialGraph = function(fruit){
// Filter the data to include only fruit of interest
var selectFruit = nest.filter(function(d){
return d.key == fruit;
})
var selectFruitGroups = svg.selectAll(".fruitGroups")
.data(selectFruit, function(d){
return d ? d.key : this.key;
})
.enter()
.append("g")
.attr("class", "fruitGroups")
var initialPath = selectFruitGroups.selectAll(".line")
.data(function(d) { return d.values; })
.enter()
.append("path")
.attr("stroke", function(d){ return colors(d.key)});
initialPath
.attr("d", function(d){
return valueLine(d.values)
})
.attr("class", "line")
svg.select('g.data-points').selectAll("dot")
.data(data.filter(function(d) {
return d.Fruit === fruit;
}))
.enter().append("circle").classed('dot', true)
.attr("r", 3)
.attr("fill", function(d){ return colors(d.Year)})
.attr("cx", function(d) { return x(d.Month); })
.attr("cy", function(d) { return y(+d.Sales); })
.on("mouseover", function(d) {
div.transition()
.duration(200)
.style("opacity", .9);
div .html("Sales:" + " " + d.Sales)
.style("font-family", "Courier New")
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px");
})
.on("mouseout", function(d) {
div.transition()
.duration(500)
.style("opacity", 0);
});
}
// Create initial graph
initialGraph("strawberry")
// Update the data
var updateGraph = function(fruit){
// Filter the data to include only fruit of interest
var selectFruit = nest.filter(function(d){
return d.key == fruit;
})
// Select all of the grouped elements and update the data
var selectFruitGroups = svg.selectAll(".fruitGroups")
.data(selectFruit)
// Select all the lines and transition to new positions
selectFruitGroups.selectAll("path.line")
.data(function(d){
return (d.values);
})
.transition()
.duration(1000)
.attr("d", function(d){
return valueLine(d.values)
});
var circles = svg.select('g.data-points').selectAll(".dot")
.data(data.filter(function(d) {
return d.Fruit === fruit;
}));
circles
.enter().append("circle")
.merge(circles).classed('data-point', true)
.attr("r", 3)
.attr("fill", function(d){ return colors(d.Year)})
.transition().duration(1000)
.attr("cx", function(d) { return x(d.Month); })
.attr("cy", function(d) { return y(+d.Sales); });
}
var legend = svg.selectAll('.legend')
.data(colors.domain()) // NEW
.enter() // NEW
.append('g') // NEW
.attr('class', 'legend') // NEW
.attr('transform', function(d, i) { // NEW
var height1 = legendRectSize + legendSpacing; // NEW
var offset = height1 * colors.domain().length /2; // NEW
var horz = 37 * legendRectSize; // NEW
var vert = i * height1 - offset; // NEW
return 'translate(' + horz + ',' + vert + ')'; // NEW
}); // NEW
legend.append('rect') // NEW
.attr('width', legendRectSize) // NEW
.attr('height', legendRectSize) // NEW
.style('fill', colors) // NEW
.style('stroke', colors); // NEW
legend.append('text') // NEW
.attr('x', legendRectSize + legendSpacing) // NEW
.attr('y', legendRectSize - legendSpacing)
.style("font-family", "Courier New")
.text(function(d) { return d; });
// Run update function when dropdown selection changes
fruitMenu.on('change', function(){
// Find which fruit was selected from the dropdown
var selectedFruit = d3.select(this)
.select("select")
.property("value")
// Run update function with the selected fruit
updateGraph(selectedFruit)
});
.line {
fill: none;
stroke-width: 2px;
}
div.tooltip {
position: absolute;
text-align: center;
width: 60px;
height: 28px;
padding: 2px;
font: 12px sans-serif;
background: lightsteelblue;
border: 0px;
border-radius: 8px;
pointer-events: none;
}
.dot:hover {
fill: black;
}
.legend { /* NEW */
font-size: 10px; /* NEW */
} /* NEW */
rect { /* NEW */
stroke-width: 2; /* NEW */
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>D3 Page Template</title>
<script src="https://d3js.org/d3.v4.js"></script>
<link rel="stylesheet" href="style.css">
</head>
<body>
<g class="data-points"></g>
<div id = "fruitDropdown"></div>
<div id="graph"></div>
<script src="Example11.js"></script>
</body>
</html>
Also, if you're up for suggestions, I'd like you to arrange fruitGroups and corresponding circles within the same group. I'd suggest you have a structure as follows:
<g class="fruitGroups">
<g class="2015">
<path></path>
<circle class="dot"></circle>
...
</g>
<g class="2016">
<path></path>
<circle></circle>
...
</g>
</g>
EDIT BASED ON COMMENTS
To change colors for the fruits, as you mentioned in the comments, I've used separate color domains and here's how it is used in determining the color of the path in the updateGraph function.
For path:
.style('stroke', function(d) { if (fruit == "strawberry") {return colors(d.key)} else if (fruit == "grape") {return colors1(d.key)} else {return colors2(d.key)}});
For circles:
.style("fill", function(d){ if (d.Fruit == "strawberry") {return colors(d.Year)} else if (d.Fruit == "grape") {return colors1(d.Year)} else {return colors2(d.Year)}})
For legends:
// rerender legend rects
svg.selectAll('.legend rect')
.style('fill', function(d) { if (fruit == "strawberry") {return colors(d)} else if (fruit == "grape") {return colors1(d)} else {return colors2(d)}})
.style('stroke', function(d) { if (fruit == "strawberry") {return colors(d)} else if (fruit == "grape") {return colors1(d)} else {return colors2(d)}});
Here's a snippet doing all of the above:
var dataAsCsv = `Month,Sales,Fruit,Year
Jan,87,strawberry,2016
Feb,3,strawberry,2016
Mar,89,strawberry,2016
Apr,56,strawberry,2016
May,1,strawberry,2016
Jun,17,strawberry,2016
Jul,59,strawberry,2016
Aug,43,strawberry,2016
Sep,16,strawberry,2016
Oct,94,strawberry,2016
Nov,99,strawberry,2016
Dec,53,strawberry,2016
Jan,93,grape,2016
Feb,8,grape,2016
Mar,95,grape,2016
Apr,62,grape,2016
May,5,grape,2016
Jun,24,grape,2016
Jul,62,grape,2016
Aug,49,grape,2016
Sep,18,grape,2016
Oct,101,grape,2016
Nov,103,grape,2016
Dec,53,grape,2016
Jan,94,blueberry,2016
Feb,15,blueberry,2016
Mar,95,blueberry,2016
Apr,64,blueberry,2016
May,11,blueberry,2016
Jun,33,blueberry,2016
Jul,64,blueberry,2016
Aug,53,blueberry,2016
Sep,27,blueberry,2016
Oct,103,blueberry,2016
Nov,108,blueberry,2016
Dec,62,blueberry,2016
Jan,80,strawberry,2015
Feb,0,strawberry,2015
Mar,71,strawberry,2015
Apr,51,strawberry,2015
May,3,strawberry,2015
Jun,11,strawberry,2015
Jul,56,strawberry,2015
Aug,34,strawberry,2015
Sep,12,strawberry,2015
Oct,75,strawberry,2015
Nov,94,strawberry,2015
Dec,46,strawberry,2015
Jan,76,grape,2015
Feb,0,grape,2015
Mar,78,grape,2015
Apr,58,grape,2015
May,10,grape,2015
Jun,22,grape,2015
Jul,47,grape,2015
Aug,36,grape,2015
Sep,18,grape,2015
Oct,86,grape,2015
Nov,98,grape,2015
Dec,40,grape,2015
Jan,79,blueberry,2015
Feb,0,blueberry,2015
Mar,78,blueberry,2015
Apr,49,blueberry,2015
May,5,blueberry,2015
Jun,31,blueberry,2015
Jul,62,blueberry,2015
Aug,49,blueberry,2015
Sep,7,blueberry,2015
Oct,86,blueberry,2015
Nov,100,blueberry,2015
Dec,46,blueberry,2015`;
// Set the margins
var margin = {top: 60, right: 100, bottom: 20, left: 80},
width = 850 - margin.left - margin.right,
height = 370 - margin.top - margin.bottom;
//Legend sizing
var legendRectSize = 18;
var legendSpacing = 4;
// Parse the month variable
var parseMonth = d3.timeParse("%b");
var formatMonth = d3.timeFormat("%b");
var formatYear = d3.timeFormat("%Y");
var parseYear = d3.timeParse("%Y");
// Set the ranges
var x = d3.scaleTime().domain([parseMonth("Jan"), parseMonth("Dec")]).range([0, width]);
var y = d3.scaleLinear().range([height, 0]);
var colors = d3.scaleOrdinal()
.domain(["2016", "2015"])
.range(["#00BFFF", "#87CEFA"]);
var colors1 = d3.scaleOrdinal()
.domain(["2016", "2015"])
.range(["red", "orange"]);
var colors2 = d3.scaleOrdinal()
.domain(["2016", "2015"])
.range(["darkgreen", "lightgreen"]);
// Define the line
var valueLine = d3.line()
.x(function(d) { return x(d.Month); })
.y(function(d) { return y(+d.Sales); })
// Define the div for the tooltip
var div = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
// Create the svg canvas in the "graph" div
var svg = d3.select("#graph")
.append("svg")
.style("width", width + margin.left + margin.right + "px")
.style("height", height + margin.top + margin.bottom + "px")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform","translate(" + margin.left + "," + margin.top + ")")
.attr("class", "svg");
var data = d3.csvParse(dataAsCsv);
// Format the data
data.forEach(function(d) {
d.Month = parseMonth(d.Month);
d.Sales = +d.Sales;
d.Fruit = d.Fruit;
d.Year = formatYear(parseYear(+d.Year));
});
var nest = d3.nest()
.key(function(d){
return d.Fruit;
})
.key(function(d){
return d.Year;
})
.entries(data)
// Scale the range of the data
x.domain(d3.extent(data, function(d) { return d.Month; }));
y.domain([0, d3.max(data, function(d) { return d.Sales; })]);
// Set up the x axis
var xaxis = svg.append("g")
.attr("transform", "translate(0," + height + ")")
.attr("class", "x axis")
.style("font-family", "Courier New")
.call(d3.axisBottom(x)
.ticks(d3.timeMonth)
.tickSize(0, 0)
.tickFormat(d3.timeFormat("%B"))
.tickSizeInner(0)
.tickPadding(10));
// Add the Y Axis
var yaxis = svg.append("g")
.attr("class", "y axis")
.style("font-family", "Courier New")
.call(d3.axisLeft(y)
.ticks(5)
.tickSizeInner(0)
.tickPadding(6)
.tickSize(0, 0));
// Add a label to the y axis
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 0 - 60)
.attr("x", 0 - (height / 2))
.attr("dy", "1em")
.style("text-anchor", "middle")
.text("Monthly Sales")
.attr("class", "y axis label")
.style("font-family", "Courier New")
.style("font-size", "11px");
//Add Title
svg.append("text")
.attr("y", 0)
.attr("x", width/2)
.attr("dy", "1em")
.style("text-anchor", "middle")
.text("Fruit Sales")
.attr("class", "y axis label")
.style("font-family", "Courier New")
.style("font-size", "20px");
svg.append('g').classed('data-points', true);
// Create a dropdown
var fruitMenu = d3.select("#fruitDropdown")
fruitMenu
.append("select")
.selectAll("option")
.data(nest)
.enter()
.append("option")
.attr("value", function(d){
return d.key;
})
.text(function(d){
return d.key;
})
// Function to create the initial graph
var initialGraph = function(fruit){
// Filter the data to include only fruit of interest
var selectFruit = nest.filter(function(d){
return d.key == fruit;
})
var selectFruitGroups = svg.selectAll(".fruitGroups")
.data(selectFruit, function(d){
return d ? d.key : this.key;
})
.enter()
.append("g")
.attr("class", "fruitGroups")
var initialPath = selectFruitGroups.selectAll(".line")
.data(function(d) { return d.values; })
.enter()
.append("path")
.attr("stroke", function(d){ if (fruit == "strawberry") {return colors(d.key)} else if (fruit == "grape") {return colors1(d.key)} else {return colors2(d.key)}});
initialPath
.attr("d", function(d){
return valueLine(d.values)
})
.attr("class", "line")
svg.select('g.data-points').selectAll("dot")
.data(data.filter(function(d) {
return d.Fruit === fruit;
}))
.enter().append("circle").classed('dot', true)
.attr("r", 3)
.attr("fill", function(d){ if (d.Fruit == "strawberry") {return colors(d.Year)} else if (d.Fruit == "grape") {return colors1(d.Year)} else {return colors2(d.Year)}})
.attr("cx", function(d) { return x(d.Month); })
.attr("cy", function(d) { return y(+d.Sales); })
.on("mouseover", function(d) {
div.transition()
.duration(200)
.style("opacity", .9);
div .html("Sales:" + " " + d.Sales)
.style("font-family", "Courier New")
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px");
})
.on("mouseout", function(d) {
div.transition()
.duration(500)
.style("opacity", 0);
});
}
// Create initial graph
initialGraph("strawberry");
// Update the data
var updateGraph = function(fruit){
// Filter the data to include only fruit of interest
var selectFruit = nest.filter(function(d){
return d.key == fruit;
})
// Select all of the grouped elements and update the data
var selectFruitGroups = svg.selectAll(".fruitGroups")
.data(selectFruit)
// Select all the lines and transition to new positions
selectFruitGroups.selectAll("path.line")
.data(function(d){
return (d.values);
})
.transition()
.duration(1000)
.attr("d", function(d){
return valueLine(d.values)
}).style('stroke', function(d) { if (fruit == "strawberry") {return colors(d.key)} else if (fruit == "grape") {return colors1(d.key)} else {return colors2(d.key)}});
var circles = svg.select('g.data-points').selectAll(".dot")
.data(data.filter(function(d) {
return d.Fruit === fruit;
}));
circles
.enter().append("circle")
.merge(circles).classed('data-point', true)
.attr("r", 3)
.transition().duration(1000)
.style("fill", function(d){ if (d.Fruit == "strawberry") {return colors(d.Year)} else if (d.Fruit == "grape") {return colors1(d.Year)} else {return colors2(d.Year)}})
.attr("cx", function(d) { return x(d.Month); })
.attr("cy", function(d) { return y(+d.Sales); });
// rerender legend rects
svg.selectAll('.legend rect')
.style('fill', function(d) { if (fruit == "strawberry") {return colors(d)} else if (fruit == "grape") {return colors1(d)} else {return colors2(d)}})
.style('stroke', function(d) { if (fruit == "strawberry") {return colors(d)} else if (fruit == "grape") {return colors1(d)} else {return colors2(d)}});
}
var legend = svg.selectAll('.legend')
.data(colors.domain()) // NEW
.enter() // NEW
.append('g') // NEW
.attr('class', 'legend') // NEW
.attr('transform', function(d, i) { // NEW
var height1 = legendRectSize + legendSpacing; // NEW
var offset = height1 * colors.domain().length /2; // NEW
var horz = 37 * legendRectSize; // NEW
var vert = i * height1 - offset; // NEW
return 'translate(' + horz + ',' + vert + ')'; // NEW
}); // NEW
legend.append('rect') // NEW
.attr('width', legendRectSize) // NEW
.attr('height', legendRectSize) // NEW
.style('fill', colors) // NEW
.style('stroke', colors); // NEW
legend.append('text') // NEW
.attr('x', legendRectSize + legendSpacing) // NEW
.attr('y', legendRectSize - legendSpacing)
.style("font-family", "Courier New")
.text(function(d) { return d; });
// Run update function when dropdown selection changes
fruitMenu.on('change', function(){
// Find which fruit was selected from the dropdown
var selectedFruit = d3.select(this)
.select("select")
.property("value")
// Run update function with the selected fruit
updateGraph(selectedFruit)
});
.line {
fill: none;
stroke-width: 2px;
}
div.tooltip {
position: absolute;
text-align: center;
width: 60px;
height: 28px;
padding: 2px;
font: 12px sans-serif;
background: lightsteelblue;
border: 0px;
border-radius: 8px;
pointer-events: none;
}
.dot:hover {
fill: black;
}
.legend { /* NEW */
font-size: 10px; /* NEW */
} /* NEW */
rect { /* NEW */
stroke-width: 2; /* NEW */
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>D3 Page Template</title>
<script src="https://d3js.org/d3.v4.js"></script>
<link rel="stylesheet" href="style.css">
</head>
<body>
<g class="data-points"></g>
<div id = "fruitDropdown"></div>
<div id="graph"></div>
<script src="Example11.js"></script>
</body>
</html>
Improvements that you can work on:
Color domains :)
Render the legends once the paths are transitioned (use the .on('end, function(){}) callback. If you aren't sure on how to use that, refer my answer: Transition on end callback
Fix bugs (if any)
Hope this helps and let me know if you have any questions.

Keep legend constant and only update chart in D3

my coding is to plot x axis with location, y axis with (value1,value2 or value3) and legend with types(high, medium,low). what I'm trying to do is to add menu with value1,2,3 and add legend with different types so if I change from either menu or click on legend, plot got updated with only selected data.
however, my code below is only able to create legend set as default type or clicked but not able to include all types. is there any way to include all types in legends constantly no matter what type is clicked and only update chart accordingly?
thank you,
<script>
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = 960- margin.left - margin.right,
height = 900 - margin.top - margin.bottom,
radius = 3.5,
padding = 1,
xVar = "location",
cVar= " type";
default = "high";
// add the tooltip area to the webpage
var tooltip = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
// force data to update when menu is changed
var menu = d3.select("#menu select")
.on("change", change);
// load data
d3.csv("sample.csv", function(error, data) {
formatted = data;
draw();
});
// set terms of transition that will take place
// when new indicator from menu or legend is chosen
function change() {
//remove old plot and data
var svg = d3.select("svg");
svg.transition().duration(100).remove();
//redraw new plot with new data
d3.transition()
.duration(750)
.each(draw)
}
function draw() {
// add the graph canvas to the body of the webpage
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 + ")")
// setup x
var xValue = function(d) { return d[xVar];}, // data -> value
xScale = d3.scale.ordinal()
.rangeRoundBands([0,width],1), //value -> display
xMap = function(d) { return (xScale(xValue(d)) + Math.random()*10);}, // data -> display
xAxis = d3.svg.axis().scale(xScale).orient("bottom");
// setup y
var yVar = menu.property("value"),
yValue = function(d) { return d[yVar];}, // data -> value
yScale = d3.scale.linear().range([height, 0]), // value -> display
yMap = function(d) { return yScale(yValue(d));}, // data -> display
yAxis = d3.svg.axis().scale(yScale).orient("left");
// setup fill color
var cValue = function(d) { return d[cVar];},
color = d3.scale.category10();
// filter the unwanted data and plot with only chosen dataset.
data = formatted.filter(function(d, i)
{
if (d[cVar] == default)
{
return d;
}
});
data = formatted;
// change string (from CSV) into number format
data.forEach(function(d) {
d[xVar] = d[xVar];
d[yVar] = +d[yVar];
});
xScale.domain(data.sort(function(a, b) { return d3.ascending(a[xVar], b[xVar])})
.map(xValue) );
// don't want dots overlapping axis, so add in buffer to data domain
yScale.domain([d3.min(data, yValue)-1, d3.max(data, yValue)+1]);
// x-axis
svg.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(xVar);
// y-axis
svg.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(yVar);
// draw dots
var dot = svg.selectAll(".dot")
.data(data)
.enter()
.append("circle")
.attr("class", "dot")
.attr("r", radius)
.attr("cx", xMap)
.attr("cy", yMap)
.style("fill", function(d) { return color(cValue(d));})
.on("mouseover", function(d) {
tooltip.transition()
.duration(200)
.style("opacity", .9);
tooltip.html(d[SN] + "<br/> (" + xValue(d)
+ ", " + yValue(d) + ")")
.style("left", (d3.event.pageX + 5) + "px")
.style("top", (d3.event.pageY - 28) + "px");
})
.on("mouseout", function(d) {
tooltip.transition()
.duration(500)
.style("opacity", 0);
});
// draw legend
var legend = svg.selectAll(".legend")
.data(color.domain().slice())
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });
// draw legend colored rectangles
legend.append("rect")
.attr("x", width - 18)
.attr("width", 18)
.attr("height", 18)
.style("fill", color)
.on("click", function (d){
default = d;
return change();
});
// draw legend text
legend.append("text")
.attr("x", width - 24)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d) { return d;})
.on("click", function (d) {
default = d;
return change();
});
};
</script>
</body>
sample.csv
location type value1 value2 value3
A high 1 -2 -5
B medium 2 3 4
C low 4 1 2
C medium 6 3 4
A high 4 5 6
D low -1 3 2
I found a way to include all types in the legend.
first, extract unique types from column "type" and save them in the "legend_keys" as array. second, instead of pre-define "default", set the first type in the "legend_keys" as a default. but next default will be set by the event on click out of legend.
d3.csv("sample.csv", function(error, data) {
formatted = data;
var nest = d3.nest()
.key(function(d) { return d[cVar]; })
.entries(formatted);
console.log(nest);
legend_keys = nest.map(function(o){return o.key});
default = legend_keys[0];
//console.log(legend_keys[0]);
draw();
});
Finally, when define the legend, read "legend_keys" as data as below.
By doing this, I can always keep all types in the legend.
var legend = svg.selectAll(".legend")
.data(legend_keys)
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; })
.on("click", function (d){
default = d;
console.log(default);
return change();
});

How to Resize D3 Line Charts

I have a question about resizing D3 multi line charts. I followed Building Responsive Visualizations with D3.js. But I am having trouble updating the elements inside of my chart. I am successfully updating the axis, but not the lines themselves. I also am unsure of how to update the dots and text in the graph.
Thanks for your help!
Here is the code I have so far:
<svg id="graph"></svg>
<script>
// Set the dimensions of the canvas / graph
var margin = 40,
width = 960 - margin*2,
height = 500 - margin*2;
// Parse the date / time
var parseDate = d3.time.format("%Y-%m-%d").parse;
// Set the ranges
var x = d3.time.scale().range([0, width]);
var y = d3.scale.linear().range([height, 0]);
// Define the axes
var xAxis = d3.svg.axis().scale(x)
.orient("bottom").ticks(5);
var yAxis = d3.svg.axis().scale(y)
.orient("left").ticks(8);
// Define the line
var valueline = d3.svg.line()
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.average_ticnum); });
var valueline2 = d3.svg.line()
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.average_fatiguenum); });
var valueline3 = d3.svg.line()
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.average_stressnum); });
var valueline4 = d3.svg.line()
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.sum_nf_sugars); });
var valueline5 = d3.svg.line()
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.sum_nf_total_carbohydrate); });
// Adds the svg canvas
var graph = d3.select("#graph")
.attr("width", width + margin*2)
.attr("height", height + margin*2)
.append("g")
.attr("transform", "translate(" + margin + "," + margin + ")");
// Get the data
d3.json("progress_output.php?useremail=<?php echo $useremail; ?>", function(error, data) {
data.forEach(function(d) {
d.date = parseDate(d.date);
d.average_ticnum = +d.average_ticnum;
d.fatiguenum = +d.average_fatiguenum;
d.stressnum = +d.average_stressnum;
});
// Scale the range of the data
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain([0,12]);
// Add the valueline path.
graph.append("path")
.attr("class", "line")
.style("stroke", "#891f83")
.attr("id","ticLine")
.attr("d", valueline(data));
graph.append("path")
.attr("class", "line")
.style("stroke", "#7db6e3")
.attr("id","fatigueLine")
.attr("d", valueline2(data));
graph.append("path")
.attr("class", "line")
.style("stroke", "#36376a")
.attr("id","stressLine")
.attr("d", valueline3(data));
graph.append("path")
.attr("class", "line")
.style("stroke", "#9bcf81")
.attr("id","sugarLine")
.attr("d", valueline4(data));
graph.append("path")
.attr("class", "line")
.style("stroke", "#efa465")
.attr("id","carbLine")
.attr("d", valueline5(data));
// Add the Dots
graph.selectAll("dot")
.data(data)
.enter().append("circle")
.attr("id","ticDots")
.attr("r", 3.5)
.style("fill", "#891f83")
.attr("cx", function(d) { return x(d.date); })
.attr("cy", function(d) { return y(d.average_ticnum); });
graph.selectAll("dot")
.data(data)
.enter().append("circle")
.attr("r", 3.5)
.style("fill", "#7db6e3")
.attr("id","fatigueDots")
.attr("cx", function(d) { return x(d.date); })
.attr("cy", function(d) { return y(d.average_fatiguenum); });
graph.selectAll("dot")
.data(data)
.enter().append("circle")
.attr("r", 3.5)
.style("fill", "#36376a")
.attr("id", "stressDots")
.attr("cx", function(d) { return x(d.date); })
.attr("cy", function(d) { return y(d.average_stressnum); });
graph.selectAll("dot")
.data(data)
.enter().append("circle")
.attr("id","sugarDots")
.attr("r", 3.5)
.style("fill", "#9bcf81")
.attr("cx", function(d) { return x(d.date); })
.attr("cy", function(d) { return y(d.sum_nf_sugars); });
graph.selectAll("dot")
.data(data)
.enter().append("circle")
.attr("r", 3.5)
.style("fill", "#efa465")
.attr("id","carbDots")
.attr("cx", function(d) { return x(d.date); })
.attr("cy", function(d) { return y(d.sum_nf_total_carbohydrate); });
// Add the X Axis
graph.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
// Add the Y Axis
graph.append("g")
.attr("class", "y axis")
.call(yAxis);
//Add the Tic title
graph.append("text")
.attr("x", 15)
.attr("y", 0)
.attr("class", "legend")
.attr("id", "ticText")
.style("color", "#891f83")
.style("fill", "#891f83")
.style("cursor", "pointer")
.style("font-family", "cooperbook")
.on("click", function(){
// Determine if current line is visible
var active = ticLine.active ? false : true,
newOpacity = active ? 0 : 1;
// Hide or show the elements
width = 200;
height = 200;
d3.select("#ticLine").style("opacity", newOpacity);
d3.selectAll("#ticDots").style("opacity", newOpacity);
// Update whether or not the elements are active
ticLine.active = active;
})
.text("Tic Severity");
//Add the fatigue title
graph.append("text")
.attr("x", 115)
.attr("y", 0)
.attr("class", "legend")
.style("color", "#7bd6e3")
.style("fill", "#7bd6e3")
.style("cursor", "pointer")
.style("font-family", "cooperbook")
.on("click", function(){
// Determine if current line is visible
var active = fatigueLine.active ? false : true,
newOpacity = active ? 0 : 1;
// Hide or show the elements
d3.select("#fatigueLine").style("opacity", newOpacity);
d3.selectAll("#fatigueDots").style("opacity", newOpacity);
// Update whether or not the elements are active
fatigueLine.active = active;
})
.text("Fatigue");
//Add the Stress title
graph.append("text")
.attr("x", 190)
.attr("y", 0)
.attr("class", "legend")
.style("color", "#36376a")
.style("fill", "#36376a")
.style("cursor", "pointer")
.style("font-family", "cooperbook")
.on("click", function(){
// Determine if current line is visible
var active = stressLine.active ? false : true,
newOpacity = active ? 0 : 1;
// Hide or show the elements
d3.select("#stressLine").style("opacity", newOpacity);
d3.selectAll("#stressDots").style("opacity", newOpacity);
// Update whether or not the elements are active
stressLine.active = active;
})
.text("Stress");
//Add the sugar title
graph.append("text")
.attr("x", 250)
.attr("y", 0)
.attr("class", "legend")
.style("color", "#9bcf81")
.style("fill", "#9bcf81")
.style("cursor", "pointer")
.style("font-family", "cooperbook")
.on("click", function(){
// Determine if current line is visible
var active = sugarLine.active ? false : true,
newOpacity = active ? 0 : 1;
// Hide or show the elements
d3.select("#sugarLine").style("opacity", newOpacity);
d3.selectAll("#sugarDots").style("opacity", newOpacity);
// Update whether or not the elements are active
sugarLine.active = active;
})
.text("Sugars");
//Add the carb title
graph.append("text")
.attr("x", 320)
.attr("y", 0)
.attr("class", "legend")
.style("color", "#efa465")
.style("fill", "#efa465")
.style("cursor", "pointer")
.style("font-family", "cooperbook")
.on("click", function(){
// Determine if current line is visible
var active = carbLine.active ? false : true,
newOpacity = active ? 0 : 1;
// Hide or show the elements
d3.select("#carbLine").style("opacity", newOpacity);
d3.selectAll("#carbDots").style("opacity", newOpacity);
// Update whether or not the elements are active
carbLine.active = active;
})
.text("Carbohydrates");
});
function resize() {
/* Find the new window dimensions */
var width = parseInt(d3.select("#graph").style("width")) - margin*2,
height = parseInt(d3.select("#graph").style("height")) - margin*2;
/* Update the range of the scale with new width/height */
x.range([0, width]).nice(d3.time.year);
y.range([height, 0]).nice();
/* Update the axis with the new scale */
graph.select('.x.axis')
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
graph.select('.y.axis')
.call(yAxis);
/* Force D3 to recalculate and update the line */
graph.selectAll('.line')
.attr("d", valueline(data));
}
d3.select(window).on('resize', resize);

d3 nested data - individual graphs - setting y.domain?

I am trying to turn some nested data into 8 individual line charts (one chart for each key). So far I am creating one graph per svg, however I am having issues with the y-domain - specifically setting the y-domain for each graph.
currently:
var line = d3.svg.line()
.x(function (d) { return x(d.date); })
.y(function (d) { return y(d.app); })
x.domain(d3.extent([parseDate("2003.0"), parseDate("2014.0")]));
y.domain(d3.extent([0,20000]));
var data2 = d3.nest()
.key(function(d) { return d.race; })
.entries(data);
var svgContainer = d3.selectAll("body")
.data(data2)
.enter()
.append("svg")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.append("path")
.data(data2)
.attr('id', function(d) { return d.key ;})
.attr('class', 'line')
.attr('opacity', .8)
.attr('d', function(d) { return line(d.values); });
I see parts of some lines in the svg's, but most are cut off. Any suggestions? I'm not sure if the paths are correct either.
Data:
{ key: "1", values: 0: ['app' : 50000, year: '2003'], 1: ['app': 20000, year: '2004'],
key: "2" values: 0: ['app' : 40000, year: '2003'], 1: ['app' 50000, year: '2004']
etc...}
Modified d3 using a different X scale and Y scale domain for each selection
var svgContainer = d3.select("body").selectAll(".line")
.data(data2)
.enter()
.append("svg")
.attr("transform", "translate(" + margin.left + "," + margin.top+ ")")
.append("g")
.each(function(d, i){
var eachRace = d.values;
console.log(eachRace);
var svg = d3.select(this);
var yMax = d3.max(eachRace, function(d) { return d.app; });
var yScale = d3.scale.linear().domain([0, yMax]).range([height/8, 0]);
var yAxis = d3.svg.axis().scale(yScale).orient("left").ticks(5);
var line = d3.svg.line()
.x(function (d) { return x(d.date); })
.y(function (d) { return yScale(d.app); })
svg.append("path")
.attr("id", function(d) { return d.key ;})
.attr('class', 'line')
.attr('opacity', .8)
.attr('d', function(d) { return line(d.values); })
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
})
You can apply extent directly to your data like so:
yScale.domain(d3.extent(data, function(d) { return d.app; }));
This will give you the extent of all the data. If you need to get the extent of portions of the data, such as in your case of one category vs. another, you need to get the extent of the result of some filtering function. You should look into either d3.filter or write your own within extent(). So you'd probably want to make the return value contingent on d.key matching your current key, however you are storing that.

Categories