Scaling y-axis appropriate to data in multiple line chart display - javascript

The multiple line chart example at https://www.d3-graph-gallery.com/graph/line_smallmultiple.html quite clearly provides the examples I need for what I'm trying to do...
except...
I need the y-axis scale for each of the charts to be appropriate for the data associated with the individual keys. As is, the example does d3.max on the entire data set, not the filtered data set controlling the individual lines.
I've tried various ways to apply the filter in the y-axis definition and can't get anything to work.
The closest I've been able to get is to make it use the max value from one of the specific keys for all the charts.
var y = d3.scaleLinear()
// .domain([0, d3.max(data, function(d) { return +d.n; })])
.domain([0, d3.max(data.filter(d => d.name === "Helen"), e => +e.n)])
.range([ height, 0 ]);
svg.append("g")
.call(d3.axisLeft(y).ticks(5));
I think I want it to filter d.name against the CURRENT-CHART key (whatever it might be) rather than a specific one (like "Helen" above), but can't figure out how to do it. Is it some feature of nesting that I haven't found yet? Something amazingly simple that I can't see??
Any suggestions?
Thanks in advance

I have built a demo for you, i hope you are looking for something like this. Please let me know if there is any issue.
// set the dimensions and margins of the graph
var margin = {top: 30, right: 0, bottom: 30, left: 50},
width = 210 - margin.left - margin.right,
height = 210 - margin.top - margin.bottom;
//Read the data
d3.csv("https://raw.githubusercontent.com/holtzy/data_to_viz/master/Example_dataset/5_OneCatSevNumOrdered.csv", function(data) {
// group the data: I want to draw one line per group
var sumstat = d3.nest() // nest function allows to group the calculation per level of a factor
.key(function(d) { return d.name;})
.entries(data);
// What is the list of groups?
allKeys = sumstat.map(function(d){return d.key})
// Add X axis --> it is a date format
var x = d3.scaleLinear()
.domain(d3.extent(data, function(d) { return d.year; }))
.range([ 0, width ]);
// color palette
var color = d3.scaleOrdinal()
.domain(allKeys)
.range(['#e41a1c','#377eb8','#4daf4a','#984ea3','#ff7f00','#ffff33','#a65628','#f781bf','#999999'])
// Add an svg element for each group. The will be one beside each other and will go on the next row when no more room available
var svg = d3.select("#my_dataviz")
.selectAll("uniqueChart")
.data(sumstat)
.enter()
.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 + ")")
.each(multiple);
svg
.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x).ticks(3));
// Add titles
svg
.append("text")
.attr("text-anchor", "start")
.attr("y", -5)
.attr("x", 0)
.text(function(d){ return(d.key)})
.style("fill", function(d){ return color(d.key) })
function multiple(item) {
var svg = d3.select(this);
var y = d3.scaleLinear()
.domain([0, d3.max(item.values, function(d) { return +d.n; })])
.range([height, 0]);
svg.append("g")
.call(d3.axisLeft(y).ticks(5));
var line = d3.line()
.x(function(d) { return x(+d.year); })
.y(function(d) { return y(+d.n); });
// Draw the line
svg
.append("path")
.attr("fill", "none")
.attr("stroke", function(d){ return color(d.key) })
.attr("stroke-width", 1.9)
.attr("d", line(item.values))
}
})
<!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>

Related

d3.v5.min.js:2 Error: <path> attribute d: Expected moveto path command ('M' or 'm'), "function a(a){va…"

