I am using D3 v4. I have a bar graph created with an x-axis using scaleBand(). Now, I have created a y-axis but my issue is that no matter how I position it, it is cutting into the actual bars of the graph.
At the top of my JS file, I have:
var width = 350;
var height = 300;
Then, the part where I actually create the Y-axis:
var y = d3.scaleLinear()
.domain([0, 300000])
.rangeRound([height, 0]);
var yAxis = d3.axisRight(y);
yAxis.ticks(6);
chart.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + (width - dist_from_right) + ", 0)")
.call(yAxis);
As you can see from the picture, the axis stretches the entirety of the height of the SVG, from bottom to top meaning that half of the 0 gets cut off and half off the 300,000 gets cut off.
First question: how do I "squish" (or scale) the y-axis so that it displays within the confines of the SVG?
Next, I want to translate the y-axis so that it is not cutting into my red bar. If I try to use the transform attribute, I can push the axis far to the right of the SVG borders but that means the numbers are off the SVG boundary. I've also tried to increase the width variable but that does nothing because it just stretches out the x-axis proportionally.
Second question: how do I move the y-axis so that it is not cutting into the x-axis and red bar and also remains visible in the SVG window?
Thanks!
In D3, axes are positioned according to the range of corresponding scales. So, you need a "padding" for the ranges. Right now, as your range goes from 0 to height (or vice versa, it doesn't matter), the axis starts at the very beginning of the SVG and ends at its very end.
I see you have a dist_from_right, but I don't know what are you doing with it in your x scale. So, for now, let's suppose you don't have any padding.
First, let's set the paddings:
var paddingLeft = 10, paddingRight = 10, paddingTop = 10, paddingBottom = 10;
Here, 10 is just a given number, change it accordingly.
After that, set the ranges using the paddings:
var y = d3.scaleLinear()
.domain([0, 300000])
.rangeRound([height - paddingBottom, paddingTop]);
The same for your x scale:
var x = d3.scaleLinear()
.domain([0, someValue])
.rangeRound([paddingLeft, width - paddingRight]);
Then you define the axis:
var xAxis = d3.axisBottom(x);//the same for the y axis
Having the ranges with the paddings, call the axes setting that paddings:
chart.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + (width - paddingRight) + ", 0)")
.call(yAxis);
And the same for the x axis:
chart.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + (height - paddingBottom) + ")")
.call(xAxis);
Here is a working example. I made the SVG light gray and the plotting area white, so you can see the paddings.:
var paddingLeft = 20, paddingRight = 40, paddingTop = 10, paddingBottom = 40;
var width = 300, height = 300;
var chart = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height)
chart.append("rect")
.attr("x", paddingLeft)
.attr("y", paddingTop)
.attr("width", width - paddingLeft - paddingRight)
.attr("height", height - paddingTop - paddingBottom)
.attr("fill", "white");
var y = d3.scaleLinear()
.domain([0, 100])
.rangeRound([height - paddingBottom, paddingTop]);
var x = d3.scaleLinear()
.domain([0, 100])
.rangeRound([paddingLeft, width - paddingRight]);
var yAxis = d3.axisRight(y);
var xAxis = d3.axisBottom(x);
chart.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + (width - paddingRight) + ", 0)")
.call(yAxis);
chart.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + (height - paddingBottom) + ")")
.call(xAxis);
svg {
background-color: lightgray;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
Related
I have a quick question. I am trying to determine the height in pixels of a chart bar. This is for a D3 implementation, and my chart has a logarithmic y-axis.
I know the Y value for the bar I am trying to plot.
I also know the height of the axis in pixels (600px).
I know the min and the max of the Y-axis
I have tried various computations but cannot seem to calculate the height of the bar so that it connects the Y value with the x-Axis.
The picture below should provide a visual illustration of what I am trying to do. Right now I can't seem to get it right ... I think this is essentially a problem in maths, not so much D3. Thank you!
*** EDIT ****
This is the y axis scale that I am using:
var y = d3.scale.log()
.range([height, 0])
.domain([d3.min(sampleData, function(d) {
return d.y;
}),
d3.max(sampleData, function(d) {
return d.y;
})
]);
I'm still not sure about your problem, because the actual height of the bar is being calculated by the very scale you use to append the rectangles. And, if you're in fact appending the rectangles, you're already setting the height attribute!
Let's see an example. This is a bar chart using your log scale (I'm using D3 v4 here, but the principle is the same) and this fake data:
var data = [2000, 5000, 3000, 8000, 1500];
As you can see, there is a minimum and a maximum. I put a padding of 20px in the scale:
var yScale = d3.scaleLog()
.range([height - padding, padding])
.domain([d3.min(data), d3.max(data)]);
So, our first value in the range is h - padding and our last value is just padding. Here is the chart:
var width = 300,
height = 400,
padding = 20;
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
var data = [2000, 5000, 3000, 8000, 1500];
var xScale = d3.scaleBand()
.range([50, width])
.domain(d3.range(data.length))
.padding(0.2);
var yScale = d3.scaleLog()
.range([height - padding, padding])
.domain([d3.min(data), d3.max(data)]);
var bars = svg.selectAll("foo")
.data(data)
.enter()
.append("rect")
.attr("x", (d, i) => xScale(i))
.attr("width", xScale.bandwidth())
.attr("height", d => height - padding - yScale(d))
.attr("y", d => yScale(d))
.attr("fill", "teal");
var gX = svg.append("g")
.attr("transform", "translate(0," + (height - padding) + ")")
.call(d3.axisBottom(xScale));
var gY = svg.append("g")
.attr("transform", "translate(50,0)")
.call(d3.axisLeft(yScale));
<script src="//d3js.org/d3.v4.min.js"></script>
Suppose you want to calculate the height of the first bar. We can see, by inspecting the DOM, that its height is 61.867984771728516 pixels:
var width = 300,
height = 400,
padding = 20;
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
var data = [2000, 5000, 3000, 8000, 1500];
var xScale = d3.scaleBand()
.range([50, width])
.domain(d3.range(data.length))
.padding(0.2);
var yScale = d3.scaleLog()
.range([height - padding, padding])
.domain([d3.min(data), d3.max(data)]);
var bars = svg.selectAll("foo")
.data(data)
.enter()
.append("rect")
.attr("x", (d, i) => xScale(i))
.attr("width", xScale.bandwidth())
.attr("height", d => height - padding - yScale(d))
.attr("y", d => yScale(d))
.attr("fill", "teal");
var gX = svg.append("g")
.attr("transform", "translate(0," + (height - padding) + ")")
.call(d3.axisBottom(xScale));
var gY = svg.append("g")
.attr("transform", "translate(50,0)")
.call(d3.axisLeft(yScale));
console.log(d3.select("rect").node().height.animVal.value)
<script src="//d3js.org/d3.v4.min.js"></script>
But this is simply the first value in the range (height - padding) minus yScale(2000):
var width = 300,
height = 400,
padding = 20;
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
var data = [2000, 5000, 3000, 8000, 1500];
var xScale = d3.scaleBand()
.range([50, width])
.domain(d3.range(data.length))
.padding(0.2);
var yScale = d3.scaleLog()
.range([height - padding, padding])
.domain([d3.min(data), d3.max(data)]);
var bars = svg.selectAll("foo")
.data(data)
.enter()
.append("rect")
.attr("x", (d, i) => xScale(i))
.attr("width", xScale.bandwidth())
.attr("height", d => height - padding - yScale(d))
.attr("y", d => yScale(d))
.attr("fill", "teal");
var gX = svg.append("g")
.attr("transform", "translate(0," + (height - padding) + ")")
.call(d3.axisBottom(xScale));
var gY = svg.append("g")
.attr("transform", "translate(50,0)")
.call(d3.axisLeft(yScale));
console.log(height - padding - yScale(2000))
<script src="//d3js.org/d3.v4.min.js"></script>
Which, by the way, is the value used to set the height of the bars:
.attr("height", d => height - padding - yScale(d))
I have currently have a quick test for a graph I'm about to create for website and I have made the most basic functionality. I have a graph, a 4 elements and an x and a y axis and a zoom functionality.
My problem lies in the fact that when I zoom on the graph, the elements are able to reach the axis and overlap it. I've pasted my source code below
//Setting generic width and height values for our SVG.
var margin = {top: 60, right: 0, bottom: 60, left: 40},
width = 1024 - 70 - margin.left - margin.right,
height = 668 - margin.top - margin.bottom;
//Other variable declarations.
//Creating scales used to scale everything to the size of the SVG.
var xScale = d3.scale.linear()
.domain([0, 1024])
.range([0, width]);
var yScale = d3.scale.linear()
.domain([1, 768])
.range([height, 0]);
//Creates an xAxis variable that can be used in our SVG.
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(yScale)
.orient("left");
//Zoom command ...
var zoom = d3.behavior.zoom()
.x(xScale)
.y(yScale)
.scaleExtent([1, 10])
.on("zoom", zoomTargets);
// The mark '#' indicates an ID. IF '#' isn't included argument expected is a tag such as "svg" or "p" etc..
var SVG = d3.select("#mainSVG")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.call(zoom);
//Create background. The mouse must be over an object on the graph for the zoom to work. The rectangle will cover the entire graph.
var rect = SVG.append("rect")
.attr("width", width)
.attr("height", height);
//Showing the axis that we created earlier in the script for both X and Y.
var xAxisGroup = SVG.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
var yAxisGroup = SVG.append("g")
.attr("class", "y axis")
.call(yAxis);
//This selects 4 circles (non-existent, there requires data-binding) and appends them all below enter.
//The amount of numbers in data is the amount of circles to be appended in the enter() section.
var circle = SVG
.selectAll("circle")
.data([40,100,400,1900])
.enter()
.append("circle")
.attr("cx",function(d){return xScale(d)})
.attr("cy",function(d){return yScale(d)})
.attr("r",20);
//Resets zoom when click on circle object. Zoom work now, should be changed to a button instead of click on circle though.
SVG.selectAll("circle").on("click", function() {
zoom.scale(1);
zoom.translate([0,0]);
zoomTargets();
});
function zoomTargets() {
SVG.select(".x.axis").call(xAxis);
SVG.select(".y.axis").call(yAxis);
SVG.selectAll("circle").attr("cx",function(d){return xScale(d)}).attr("cy",function(d){return yScale(d)});
}
function resetZoom() {
zoom.scale(1);
zoom.translate([0,0]);
zoomTargets();
}
I've tried using "append("g2") before creating a circle to I can make g2 smaller than the entire svg, but that doesn't seem to work. As far as I have understood, you can just append a new element inside your existing one. I'm guessing I'm wrong since it hasn't worked for me.
Thank you in advance for your help.
Leave a small gap between the most extreme data point and the axis. In particular, you may want the range of your domain to take the margins into account:
var xScale = d3.scale.linear()
.domain([0, 1024])
.range([0, width-margin.right]);
var yScale = d3.scale.linear()
.domain([1, 768])
.range([height, margin.bottom]);
I am trying to develop a scatterplot using d3 but the domain for y-axis is confusing me. y-axis are gonna display patient names and x-axis display their appointment dates. x-axis are working fine, but y-axis are displaying only two patient names.
function graph() {
var num_patient = Object.keys(patientList).length;
var patient_names = Object.keys(patientList);
console.log(patient_names);
var x = d3.time.scale().range([0, width]);
var y = d3.scale.ordinal().range([height, 0]);
x.domain(d3.extent(data, function(d) {return parseDate(d.dates); }));
//y.domain(patient_names.map(function(d) { return d.name;}));
y.domain(patient_names);
console.log(y.domain());
var xAxis = d3.svg.axis()
.scale(x)
.ticks(d3.time.year, 1)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var svg = d3.select("#punchcard")
.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("r", 3.5)
.attr("cx", function(d) { return x(d.date); })
.attr("cy", function(d) { return y(d.value); });
svg.append("g") // Add the X Axis
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g") // Add the Y Axis
.attr("class", "y axis")
.call(yAxis);
}
console.log(patient_names) display the names correctly:
`["Andrew","Fred","Steve","John"]`
console.log(y.domain()) displays an extra undefined object:
["Andrew", "Fred","Steve" , "John", undefined]
But the y-axis only display Andrew at 0 and Fred at height h. How can I get to display all four names? I cannot hard code them as they are user input values. BTW: I am a beginner with d3 and js.
Thanks in advance!
With ordinal scales, you need to define the range points for the inputs explicitly (see the documentation). That is, you need to tell the scale explicitly which input value to map to which output. For example:
var y = d3.scale.ordinal()
.domain(["Andrew","Fred","Steve","John"])
.range([height, height * 2/3, height * 1/3, 0]);
You probably want to use the .rangePoints() method instead, which allows you to specify an interval that D3 automatically divides based on the number of values in the domain:
var y = d3.scale.ordinal()
.domain(["Andrew","Fred","Steve","John"])
.rangePoints([height, 0]);
Note that for .rangePoints() to work properly, you need to set the domain before the output range.
Working with one bar chart with d3.js I am unable to align ticks in x axis with bars.
In left and right verges the ticks are ok, but not in the middle.
Here is the code:
var formatDate = d3.time.format("%e %b");
var height = 325;
var xTimeScale = d3.time.scale()
.domain([new Date(data[0].date), d3.time.day.offset(new Date(data[data.length - 2].date), 1)])
.range([30, width]);
var xAxis = d3.svg.axis()
.scale(xTimeScale)
.orient("bottom")
.ticks(d3.time.days, .1)
.tickFormat(formatDate);
chart.append("g")
.attr("class", "xaxis axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
chart.selectAll(".xaxis text")
.attr("transform", function(d) {return "translate(" + this.getBBox().height * -2 + "," + this.getBBox().height + ")rotate(-45)";});
What am I missing?
Thanks in advance.
Update: Here is the jsFiddle updated with chrtan suggestions.
My problem now is to align text with the center of bar and not in left.
From the looks of your graph, you lost a bar's worth of space. If all the bars are supposed to be left-aligned against a tick, that final bar you have should be to the right of the January 31st tick.
You might need to add the February 1st tick by perhaps changing the [data.length - 2] to [data.length - 1] in the domain() for your xTimeScale.
Then for display purposes, you could probably remove the last tick axis text with:
d3.select(chart.selectAll(".xaxis text")[0].pop()).remove();
The inner selectAll should get the array containing your xAxis tick texts and then pop the very last tick. This last tick should then be removed by the outer select.
An example with an auto time ticks with d3.js
// set domain for axis
var x_domain = d3.extent(data, function(d) { return new Date(d.date); });
//format date
var date_format = d3.time.format('%Y %B');
var vis = d3.select("#graph")
.append("svg:svg")
.attr('class', 'chart')
.attr("width", width)
.attr("height", height);
var yScale = d3.scale.linear()
.domain([0, d3.max(data, function(d) { return d.data; })]).nice()
.range([height - padding, padding]);
var xScale = d3.time.scale()
.domain(x_domain)
.range([padding, width - padding]);
// define the y axis
var yAxis = d3.svg.axis()
.orient("left")
.scale(yScale);
// define the x axis
var xAxis = d3.svg.axis()
.orient("bottom")
.scale(xScale)
.tickFormat(date_format);
// draw y axis with labels and move in from the size by the amount of padding
vis.append("g")
.attr("class", "axis")
.attr("transform", "translate("+padding+",0)")
.call(yAxis);
// draw x axis with labels and move to the bottom of the chart area
vis.append("g")
.attr("class", "xaxis axis")
.attr("transform", "translate(0," + (height - padding) + ")")
.call(xAxis);
// and set data in graph...
a great example : http://bl.ocks.org/phoebebright/3061203
I am trying to develop a timeline chart on d3.js. As you will see on the image below, I cannot position the triangles on the same orientation with the y-axis values. The milestones are positioned in the middle of the related y-axis component.
yaxis initiation code fragment:
var x = d3.time.scale().rangeRound([0, self.config.width]);
var y = d3.scale.ordinal().rangeRoundBands([self.config.height, 0]);
var xAxis = d3.svg.axis().scale(x).orient("bottom").tickSubdivide(4).tickSize(6, 3, 0);//.ticks(d3.time.months,4)
var yAxis = d3.svg.axis().scale(y).orient("left").tickSize(4);
appending y axis to svg:
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
the code fragment for milestones:
var abs = svg.selectAll(".milestone")
.data(data)
.enter().append("g");
abs.selectAll("symbol")
.data(function(d) {
return d.milestoneList;
})
.enter().append("svg:path")
.attr("transform", function(d) {
return "translate(" + x(d.deadline) + "," + y(d.name) + ")";
})
.attr("d", d3.svg.symbol().type("triangle-down"));
For instance FG55 y-axis is set: translate(0,423) although the milestones from FG55 are set translate(<xValue for each>,376) so there are 47px difference on y
How can I position the yaxis labels and ticks properly?
I modified my code as it follows:
Old code
var y = d3.scale.ordinal().rangeRoundBands([self.config.height, 0]);
New Code
var y = d3.scale.ordinal().rangePoints([self.config.height, 0], 1.5);
When using bands in D3 the scale will give you the location of the top of the band, rather than the centre. The scale also provides you with the width of the bands which you can use to calculate the y position where your point should be placed.
Therefore in the code above, you would change your transform to this:
.attr("transform", function(d) {
var yPosition = y(d.name) + y.bandwidth() / 2;
return "translate(" + x(d.deadline) + "," + yPosition + ")";
})