Align gridline with axis y in Bar Chart D3 v5 - d3.js

I have a problem with a bar chart. I insert the gridline, but the effect I get is not as desired as can be seen from the image.
image
The gridline is not align with y axis.
The code:
var svg = d3.select("#chart");
var margin = {top: 10, bottom: 10, left: 40, right: 0};
var width = +svg.attr("width") - margin.left - margin.right;
var height = +svg.attr("height") - margin.top - margin.bottom;
var x = d3.scaleBand()
.range([margin.left, width - margin.right])
.padding(0.1)
//.paddingOuter(0.2);
var y = d3.scaleLinear()
.rangeRound([height - margin.bottom, margin.top]);
var xAxis = function(g) {
g
.attr("transform", "translate(0," + (height - margin.bottom) + ")")
.call(d3.axisBottom(x).tickSizeOuter(0));
return g;
};
var yAxis = function(g) {
g
.attr("transform", "translate(" + margin.left + ", " + (margin.bottom - margin.top) + ")")
.call(d3.axisLeft(y));
return g;
};
function gridlines() {
return d3.axisLeft(y).ticks();
}
svg.append("g")
.attr("class", "grid")
.attr("transform", "translate(" + margin.left + "," + (margin.bottom - margin.top) + ")")
.call(gridlines()
.tickSize(-width)
.tickFormat("")
);
svg.append("g").attr("class", "x-axis");
svg.append("g").attr("class", "y-axis");
Thank.

Related

data bars on the xaxis should be aligned properly

I am building my first bar chart using d3.js v5 what I want is that the bars should be aligned properly on the xaxis
I have almost done building the chart but can't figure out the problem
var headingOne;
var headingTwo;
var dataset;
var description;
var frequency;
var barPadding = 20;
document.addEventListener("DOMContentLoaded", function() {
var req = new XMLHttpRequest(); // Next time will use d3.json();
req.open(
"GET",
"https://raw.githubusercontent.com/freeCodeCamp/ProjectReferenceData/master/GDP-data.json",
true
);
req.send();
req.onload = function() {
let json = JSON.parse(req.responseText);
headingOne = json.source_name;
headingTwo = `From ${json.from_date.substring(0,4)} to ${json.to_date.substring(0,4)}`;
dataset = json.data;
descripton = json.description;
d3
.select("body")
.append("h1")
.text(headingOne)
.attr("class", "headings")
.attr("id", "title");
d3
.select("body")
.append("h2")
.text(headingTwo)
.attr("class", "headings");
var margin = { top: 20, right: 20, bottom: 50, left: 40 },
height = 600 - margin.top - margin.bottom,
width = 1100 - margin.left - margin.right;
var minDate = new Date(dataset[0][0]);
var maxDate = new Date(dataset[dataset.length - 1][0]);
var xScale = d3.scaleTime().domain([minDate, maxDate]).range([barPadding, width - barPadding]);
var yScale = d3.scaleLinear().domain([0, d3.max(dataset, d => d[1])]).range([height, barPadding]);
var xAxis = d3.axisBottom().scale(xScale);
var yAxis = d3.axisLeft().scale(yScale);
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("rect")
.data(dataset)
.enter()
.append("rect")
.attr("class", "bar")
.attr("x", (d, i) => i * (width / dataset.length))
.attr("data-date", (d) => d[0])
.attr("y", (d) => yScale(d[1]))
.attr("data-gdp", (d) => d[1])
.attr("width", width / dataset.length)
.attr("height", d => height - yScale(d[1]))
svg.append("g").attr("transform", "translate("+barPadding+"," + (height) + ")").attr("id", "x-axis").call(xAxis);
svg.append("g").attr("transform", "translate("+margin.left+", 0)").attr("id", "y-axis").call(yAxis);
};
});
I expect the bars properly aligned on the xaxis.
Now the bars started before the xaxis (start of the xaxis towards left) starting point which is wrong but finished in the right position (end of the xaxis towards right)
the data is exceeding the limit.

Center Pie Chart in SVG Element

