D3.js violin plot not showing up - javascript

Sorry in advance, I am very new to javascript. I am trying to use this code https://www.d3-graph-gallery.com/graph/violin_basicDens.html with my own data.
<!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>
<script>
// set the dimensions and margins of the graph
var margin = {top: 10, right: 30, bottom: 30, left: 40},
width = 1200 - margin.left - margin.right,
height = 500 - 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 + ")");
// Read the data and compute summary statistics for each specie
d3.csv("violinsummary.csv", function(data) {
// Show the X scale
var x = d3.scaleBand()
.range([0, width])
.domain(["2017-09", "2017-10","2018-02","2018-03"])
.paddingInner(1)
.paddingOuter(.5);
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// Show the Y scale
var y = d3.scaleLinear()
.domain([80, 100])
.range([height, 0]);
svg.append("g").call(d3.axisLeft(y));
// Features of density estimate
var kde = kernelDensityEstimator(kernelEpanechnikov(.2), y.ticks(50));
// Compute the binning for each group of the dataset
var sumstat = d3.nest() // nest function allows to group the calculation per level of a factor
.key(function(d) {
return d.DATE;})
.rollup(function(d) { // For each key..
input = d.map(function(g) {
return g.Power;});
density = kde(input); // And compute the binning on it.
return(density);
})
.entries(data);
console.log(input);
console.log(density);
// What is the biggest value that the density estimate reach?
var maxNum = 0;
for ( i in sumstat ){
allBins = sumstat[i].value;
kdeValues = allBins.map(function(a){return a[1]});
biggest = d3.max(kdeValues);
if (biggest > maxNum) { maxNum = biggest }
}
console.log(allBins);
console.log(kdeValues);
console.log(biggest);
// The maximum width of a violin must be x.bandwidth = the width dedicated to a group
var xNum = d3.scaleLinear()
.range([0, x.bandwidth()])
.domain([-maxNum,maxNum]);
console.log(sumstat);
// Add the shape to this svg!
svg
.selectAll("myViolin")
.data(sumstat)
.enter() // So now we are working group per group
.append("g")
.attr("transform", function(d){ return("translate(" + x(d.key) +" ,0)") } ) // Translation on the right to be at the group position
.append("path")
.datum(function(d){ return(d.value)}) // So now we are working density per density
.style("stroke", "none")
.style("fill","#69b3a2")
.attr("d", d3.area()
.x0(function(d){ return(xNum(-d[1])) } )
.x1(function(d){ return(xNum(d[1])) } )
.y(function(d){ return(y(d[0])) } )
.curve(d3.curveCatmullRom) // This makes the line smoother to give the violin appearance. Try d3.curveStep to see the difference
)
});
// 2 functions needed for kernel density estimate
function kernelDensityEstimator(kernel, X) {
return function(V) {
return X.map(function(x) {
return [x, d3.mean(V, function(v) { return kernel(x - v); })];
});
};
}
function kernelEpanechnikov(k) {
return function(v) {
return Math.abs(v /= k) <= 1 ? 0.75 * (1 - v * v) / k : 0;
};
}
</script>
I believe the part of the code where I am adding the shape to the svg is incorrect. All my console log outputs show the correct data. I also ran console log outputs for the example and my data and the example's data have the same data types throughout.
Data (violinsummary.csv):
Power,DATE
89.29,2017-09
89.9,2017-09
91.69,2017-09
89.23,2017-09
91.54,2017-09
88.49,2017-09
89.15,2017-09
90.85,2017-09
89.59,2017-09
93.38,2017-10
92.41,2017-10
90.65,2017-10
91.07,2017-10
90.13,2017-10
91.73,2017-10
91.09,2017-10
93.21,2017-10
91.62,2017-10
89.58,2017-10
90.59,2017-10
92.57,2017-10
89.99,2017-10
90.59,2017-10
88.12,2017-10
91.3,2017-10
89.59,2018-02
91.9,2018-02
87.83,2018-02
90.36,2018-02
91.38,2018-02
91.56,2018-02
91.89,2018-02
90.95,2018-02
90.15,2018-02
90.24,2018-02
94.04,2018-02
85.4,2018-02
88.47,2018-02
92.3,2018-02
92.46,2018-02
92.26,2018-02
88.78,2018-02
90.13,2018-03
89.95,2018-03
92.98,2018-03
91.94,2018-03
90.29,2018-03
91.2,2018-03
94.22,2018-03
90.71,2018-03
93.03,2018-03
91.89,2018-03

