Axis not ligning up to bars - javascript

I have this d3 code, but I'm having trouble to align the bars to the x-axis.
I want the number, which represents an hour (range 0h-23h) to appear in the middle of the number of hours, which is the y-value.
The variable times gets instantiated with 24 values (indexes 0 to 23 h).
Any response or ideas are welcome.
What it looks like now;
http://i.imgur.com/PLu7Uv2.png?1
var times = buildTimeTable(data);
var margin = {top: 20, right: 10, bottom: 20, left: 10};
var width = 500- margin.left - margin.right, height = 200- margin.top - margin.bottom;
var x = d3.scale.linear().domain([0, 23]).range([0, width]);
var y = d3.scale.linear().domain([0, Math.max.apply(Math, times)]).range([height, 0]);
var xAxis = d3.svg.axis().orient("bottom").scale(x).ticks(24);
var yAxis = d3.svg.axis().orient("left").scale(y);
d3.select("#statistics-hours > svg").remove();
var svg = d3.select("#statistics-hours").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 bar = svg.selectAll(".bar").data(times).enter().append("rect")
.attr("class", "bar")
.attr("width", (width)/times.length - 5)
.attr("height", function(d){return height - y(d);})
.attr("x", function(d, i){return i * (width/times.length);})
.attr("y", function(d){return y(d);})
.attr("fill", "steelblue");
svg.append("g").attr("class", "axis").attr("transform", "translate("+ margin.left +"," + (height) + ")").call(xAxis);
svg.append("g").attr("class", "axis").attr("transform", "translate(" + (margin.left) + ",0)").call(yAxis);

You probably want to consider using an ordinal scale.
var x = d3.scale.ordinal().domain(d3.range[0,24]).rangeRoundBands([0,width],.1)
Now, x is just
.attr('x',function(d){return x(d);})
And the width is just...
.attr('width',function(d){return x.rangeBand();})

Related

Drawing a D3.js multi-line plot with labels from a nested JSON

I have the following data JSON structure.
var data = {
"Accel": {
"10%": {
"x": [0,1,2,3,4,5,6,...],
"y": [0.3769,0.4052,0.4354,0.4675,...]
},
"20%": {
"x": [0,1,2,3,4,5,6,...],
"y": [0.7543,0.3756,0.7556,0.2344...]
},
...
"100%": {
"x": [0,1,2,3,4,5,6,...],
"y": [0.7543,0.3756,0.7556,0.2344...]
}
}
}
I want to draw that 10 curves / polylines in the same axes with D3.js using the alphanumeric intermediate key (XX%) to label each curve on the plot.
My best try so far:
// Margin, scales and axes
var margin = {top: 20, right: 50, bottom: 50, left: 20}, width = 1200, height = 800;
var xScale = d3.scaleLinear().domain([0,60]).range([0, width - margin.left - margin.right]).nice();
var yScale = d3.scaleLinear().domain([0,30]).range([height - margin.top - margin.bottom, 0]).nice();
var xAxis = d3.axisBottom(xScale).ticks(10, "s");
var yAxis = d3.axisRight(yScale).ticks(10, ".3");
// SVG
var svg = d3.select(".graph").append("svg")
.attr("preserveAspectRatio", "xMinYMin meet")
.attr('viewBox','0 0 ' + width + ' ' + height);
// X axis
svg.append("g")
.attr("class", "x-axis")
.attr("transform", "translate(" + margin.left + "," + (height - margin.bottom) + ")")
.call(xAxis);
// Y axis
svg.append("g")
.attr("class", "y-axis")
.attr("transform", "translate(" + (width - margin.right) + "," + margin.top + ")")
.call(yAxis);
// Line definition
var TbhLine = d3.line()
.x(d => xScale(d.x))
.y(d => yScale(d.y));
// Lines plotting
var TbhPaths = svg.append("g")
.selectAll("path")
.data(data["Accel"])
.enter()
.append('path')
.attr("fill", "none")
.attr("stroke-width", 1.5)
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.attr("d", d => TbhLine(d));
When I run it, axes, scales, domains and ranges are all fine, but no line is plotted.
Any ideas on how could this be done? I am using D3 v6.
Thanks in advance.