I've already read:
https://bl.ocks.org/mbostock/3887235
http://zeroviscosity.com/d3-js-step-by-step/step-1-a-basic-pie-chart
Center align a pie chart on svg
Consider the following:
var dataAsCsv = `Col1,Col2
Type1,123456
Type2,789012
Type3,34567`;
var data = d3.csvParse(dataAsCsv);
var margin = {top: 50, right: 20, bottom: 50, left: 80},
width = 1400 - margin.left - margin.right,
height = 700 - margin.top - margin.bottom;
var svgPie = d3.select('body').append('svg')
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom);
var gPie = svgPie.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var radius = Math.min(width, height) / 2;
var color = d3.scaleOrdinal(d3.schemeCategory20b);
var label = d3.arc()
.outerRadius(radius - 40)
.innerRadius(radius - 40);
var path = d3.arc()
.outerRadius(radius - 10)
.innerRadius(0);
var pie = d3.pie()
.value(function(d) { return d.Col2; })
.sort(null);
var arc = gPie.selectAll(".arc")
.data(pie(data))
.enter().append("g")
.attr("class", "arc");
arc.append("path")
.attr("d", path)
.attr("fill", function(d) { return color(d.data.Col1); });
arc.append("text")
.attr("transform", function(d) { return "translate(" + label.centroid(d) + ")"; })
.attr("dy", "0.35em")
.text(function(d) { return d.data.Col1; });
<script src="https://d3js.org/d3.v4.js"></script>
I am trying to center the pie chart vertically and horizontally with respect to the entire svg element that it is in. I tried modifying my code to the examples above to no avail.
You just have to translate the parent g element at half width horizontally and at half height vertically:
Instead of:
var gPie = svgPie.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
write:
var gPie = svgPie.append("g")
.attr("transform", "translate(" + width/2 + "," + height/2 + ")");
Check the demo:
var dataAsCsv = `Col1,Col2
Type1,123456
Type2,789012
Type3,34567`;
var data = d3.csvParse(dataAsCsv);
var margin = {top: 50, right: 20, bottom: 50, left: 80},
width = 1400 - margin.left - margin.right,
height = 700 - margin.top - margin.bottom;
var svgPie = d3.select('body').append('svg')
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom);
var gPie = svgPie.append("g")
.attr("transform", "translate(" + width/2 + "," + height/2 + ")");
var radius = Math.min(width, height) / 2;
var color = d3.scaleOrdinal(d3.schemeCategory20b);
var label = d3.arc()
.outerRadius(radius - 40)
.innerRadius(radius - 40);
var path = d3.arc()
.outerRadius(radius - 10)
.innerRadius(0);
var pie = d3.pie()
.value(function(d) { return d.Col2; })
.sort(null);
var arc = gPie.selectAll(".arc")
.data(pie(data))
.enter().append("g")
.attr("class", "arc");
arc.append("path")
.attr("d", path)
.attr("fill", function(d) { return color(d.data.Col1); });
arc.append("text")
.attr("transform", function(d) { return "translate(" + label.centroid(d) + ")"; })
.attr("dy", "0.35em")
.text(function(d) { return d.data.Col1; });
<script src="https://d3js.org/d3.v4.js"></script>

How to show the end scale value in the axis?