x.paddingInner is too large, so the bandwidth is too narrow for the violins to display. A value of 1 means a bandwidth of zero.
If x.paddingInner is set to a lower value, for example 0.1, then the bandwidth for the x scale will be wider, so the range for xNum scale will be wider and the violins are viewable.

Related

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 area stacked line chart

I'm working on modifying this stacked line chart example: https://bl.ocks.org/d3indepth/e4efd402b4d9fdb2088ccdf3135745c3
I'm adding a time x axis, but I'm struggling with this block of code:
var areaGenerator = d3.area()
.x(function(d, i) {
// return i * 100;
return i * 253.5;
})
.y0(function(d) {
return y(d[0]);
})
.y1(function(d) {
return y(d[1]);
});
The original example has the .x accessor as i * 100 which seems to be a random value. When I add the X axis the stacked line chart does not line up correctly with the date ticks. I can manually force it to line up by returning i * 253.5 but that is not ideal. I don't really understand how this area function is working - any help would be appreciated.
let height = 600;
let width = 800;
const yMax = 4000;
//var hEach = 40;
let margin = {top: 20, right: 15, bottom: 25, left: 25};
width = width - margin.left - margin.right;
height = height - margin.top - margin.bottom;
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 + ")");
let formatDate = d3.timeFormat("%b-%Y")
let parseTime = d3.timeParse("%Y-%m-%d");
let data = [
{
"host_count": 2553,
"container_count": 875,
"hour": "2019-01-31",
"apm_host_count": 0,
"agent_host_count": 2208,
"gcp_host_count": 0,
"aws_host_count": 345
},
{
"host_count": 1553,
"container_count": 675,
"hour": "2019-02-01",
"apm_host_count": 0,
"agent_host_count": 1208,
"gcp_host_count": 0,
"aws_host_count": 445
},
{
"host_count": 716,
"container_count": 6234,
"hour": "2019-02-02",
"apm_host_count": 0,
"agent_host_count": 479,
"gcp_host_count": 0,
"aws_host_count": 237
},
{
"host_count": 516,
"container_count": 4234,
"hour": "2019-02-03",
"apm_host_count": 0,
"agent_host_count": 679,
"gcp_host_count": 0,
"aws_host_count": 137
}
];
// format the data
data.forEach(function(d) {
d.hour = parseTime(d.hour);
});
// set the ranges
var x = d3.scaleTime().range([0, width]);
x.domain(d3.extent(data, function(d) { return d.hour; }));
var xAxis = d3.axisBottom(x).ticks(11).tickFormat(d3.timeFormat("%y-%b-%d")).tickValues(data.map(d=>d.hour));
var y = d3.scaleLinear()
.domain([0, yMax])
.range([height, 0]);
var areaGenerator = d3.area()
.x(function(d, i) {
console.log(d);
return i * 100;
})
.y0(function(d) {
return y(d[0]);
})
.y1(function(d) {
return y(d[1]);
});
var colors = ['#FBB65B', '#513551', '#de3163']
var stack = d3.stack()
.keys(['agent_host_count', 'aws_host_count', 'container_count']);
var stackedSeries = stack(data);
d3.select('g')
.selectAll('path')
.data(stackedSeries)
.enter()
.append('path')
.style('fill', function(d, i) {
return colors[i];
})
.attr('d', areaGenerator)
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
<!doctype html>
<html lang="en">
<head>
<title>Usage</title>
</head>
<body>
<div id="svg"></div>
<script src="https://d3js.org/d3.v5.min.js"></script>
</body>
</html>
When using axes in d3, you actually need to use the axis variable to calculate scaling factors. The functions that do this calculation are returned by the scale*() methods. In your code you have this for the x-axis:
var x = d3.scaleTime().range([0, width]);
As such, the variable x now contains a function that will do interpolation for you. this is what your areaGenerator function should look like:
var areaGenerator = d3.area()
.x(function(d, i) {
return x(d.data.hour);
})
.y0(function(d) {
return y(d[0]);
})
.y1(function(d) {
return y(d[1]);
});
The only thing you need to remember is that when calculating the value you need to use the same variable that the axis is based on. I.e. your x-axis is a time axis so you need to calculate the interpolation using the time variable (d.data.hour).
As to where 100 comes from in the example, you are essentially correct. In that block the value of 100 is more or less arbitrary. It was likely chosen because the chart looks reasonably good at that scale. By choosing 100, each "tick" is spaced 100px apart and since there is no x-axis to be judged against it doesn't actually matter what is used as long as it changes for each data point.