How do I make sure the zoom doesn't go below zero and avoid the zoomed points touching the y and z axis? d3.zoom v4

I am new to D3.js. I am stuck of the following concepts:
I couldn't find examples where this is done in D3.js V4 and I am not sure how to navigate it.
To limit the zoom from going beyond zero I would like to use the minimum of the zoom as ZERO. I am not sure how to do this in scatter plot.
To avoid the zoomed points touching the y and z axis. I would like the points to fade or disappear when it touches the axis areas.
Here is my code
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = 750 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var xMax = d3.max(graphdata, function(d) { return d["x"]; }),
yMax = d3.max(graphdata, function(d) { return d["y"]; });
var xScale = d3.scaleLinear()
.range([0, width])
.domain([0, xMax]).nice();
var yScale = d3.scaleLinear()
.range([height, 0])
.domain([0, yMax]).nice();
var xAxis = d3.axisBottom()
.scale(xScale);
var yTicks = 5
var yAxis = d3.axisLeft()
.scale(yScale);
var svg = d3.select("#plotspace").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("id", "plot")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
// create a clipping region
svg.append("defs").append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height);
var gX = svg.append('g')
.attr('transform', 'translate(0,' + height + ')')
.attr('class', 'x axis')
.call(xAxis);
var gY= svg.append('g')
.attr('transform', 'translate(0,0)')
.attr('class', 'y axis')
.call(yAxis
);
var bubble = svg.selectAll('.bubble')
.data(graphdata)
.enter().append('path')
.attr('class', 'bubble')
.attr("d", d3.symbol().type(d3.symbolCircle).size(30))
.attr("transform", function(d) { return "translate(" + xScale(d["x"]) + "," + yScale(d["y"]) + ")"; })
.attr('r', 3.5 )
.attr('fill-opacity',0.7)
.style('fill','blue');
bubble.append('title')
.attr('x', 3.5 )
.text(keys[0]);
// Pan and zoom
var zoom = d3.zoom()
.scaleExtent([.5, 20])
.translateExtent([[0, 0], [width, height]])
.extent([[0, 0], [width, height]])
.on("zoom", zoomed);
svg.append("rect")
.attr("width", width)
.attr("height", height)
.style("fill", "none")
.style("pointer-events", "all")
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
.call(zoom);
function zoomed() {
var new_xScale = d3.event.transform.rescaleX(xScale);
var new_yScale = d3.event.transform.rescaleY(yScale);
gX.call(xAxis.scale(new_xScale));
gY.call(yAxis.scale(new_yScale));
bubble.data(graphdata)
.attr("transform", function(d) { return "translate(" + new_xScale(d["x"]) + "," + new_yScale(d["y"]) + ")"; })
}
Your first issue, negative numbers, is a result of allowing a zoom out from the initial zoom state. If the scales already hold all the data (since you dynamically create the scales), you should never have to zoom out from this zoom level. Zooming out from the initial zoom creates a plot area greater than the translate extent, this is causing negative values to appear in the scale. Try:
zoom.scaleExtent([1,4]);
That fixes the negative numbers, but you can still have overflow within those translate extents because you aren't using a clip path correctly.
You currently use one g called svg to plot points and draw axes, but you don't want to apply a clip area to this g, as the axes are outside of where you wish to draw the points. Instead, you could create a new g for the points only, and apply the plot area to that g with g.attr('clip-path','url(#id)');. Below I call that g plotArea and demonstrate these two changes:
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = 750 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var graphdata = d3.range(200).map(function(d) {
return {x: d3.randomLogNormal()(), y: d3.randomLogNormal()()}
})
var xMax = d3.max(graphdata, function(d) { return d["x"]; }),
yMax = d3.max(graphdata, function(d) { return d["y"]; });
var xScale = d3.scaleLinear()
.range([0, width])
.domain([0, xMax]).nice();
var yScale = d3.scaleLinear()
.range([height, 0])
.domain([0, yMax]);
var xAxis = d3.axisBottom()
.scale(xScale);
var yTicks = 5
var yAxis = d3.axisLeft()
.scale(yScale);
var svg = d3.select("#plotspace").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("id", "plot")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// create a clipping region
svg.append("defs").append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height);
var plotArea = svg.append("g") // we don't want to clip the axes.
.attr("clip-path","url(#clip)");
var gX = svg.append('g')
.attr('transform', 'translate(0,' + height + ')')
.attr('class', 'x axis')
.call(xAxis);
var gY= svg.append('g')
.attr('transform', 'translate(0,0)')
.attr('class', 'y axis')
.call(yAxis
);
var bubble = plotArea.selectAll('.bubble') // add to clipped area.
.data(graphdata)
.enter().append('path')
.attr('class', 'bubble')
.attr("d", d3.symbol().type(d3.symbolCircle).size(30))
.attr("transform", function(d) { return "translate(" + xScale(d["x"]) + "," + yScale(d["y"]) + ")"; })
.attr('r', 3.5 )
.attr('fill-opacity',0.7)
.style('fill','blue')
// Pan and zoom
var zoom = d3.zoom()
.scaleExtent([1, 20])
.translateExtent([[0, 0], [width, height]])
.extent([[0, 0], [width, height]])
.on("zoom", zoomed);
svg.append("rect")
.attr("width", width)
.attr("height", height)
.style("fill", "none")
.style("pointer-events", "all")
.call(zoom);
function zoomed() {
var new_xScale = d3.event.transform.rescaleX(xScale);
var new_yScale = d3.event.transform.rescaleY(yScale);
gX.call(xAxis.scale(new_xScale));
gY.call(yAxis.scale(new_yScale));
bubble.data(graphdata)
.attr("transform", function(d) { return "translate(" + new_xScale(d["x"]) + "," + new_yScale(d["y"]) + ")"; })
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div id="plotspace"></div>

Scientific notation in d3.js axis

I am trying to plot some extremely small values with d3.js. Is there a direct way to visualise the tick labels in scientific (exponential) notation?
<!DOCTYPE html>
<meta charset="utf-8">
<style>
</style>
<body>
<!-- load the d3.js library -->
<script src="//d3js.org/d3.v4.min.js"></script>
<script>
// set the dimensions and margins of the graph
var margin = {top: 20, right: 20, bottom: 30, left: 50},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
// parse the date / time
var data = [[0.3, 5e-300],[0.1, 3e-300],[0.7, 4e-300],[0.2, 7e-300],[0.6, 2.5e-300],[0.9, 4.2e-300]]
// set the ranges
var x = d3.scaleLinear().range([0, width]).domain([0, d3.max(data, function(d) { return d[0]; })]);
var y = d3.scaleLinear().range([height, 0]).domain([0, d3.max(data, function(d) { return d[1]; })]);
// append the svg obgect to the body of the page
// appends a 'group' element to 'svg'
// moves the 'group' element to the top left margin
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.selectAll("dot")
.data(data)
.enter().append("circle")
.attr("cx", function (d,i) { return x(d[0]); } )
.attr("cy", function (d) { return y(d[1]); } )
.attr("r", 8);
// Add the X Axis
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// Add the Y Axis
svg.append("g")
.call(d3.axisLeft(y)
.tickFormat(d3.formatPrefix(".1s", 1e-300)));;
</script>
</body>
Here's an example created with in matplotlib. I would like to achieve the same thing with regard to y-axis notation
A solution with d3.format:
svg.append("g")
.call(d3.axisLeft(y)
.tickFormat(d3.format(".1e")));
Here is a demo:
var margin = {top: 20, right: 20, bottom: 30, left: 50},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
// parse the date / time
var data = [[0.3, 5e-300],[0.1, 3e-300],[0.7, 4e-300],[0.2, 7e-300],[0.6, 2.5e-300],[0.9, 4.2e-300]]
// set the ranges
var x = d3.scaleLinear().range([0, width]).domain([0, d3.max(data, function(d) { return d[0]; })]);
var y = d3.scaleLinear().range([height, 0]).domain([0, d3.max(data, function(d) { return d[1]; })]);
// append the svg obgect to the body of the page
// appends a 'group' element to 'svg'
// moves the 'group' element to the top left margin
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.selectAll("dot")
.data(data)
.enter().append("circle")
.attr("cx", function (d,i) { return x(d[0]); } )
.attr("cy", function (d) { return y(d[1]); } )
.attr("r", 8);
// Add the X Axis
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// Add the Y Axis
svg.append("g")
.call(d3.axisLeft(y)
.tickFormat(d3.format(".1e")));
<script src="https://d3js.org/d3.v4.min.js"></script>

d3 v4 X-axis ticks

Having a brutal time trying to illustrate a historic S&P 500 performance chart year-by-eyar. No matter what I try, the X-axis "ticks()" method is ignored and simply uses a value of 1. Which is very hard to see.
var margin = {top: 20, right: 20, bottom: 70, left: 40},
width = $('#sp501').width() - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
// Parse the date / time
var parseDate = d3.timeParse("%Y-%m");
var x = d3.scaleBand().range([0, width], .05);
var y = d3.scaleLinear().range([height, 0]);
var xAxis = d3.axisBottom(x)
.tickFormat(d3.timeFormat("%Y"))
.ticks(10); // I can't figure out why this is ignored!
var yAxis = d3.axisLeft(y)
.ticks(10);
var svg = d3.select("#sp501")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
d3.csv("data/sp500-historical.csv", function(error, data) {
data.forEach(function(d) {
d.date = parseDate(d.date);
d.value = +d.value;
});
x.domain(data.map(function(d) { return d.date; }));
y.domain([-60, 60]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", "-.55em")
.attr("transform", "rotate(-90)" );
The data is in a convention format:
date,value
1928-01,43.81
1929-01,-8.30
1930-01,-25.12
1931-01,-43.84
1932-01,-8.64
1933-01,49.98
1934-01,-1.19
1935-01,46.74
1936-01,31.94
And I've tried converting the dates to just integer years to see if that works, to no avail.
Am I nuts or is something wrong with X-axis ticks using dates?
Thanks!
While not setting the ticks explicitly, this snippet worked to feed the extent to d3 before the axisBottom call, which then cleaned up the display.
svg.append("g")
.attr("class", "e4rAxis")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));

How to draw a path smoothly from start point to end point in D3.js

I have the following code which plots a line path based on sine function:
var data = d3.range(40).map(function(i) {
return {x: i / 39, y: (Math.sin(i / 3) + 2) / 4};
});
var margin = {top: 10, right: 10, bottom: 20, left: 40},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.scale.linear()
.domain([0, 1])
.range([0, width]);
var y = d3.scale.linear()
.domain([0, 1])
.range([height, 0]);
var line = d3.svg.line()
.interpolate('linear')
.x(function(d){ return x(d.x) })
.y(function(d){ return y(d.y) });
var svg = d3.select("body").append("svg")
.datum(data)
.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("path")
.attr("class", "line")
.attr("d", line);
svg.selectAll('.point')
.data(data)
.enter().append("svg:circle")
.attr("cx", function(d, i){ return x(d.x)})
.attr("cy", function(d, i){ return y(d.y)})
.attr('r', 4);
What I want to do is to plot it smoothly from the first node to last node. I also want to have a smooth transition between two consecutive nodes and not just putting the whole line at once. Simply like connecting the dots using a pencil.
Any help would be really appreciated.
You can animate paths quite easily with stroke-dashoffset and and path.getTotalLength()
var totalLength = path.node().getTotalLength();
path
.attr("stroke-dasharray", totalLength + " " + totalLength)
.attr("stroke-dashoffset", totalLength)
.transition()
.duration(2000)
.ease("linear")
.attr("stroke-dashoffset", 0);
http://bl.ocks.org/4063326

Categories

Resources