Using d3.js v4, how to show the special scale value which can't be divide exactly?
For example: set the domain([0,24]), set the ticks(3). The value 24 don't show in the axis.
Please tell me how to show the value.
var margin = {top: 10, right: 30, bottom: 30, left: 30},
width = 600 - margin.left - margin.right,
height = 450 - margin.top - margin.bottom;
var x = d3.scaleBand()
.rangeRound([0, width])
.domain(['A', 'B', 'C'])
.padding(0);
var y = d3.scaleLinear()
.domain([0, 24])
.range([height, 0]);
var xAxis = d3.axisBottom(x),
yAxis = d3.axisLeft(y).ticks(3);
var svg = d3.select('#chart')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.style('background-color', '#ecf0f1')
.append('g')
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var g = svg.append('g');
g.append('g')
.attr('class', 'axis axis--x')
.attr('transform', 'translate(0,' + height + ')')
.call(xAxis);
g.append('g')
.attr('class', 'axis axis--y')
.call(yAxis);
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg id="chart"></svg>
Use tickValues, defining how many ticks you want with scale.ticks() and pushing the first and last domain values by concatenation with scale.domain():
yAxis = d3.axisLeft(y)
.tickValues(y.ticks(3).concat(y.domain()));
Check the updated snippet:
var margin = {top: 10, right: 30, bottom: 30, left: 30},
width = 600 - margin.left - margin.right,
height = 450 - margin.top - margin.bottom;
var x = d3.scaleBand()
.rangeRound([0, width])
.domain(['A', 'B', 'C'])
.padding(0);
var y = d3.scaleLinear()
.domain([0, 24])
.range([height, 0]);
var xAxis = d3.axisBottom(x),
yAxis = d3.axisLeft(y)
.tickValues(y.ticks(3).concat(y.domain()));
var svg = d3.select('#chart')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.style('background-color', '#ecf0f1')
.append('g')
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var g = svg.append('g');
g.append('g')
.attr('class', 'axis axis--x')
.attr('transform', 'translate(0,' + height + ')')
.call(xAxis);
g.append('g')
.attr('class', 'axis axis--y')
.call(yAxis);
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg id="chart"></svg>

How to link D3 zoom and pan controls