How to load data to D3 chart from JSON when there are only tuples

I have a JSON file that looks like that:
EDIT: I am using dates as keys, not integers as I have previously typed.
"2005-12-01": 120,
"2005-10-01": 32,
Now I would need to use the first column as the X axis and the other column as the Y axis.
In the examples, they refer to the value by the element name, like:
const line = d3.line()
.x(d => x(d.date))
.y(d => y(d.value));
which I cannot do here.
You've got to map the keys to an array of objects with key/value pairs and then use it to set the domains and draw the line.
// The number of datapoints
var json = {
"2005-12-01": 120,
"2005-10-01": 32,
"2005-08-01": 20,
"2005-06-01": 123
};
var data = Object.keys(json).map(function (k) {
return {date: new Date(k), value: +json[k]};
});
Rest of the code would be the same as drawing a simple line chart.
I created a fork of a sample d3 line chart using the JSON from your question as the input data. (had to hardly change any lines except data mapping)
<!DOCTYPE html>
<meta charset="utf-8">
<style type="text/css">
.line {
fill: none;
stroke: #ffab00;
stroke-width: 3;
}
</style>
<body>
</body>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
// 2. Use the margin convention practice
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 json = {
"2005-12-01": 120,
"2005-10-01": 32,
"2005-08-01": 20,
"2005-06-01": 123
};
var data = Object.keys(json).map(function (k) { return {date: new Date(k), value: +json[k]};});
// 5. X scale will use the index of our data
var xScale = d3.scaleTime()
.domain(d3.extent(data, d => d.date)) // input
.range([0, width]); // output
// 6. Y scale will use the randomly generate number
var yScale = d3.scaleLinear()
.domain(d3.extent(data, d => d.value)) // input
.range([height, 0]); // output
// 7. d3's line generator
var line = d3.line()
.x(function(d, i) { return xScale(d.date); }) // set the x values for the line generator
.y(function(d) { return yScale(d.value); }) // set the y values for the line generator
.curve(d3.curveMonotoneX) // apply smoothing to the line
// 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(data) // 10. Binds data to the line
.attr("class", "line") // Assign a class for styling
.attr("d", line); // 11. Calls the line generator
</script>
loop over the keys of your JSON and create new objects
data = Object.keys(data).map(k => { return {x:+k, value: data[k]}; });
Maybe you need to sort the array
data.sort( (a,b) => a.x - b.x );
var data = {"0": 120,
"1": 32,
"2": 234,
"3": 43};
data = Object.keys(data).map(k => { return {x:+k, value: data[k]}; });
console.log(data);

plotting the points x and y, the axes y and x are not shown correctly