I am trying to plot a multi-line graph with d3.js but I am getting this error:
Error: attribute d: Expected moveto path command ('M' or 'm'), "function t(t){va…".
I have been stuck at it a while now and tried everything I could think of, to help you in your reflections, Find the code that I use below.
// set the dimensions and margins of the graph
var margin = {top: 10, right: 30, bottom: 30, left: 60},
width = 460 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
const dateParser = d3.timeFormat("%Y-%m-%d %H:%M:%S");
data.forEach(function(d) {
d.ts = dateParser(new Date(d.ts));
d.value = parseFloat(d.value)
});
// append the svg object to the body of the page
var svg = d3.select("#graph")
.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 + ")");
//Read the data
// group the data: I want to draw one line per group
var sumstat = d3.nest() // nest function allows to group the calculation per level of a factor
.key(function(d) { return d.key;})
.entries(data);
// Add X axis --> it is a date format
var x = d3.scaleLinear()
.domain(d3.extent(data, function(d) { return d.ts; }))
.range([ 0, width ]);
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x).ticks(5));
// Add Y axis
var y = d3.scaleLinear()
.domain([0, d3.max(data, function(d) { return +d.value; })])
.range([ height, 0 ]);
svg.append("g")
.call(d3.axisLeft(y));
// color palette
var res = sumstat.map(function(d){ return d.key }) // list of group names
var color = d3.scaleOrdinal()
.domain(res).range(['#e41a1c','#377eb8','#4daf4a','#984ea3','#ff7f00','#ffff33','#a65628','#f781bf','#999999'])
// Draw the line
svg.selectAll(".line")
.data(sumstat)
.enter()
.append("path")
.attr("fill", "none")
.attr("stroke", function(d){ return color(d.key) })
.attr("stroke-width", 1.5)
.attr("d", function(d){
return d3.line()
.x(function(d) { return x(d.ts); })
.y(function(d) { return y(d.value); })
})
I cannot figure what I am doing wrong, in case the example I am looking at is this link. I am fairly new to d3 and it is not an easy library to use
When you set the d attribute, you return the line generator itself from the data linking function, but you fail to execute it. Configuring the generator and executing it is a two-step process:
first, contruct and configure the generator (line is a function)
var line = d3.line()
.x(function(d) { return x(d.ts); })
.y(function(d) { return y(d.value); })
then, pass the function to attr(), so it will be executed as line(d)
svg.selectAll(".line")
.data(sumstat)
.enter()
.append("path")
.attr("fill", "none")
.attr("stroke", function(d){ return color(d.key) })
.attr("stroke-width", 1.5)
.attr("d", line)

D3 scatterplot newbe