I would like to link the zoom and pan controls of multiple charts together so they all pan and zoom together when one of the charts pan and zoom controls are engaged.
I tried creating a single zoom object and passing it to the charts, but only the last chart actually pan and zoomed. The others remained static even though I was zooming in their areas.
Here's a snapshot of the charts. Each chart has an overview chart with a viewport that can be moved as well. I would like to link all the controls together so the viewports are also the same on each chart.
So, how can I link the pan and zoom controls on multiple charts?
Here is a jsfiddle for this code: https://jsfiddle.net/babazaroni/a52oukzn/
Here is my code:
The overall chart creator. This code makes 3 charts.
define([
'd3',
'components/sl',
'MockData',
'components/candlestickSeries',
'Chart'
], function (d3, sl, MockData,candlestickSeries,Chart) {
'use strict';
function generateData()
{
var data = new MockData(0.1, 0.1, 100, 50, function (moment) {
return !(moment.day() === 0 || moment.day() === 6);
})
.generateOHLC(new Date(2014, 1, 1), new Date(2014, 8, 1));
return data;
}
var data = generateData();
d3.select('#chart1')
.datum(data)
.call(Chart());
data = generateData();
d3.select('#chart1')
.datum(data)
.call(Chart());
data = generateData();
d3.select('#chart1')
.datum(data)
.call(Chart());
});
Here is the chart code:
define([
'd3',
'components/sl',
'MockData',
'components/candlestickSeries'
], function (d3, sl, MockData) {
'use strict';
function timeSeriesChart() {
function chart(selection)
{
selection.each(function(data) {
var minDate = new Date(d3.min(data, function (d) { return d.date; }).getTime() - 8.64e7);
var maxDate = new Date(d3.max(data, function (d) { return d.date; }).getTime() + 8.64e7);
var yMin = d3.min(data, function (d) { return d.low; });
var yMax = d3.max(data, function (d) { return d.high; });
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// The primary chart
// Set up the drawing area
var margin = {top: 20, right: 20, bottom: 30, left: 35},
width = 600 - margin.left - margin.right,
height = 200 - margin.top - margin.bottom;
var plotChart = d3.select(this).classed('chart', true).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 plotArea = plotChart.append('g')
.attr('clip-path', 'url(#plotAreaClip)');
plotArea.append('clipPath')
.attr('id', 'plotAreaClip')
.append('rect')
.attr({ width: width, height: height });
// Scales
var xScale = d3.time.scale(),
yScale = d3.scale.linear();
// Set scale domains
xScale.domain([minDate, maxDate]);
yScale.domain([yMin, yMax]).nice();
// Set scale ranges
xScale.range([0, width]);
yScale.range([height, 0]);
// Axes
var xAxis = d3.svg.axis()
.scale(xScale)
.orient('bottom')
.ticks(10);
var yAxis = d3.svg.axis()
.scale(yScale)
.orient('left');
plotChart.append('g')
.attr('class', 'x axis')
.attr('transform', 'translate(0,' + height + ')')
.call(xAxis);
plotChart.append('g')
.attr('class', 'y axis')
.call(yAxis);
plotChart.append("text")
.attr("x", (width / 2))
.attr("y", 1 - (margin.top / 2))
.attr("text-anchor", "middle")
.style("font-size", "16px")
.style("text-decoration", "underline")
.text("Your title goes here");
// Data series
var series = sl.series.candlestick()
.xScale(xScale)
.yScale(yScale);
var dataSeries = plotArea.append('g')
.attr('class', 'series')
.datum(data)
.call(series);
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Navigation chart
var navWidth = width,
navHeight = 100 - margin.top - margin.bottom;
// Set up the drawing area
var navChart = d3.select(this).classed('chart', true).append('svg')
.classed('navigator', true)
.attr('width', navWidth + margin.left + margin.right)
.attr('height', navHeight + margin.top + margin.bottom)
.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
// Scales
var navXScale = d3.time.scale()
.domain([
new Date(minDate.getTime() - 8.64e7),
new Date(maxDate.getTime() + 8.64e7)
])
.range([0, navWidth]),
navYScale = d3.scale.linear()
.domain([yMin, yMax])
.range([navHeight, 0]);
// Axes
var navXAxis = d3.svg.axis()
.scale(navXScale)
.orient('bottom');
navChart.append('g')
.attr('class', 'x axis')
.attr('transform', 'translate(0,' + navHeight + ')')
.call(navXAxis);
// Data series
var navData = d3.svg.area()
.x(function (d) { return navXScale(d.date); })
.y0(navHeight)
.y1(function (d) { return navYScale(d.close); });
var navLine = d3.svg.line()
.x(function (d) { return navXScale(d.date); })
.y(function (d) { return navYScale(d.close); });
navChart.append('path')
.attr('class', 'data')
.attr('d', navData(data));
navChart.append('path')
.attr('class', 'line')
.attr('d', navLine(data));
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Viewport
function redrawChart() {
dataSeries.call(series);
plotChart.select('.x.axis').call(xAxis);
}
function updateZoomFromChart() {
var fullDomain = maxDate - minDate,
currentDomain = xScale.domain()[1] - xScale.domain()[0];
var minScale = currentDomain / fullDomain,
maxScale = minScale * 20;
zoom.x(xScale)
.scaleExtent([minScale, maxScale]);
}
var viewport = d3.svg.brush()
.x(navXScale)
.on("brush", function () {
xScale.domain(viewport.empty() ? navXScale.domain() : viewport.extent());
redrawChart();
})
.on("brushend", function () {
updateZoomFromChart();
});
navChart.append("g")
.attr("class", "viewport")
.call(viewport)
.selectAll("rect")
.attr("height", navHeight);
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Zooming and panning
function updateViewpointFromChart() {
if ((xScale.domain()[0] <= minDate) && (xScale.domain()[1] >= maxDate)) {
viewport.clear();
}
else {
viewport.extent(xScale.domain());
}
navChart.select('.viewport').call(viewport);
}
var zoom = d3.behavior.zoom()
.x(xScale)
.on('zoom', function() {
if (xScale.domain()[0] < minDate) {
zoom.translate([zoom.translate()[0] - xScale(minDate) + xScale.range()[0], 0]);
} else if (xScale.domain()[1] > maxDate) {
zoom.translate([zoom.translate()[0] - xScale(maxDate) + xScale.range()[1], 0]);
}
redrawChart();
updateViewpointFromChart();
});
var overlay = d3.svg.area()
.x(function (d) { return xScale(d.date); })
.y0(0)
.y1(height);
plotArea.append('path')
.attr('class', 'overlay')
.attr('d', overlay(data))
.call(zoom);
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Setup
var daysShown = 30;
xScale.domain([
data[data.length - daysShown - 1].date,
data[data.length - 1].date
]);
redrawChart();
updateViewpointFromChart();
updateZoomFromChart();
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Helper methods
});
}
// alert("here we are again and again");
return chart;
}
return timeSeriesChart;
});
From this example : http://bl.ocks.org/mbostock/6123708
Create the zoom:
var zoom = d3.behavior.zoom()
.scaleExtent([1, 10])
.on("zoom", zoomed);
function zoomed() {
container.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
}
Call zoom on your container :
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.right + ")")
.call(zoom); //<<<<<HERE
That is what it will look like on a single canvas, but in your case it will look something like this :
Create the zoom:
var zoom = d3.behavior.zoom()
.scaleExtent([1, 10])
.on("zoom", zoomed);
transform each of your canvas's/containers
function zoomed() {
container1.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
container2.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
container3.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
}
Call zoom on all of your containers :
var svg1 = 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.right + ")")
.call(zoom); //<<<<<HERE
var svg2 = 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.right + ")")
.call(zoom); //<<<<<HERE
var svg3 = 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.right + ")")
.call(zoom); //<<<<<HERE
Something along those lines should work but can't test it without any example.