I'm trying to generate a line chart loading csv files by placing points on the x and y axis. for some strange reason the axes are not shown correctly. I have negative values for x and y. so it should have a Cartesian plane like the one in this image. in this case the complete Cartesian plane must be shown. In my points there are negative values in x and y.
I do not know why the points are showing wrong.
the update button, load new points updating the values of the graph.
http://plnkr.co/edit/dDx8gRZIfjvemWpgikAp?p=preview
var margin = {top: 30, right: 20, bottom: 30, left: 50},
width = 600 - margin.left - margin.right,
height = 270 - margin.top - margin.bottom;
// Set the ranges
var x = d3.scale.linear().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(5);
// Define the line
var valueline = d3.svg.line()
.x(function(d) { return x(d.datax); })
.y(function(d) { return y(d.datay); });
// 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 + ")");
The negative values in x and y is not displayed because your minimum domain value is 0:
x.domain([0, d3.max(data, function(d) { return d.datax; })]);
y.domain([0, d3.max(data, function(d) { return d.datay; })]);
It should have been(so that it sets the minimum value of dataset as domains minimum)
x.domain(d3.extent(data, function(d) { return d.datax; }));
y.domain(d3.extent(data, function(d) { return d.datay; }));
working code here

Why the line in this D3 chart isn't properly displayed when updated?

I'm a beginner with D3.js and I want to display a dynamic line chart where the line is always growing with random fluctuations.
I don't need an X axis but I'd like to get a dynamic Y axis based on the last point inserted in the line.
var n = 40,
random = function(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; },
data = d3.range(n).map(random);
var margin = {top: 20, right: 20, bottom: 20, left: 40},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.scale.linear()
.domain([1, n - 2])
.range([0, width]);
var y = d3.scale.linear()
.domain([0, 100])
.range([height, 0]);
var line = d3.svg.line()
.interpolate("basis")
.x(function(d, i) { return x(i); })
.y(function(d, i) { return y(d); });
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 + ")");
svg.append("defs").append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height);
svg.append("g")
.attr("class", "y axis")
.call(d3.svg.axis().scale(y).orient("left"));
var path = svg.append("g")
.attr("clip-path", "url(#clip)")
.append("path")
.datum(data)
.attr("class", "line")
.attr("d", line);
var min = 0, max = min + 40;
tick();
//Update the chart
function tick() {
// push a new data point onto the back
var r = random(min, max);
data.push(r);
min += 10;
max += 10;
// update Y Axis
var y = d3.scale.linear().domain([r - 20,r + 20]).range([height, 0]);
var yAxis = d3.svg.axis().scale(y).orient("left");
svg.selectAll(".y.axis").call(yAxis);
// redraw the line, and slide it to the left
path
.attr("d", line)
.attr("transform", null)
.transition()
.duration(500)
.ease("linear")
.attr("transform", "translate(" + x(0) + ",0)")
.each("end", tick);
// pop the old data point off the front
data.shift();
}
JSFiddle : https://jsfiddle.net/ugj8g9wu/
If I didn't increase the min / max and don't update the Y Axis everything is ok.
But with the code above, my line quickly go above the the Y axis, which doesn't make any sens since the randomized value is include in the domain of the Y axis...
Could you tell me what's going on and why my line isn't properly displayed?
The issue is a bit hidden. In tick(), you made a new y to handle the new domain and range, but you only updated yAxis with this y. What about the line which is still referencing the original y? It also needs update! You can either add code to update the line:
// update Y Axis
var y = d3.scale.linear().domain([r - 20,r + 20]).range([height, 0]);
var yAxis = d3.svg.axis().scale(y).orient("left");
svg.selectAll(".y.axis").call(yAxis);
// NEW CODE
line.y(function(d, i) { return y(d); });
Or (better I think), instead of creating a new y every tick, you can modify the existing one, saving all the efforts to assign it to everywhere else using it. Just change this line:
var y = d3.scale.linear().domain([minY, maxY]).range([height, 0]);
into:
y.domain([minY, maxY]);
Then you'll be able to see the newest point coming in the right.
But there's one more problem with the code: you are increasing the value too quickly so that it's hard to see old points on the chart, so I tuned the arguments a bit to make it look better. Ideally, the minY and maxY should be calculated according to the values in data, not guessing magic boundarys. :)
Fiddle: https://jsfiddle.net/gbwycmrd/

Categories

Resources