I'm new to D3 and need a simple scatterplot.
The problem is that the data is not showing up where I want it to show up. I made some test data giving values for x and y between 100 an 200 but the dots
always seem to be in the same place on the screen. What I change to domain or range they show up on the same place. I think It must be something fundamental but I cant find it. Please give me a clue.
This is the code from the test:
<!DOCTYPE html>
<meta charset="utf-8">
<!-- Load d3.js -->
<script src="d3.v6.js"></script>
<!-- Create a div where the graph will take place -->
<div id="my_dataviz"></div>
<script>
// set the dimensions and margins of the graph
const margin = {top: 10, right: 30, bottom: 30, left: 60},
width = 920 - margin.left - margin.right,
height = 800 - margin.top - margin.bottom;
// append the svg object to the body of the page
const 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})`);
let dataExample = [];
for (let i = 0; i < 10000; i++) {
const x = Math.floor(Math.random() * 100) + 100;
const y = Math.floor(Math.random() * 100) + 100;
dataExample.push([x, y]);
}
//Read the data (DataFile.csv is NOT used. Using data from dataExample
d3.csv("DataFile.csv").then( function(data) {
// Add X axis
const x = d3.scaleLinear()
.domain([0, 10000])
.range([ 0, width ]);
svg.append("g")
.attr("transform", `translate(0, ${height})`)
.call(d3.axisBottom(x));
// Add Y axis
const y = d3.scaleLinear()
.domain([0, 10000])
.range([ height, 0]);
svg.append("g")
.call(d3.axisLeft(y));
// Add dots
svg.append('g')
.selectAll("dot")
.data(dataExample)
.join("circle")
.attr("cx", function (d) { return d[0]; } )
.attr("cy", function (d) { return d[1]; } )
//console.log(dataExample)
.attr("r", 1.5)
.style("fill", "#69b3a2")
})
</script>
The axes are 0 to 10000 but the plotted data shows op between y=7200 to 8800 and x=800 and 2500.
You need to use your x and y scales when setting the "cx" and "cy" attributes of the circles. Right now you're setting these attributes to the values in your data, without using the scales. This code should look like this:
svg.append('g')
.selectAll("circle")
.data(dataExample)
.join("circle")
.attr("cx", function (d) { return x(d[0]); } )
.attr("cy", function (d) { return y(d[1]); } )
.attr("r", 1.5)
.style("fill", "#69b3a2");
The purpose of the x and y scales in the scatterplot are to map values in your data to positions in the scatterplot.
With this fix, you'll likely want to update the domains of the scales to better match the values in the data, such as by using d3.extent to get the min and max values.

D3 Line Chart not render like wave

My line chart is heaving a very minor fluctuation but when I am plotting using D3 it is not working properly and coming as single line:
Can some one please help me as it not working properly.
var margin = { top: 50, right: 50, bottom: 50, left: 50 }
, width = window.innerWidth - margin.left - margin.right // Use the window's width
, height = window.innerHeight - margin.top - margin.bottom; // Use the window's height
// The number of datapoints
var n = dataset.length;
// 5. X scale will use the index of our data
var xScale = d3.scaleLinear()
.domain([0, n - 1]) // input
.range([0, width]); // output
// 6. Y scale will use the randomly generate number
var yScale = d3.scaleLinear()
.domain([0, 1]) // input
.range([height, 0]); // output
// 7. d3's line generator
var line = d3.line()
.x(function (d, i) { return xScale(i); }) // set the x values for the line generator
.y(function (d) { return yScale(d.y); }) // set the y values for the line generator
.curve(d3.curveMonotoneX) // apply smoothing to the line
// 8. An array of objects of length N. Each object has key -> value pair, the key being "y" and the value is a random number
// 1. Add the SVG to the page and employ #2
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 + ")");
// 3. Call the x axis in a group tag
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(xScale)); // Create an axis component with d3.axisBottom
// 4. Call the y axis in a group tag
svg.append("g")
.attr("class", "y axis")
.call(d3.axisLeft(yScale)); // Create an axis component with d3.axisLeft
// 9. Append the path, bind the data, and call the line generator
svg.append("path")
.datum(dataset) // 10. Binds data to the line
.attr("class", "line") // Assign a class for styling
.attr("d", line); // 11. Calls the line generator
// 12. Appends a circle for each datapoint
svg.selectAll(".dot")
.data(dataset)
.enter().append("circle") // Uses the enter().append() method
.attr("class", "dot") // Assign a class for styling
.attr("cx", function (d, i) { return xScale(i) })
.attr("cy", function (d) { return yScale(d.y) })
.attr("r", 5)
Fiddle link is here
https://jsfiddle.net/ananddeepsingh/52okcewv/1/

Creating a zoom function in D3

I have a function where that when a button is pressed (Several buttons the represent several animal types), that animal types SVG is updated with its corresponding data. I'm trying to replicate this zoom function but am having issues implementing it with my code. There are several SVGs that are used globally like this (one for each animal type):
let x = d3.scaleLinear()
.domain([0, 1000])
.range([ 0, width ]);
var xAxis = d3.axisBottom(x);
svgReptile.append("g")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
const yAxis = d3.scaleLinear()
.domain([0, 220])
.range([ height, 0])
svgReptile.append("g")
.call(d3.axisLeft(yAxis))
The function below is called when one of the animal buttons is pressed.
function update(animal, whatSVG, xAxis, yAxis, color) {
const points = whatSVG
.selectAll("circle")
.data(data);
points.enter()
.append("circle")
.attr("cx", function(d) {
return xAxis(d.state);
})
.attr("cy", function(d) {
return yAxis(d.percentage);
})
.merge(points)
.attr("r", 3)
.attr("cx", function(d) {
return xAxis(d.decade)
})
.attr("cy", function(d) {
return yAxis(d.count)
})
.style("fill", function (d) { return colour(d.animal) } );
points.exit()
.attr('r', 0)
.remove();
}
Question:
How can I implement a zoom feature that expands the x-axis when zoomed (or anything similar) like the one linked above?
I think you're looking for a 'brush zoom' from the last line of your question.
The following source code if from an example in a d3 graph gallery
The cross hair allows you to select an area to expand. If you follow the link there is a graph above it that is entitled "Zoom with axis" but it doesn't zoom in the way you've described, it just moves the axis, but doesn't enlarge the graph contents with it. Perhaps both will be useful!
Hope this helps
// set the dimensions and margins of the graph
var margin = {top: 10, right: 20, bottom: 20, left: 20},
width = 500 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
// append the svg object to the body of the page
var Svg = d3.select("#brushZoom")
.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 + ")");
//Read the data
d3.csv("https://raw.githubusercontent.com/holtzy/D3-graph-gallery/master/DATA/iris.csv", function(data) {
// Add X axis
var x = d3.scaleLinear()
.domain([4, 8])
.range([ 0, width ]);
var xAxis = Svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// Add Y axis
var y = d3.scaleLinear()
.domain([0, 9])
.range([ height, 0]);
Svg.append("g")
.call(d3.axisLeft(y));
// Add a clipPath: everything out of this area won't be drawn.
var clip = Svg.append("defs").append("svg:clipPath")
.attr("id", "clip")
.append("svg:rect")
.attr("width", width )
.attr("height", height )
.attr("x", 0)
.attr("y", 0);
// Color scale: give me a specie name, I return a color
var color = d3.scaleOrdinal()
.domain(["setosa", "versicolor", "virginica" ])
.range([ "#440154ff", "#21908dff", "#fde725ff"])
// Add brushing
var brush = d3.brushX() // Add the brush feature using the d3.brush function
.extent( [ [0,0], [width,height] ] ) // initialise the brush area: start at 0,0 and finishes at width,height: it means I select the whole graph area
.on("end", updateChart) // Each time the brush selection changes, trigger the 'updateChart' function
// Create the scatter variable: where both the circles and the brush take place
var scatter = Svg.append('g')
.attr("clip-path", "url(#clip)")
// Add circles
scatter
.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("cx", function (d) { return x(d.Sepal_Length); } )
.attr("cy", function (d) { return y(d.Petal_Length); } )
.attr("r", 8)
.style("fill", function (d) { return color(d.Species) } )
.style("opacity", 0.5)
// Add the brushing
scatter
.append("g")
.attr("class", "brush")
.call(brush);
// A function that set idleTimeOut to null
var idleTimeout
function idled() { idleTimeout = null; }
// A function that update the chart for given boundaries
function updateChart() {
extent = d3.event.selection
// If no selection, back to initial coordinate. Otherwise, update X axis domain
if(!extent){
if (!idleTimeout) return idleTimeout = setTimeout(idled, 350); // This allows to wait a little bit
x.domain([ 4,8])
}else{
x.domain([ x.invert(extent[0]), x.invert(extent[1]) ])
scatter.select(".brush").call(brush.move, null) // This remove the grey brush area as soon as the selection has been done
}
// Update axis and circle position
xAxis.transition().duration(1000).call(d3.axisBottom(x))
scatter
.selectAll("circle")
.transition().duration(1000)
.attr("cx", function (d) { return x(d.Sepal_Length); } )
.attr("cy", function (d) { return y(d.Petal_Length); } )
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
<div id="brushZoom"></div>

Dynamically update chart data in D3

I'm working on a real-time visualization of incoming data. I use D3 for the visualization and started based on this example: http://bl.ocks.org/mbostock/3883195
This is the version I'm currently working on:
var margin = {top: 20, right: 20, bottom: 30, left: 50},
width = 500 - margin.left - margin.right,
height = 300 - margin.top - margin.bottom;
var x = d3.time.scale().range([0, width]);
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");
var area = d3.svg.area()
.x(function(d) { return x(d.x); })
.y0(height)
.y1(function(d) { return y(d.y); });
var svg = d3.select("div#chart").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var data = [[1,1],[2,3],[3,2],[4,4]];
var dataCallback = function(d) {
d.x = +d[0];
d.y = +d[1];
};
data.forEach(dataCallback);
x.domain(d3.extent(data, function(d) { return d.x; }));
y.domain([0, d3.max(data, function(d) { return d.y; })]);
svg.append("path")
.datum(data)
.attr("class", "area")
.attr("d", area);
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("Number of Messages");
This produces the following graph:
Now, as it should update itself regularly, I wanted to dynamically update the data in the graph using the following code:
var n = svg.selectAll("path").data([5,20])
n.enter();
n.exit().remove();
However, that does not work. I'm new to D3 and still learning the basics. Ideally, the graph visualization shifts to the left and the new data is shown in the graph. But so far I can't even add new data to it. Could someone help me with that? I also searched for examples similar to this, but did not found anything which really helped me so far.
d3 is pretty good at keeping track of what data's been added and what's being removed - it does this with joins. Take a look at http://bl.ocks.org/mbostock/3808218
IF we use the data binding (instead of "datum")
svg.append("path")
.data([data])
.attr("class", "area")
.attr("d", area);
and then on update do something like:
// update the list of coordinates
data.splice(0,1);
data.push([5,20]);
// re-decorate the last, newly added item
dataCallback(data[data.length - 1]);
// You will also need to update your axes
x.domain(d3.extent(data, function(d) { return d.x; }));
y.domain([0, d3.max(data, function(d) { return d.y; })]);
// update the data association with the path and recompute the area
svg.selectAll("path").data([data])
.attr("d", area);
And you should get your area to update.
You can try it out here
With the way your data is organized using the joined data (instead of datum) may not make much difference. Note that here the (single) data element you are associating with the path is the full list of coordinates so to update the area you need to pass in a new full list of coordinates.
If you really want to minimized the amount you need to redraw you could break up the area and draw each line segment. Then essentially you fall back to the bar chart example but with not-flat bars and no spacing.

Categories

Resources