trouble with scales and hebin in d3js

I am trying to use the hexbin layout with data that is normally distributed around 0 - all the examples use data centered around the center of the screen, so the scales are the same as the screen scales (except for y inversion)
I've tried to modify the scale functions to account for possible negative values. It works for the y-scale, but the x-scale gives NaNs, and the hexagons are plotted off the screen upper left. That is not the only problem - I would like to programmatically determine the bin size for the hexbin function - in my data series, all of the values are 'binned' into only one to three hexagons, and I need them spread out over the available domain.. here is my code
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://d3js.org/d3.hexbin.v0.min.js?5c6e4f0"></script>
<script>
minMultArray =function(arr,index){
var min = arr[0][index];
for (i=0;i<arr.length;i++) {
min = (min>arr[i][index]?arr[i][index]:min);
}
return min;
};
maxMultArray =function(arr,index){
var max = arr[0][index];
for (i=0;i<arr.length;i++) {
max = (max< arr[i][index]?arr[i][index]:max);
}
return max;
};
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var randomX = d3.random.normal(0, 5),
randomY = d3.random.normal(0, 6),
points = d3.range(2000).map(function() { return [randomX(), randomY()]; });
var minX = minMultArray(points,0);
var minY = minMultArray(points,1);
//var minZ = minMultArray(points,2);
var maxX = maxMultArray(points,0);
var maxY = maxMultArray(points,1);
//var maxZ = maxMultArray(points,2);
var color = d3.scale.linear()
.domain([0, 20])
.range(["white", "steelblue"])
.interpolate(d3.interpolateLab);
var hexbin = d3.hexbin()
.size([width, height])
.radius(20);
alert('minX='+minX +' maxX='+maxX);
var x = d3.scale.linear()
.domain([minX, maxX])
.range(0,width);
alert('xScale(3)='+x(3));
var y = d3.scale.linear()
.domain([minY, maxY])
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.tickSize(6, -height);
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.tickSize(6, -width);
console.log('hex = ' +hexbin(points));
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("clipPath")
.attr("id", "clip")
.append("rect")
.attr("class", "mesh")
.attr("width", width)
.attr("height", height);
svg.append("g")
.attr("clip-path", "url(#clip)")
.selectAll(".hexagon")
.data(hexbin(points))
.enter().append("path")
.attr("class", "hexagon")
.attr("d", hexbin.hexagon())
.attr("transform", function(d) { return "translate(" + (d.x) + "," + (d.y) + ")"; })
.style("fill", function(d) { return color(d.length); });
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
</script>
After more debugging the hexbin functions, they are not compatible with negative and/or fractional domains- so I solved this by mapping my original data by linear scales up to the height and width of the hexagon plots. Then bin size is controlled by radius. I also modified the hexbin binning function to handle three element arrays, and can compute stats on the third element, using color or size to show mean/median/stddev/max/min. If interested, I can post the code on github...

Resources