I have a time-series d3.js chart which I can't seem to get lined up properly. Initially I create the x-axis with:
var x_domain = d3.extent(active_data, function(d) { return d.date; })
var x = d3.time.scale()
.domain(x_domain)
.range([bar_width/2, width-bar_width/2]); // stop bars going outside chart
var date_format = d3.time.format("%b %y");
var xAxis = d3.svg.axis()
.orient("bottom")
.scale(x)
.ticks(8)
.tickFormat(date_format);
// create the xAxis
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)");
svg.selectAll(".x text") // some legacy code I had lying around
.attr("transform", function(d) {
return "translate(" + this.getBBox().height*3 + "," + this.getBBox().height + ")rotate(0)";
and then I update later on when user selects a date range. "active_data" is updated with the actual values/dates that will show from my total data before this update.
function update_chart(start){
/*...update active_data and other stuff...*/
x.domain(d3.extent(active_data, function(d){return d.date;}))
.range([bar_width/2, width-bar_width/2]);
/*...*/
var svg = d3.select("body").transition().duration(500);
svg.select(".x.axis").call(xAxis);
}
There's a lot more going on, but those are the related bits that I'm having trouble with. I've tried everything I can think of, searched online and I just don't understand what I'm doing wrong. It produces the following sort of problem:
d3 time scale some ticks on second line
You can see the dates go onto the second line. On some charts, when I update to the lowest or the maximum date, it draws it correctly, then draws all of them correctly afterwards. On another chart, it does this:
ticks all over each other
On this second one, I don't change the .ticks(8), so I don't know why it does that. Any insight would be greatly appreciated.
Ok I figured it out, and there were a couple bugs.
Then, the following was giving some dates a special x location, but not all of them:
// create the xAxis
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)");
I changed it to:
// create the xAxis
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
and let the update do all the work (let D3 figure it out). The date was now in the right spot, except it was vertical instead of horizontal. I fixed that by removing this:
svg.selectAll(".x text") // some legacy code I had lying around
.attr("transform", function(d) {
return "translate(" + this.getBBox().height*3 + "," + this.getBBox().height + ")rotate(0)";
And it works properly!
Related
I'm writing d3.js (d3 v4) code to generate a simple bar chart, which I managed to do, but I am confused about the behavior of the code in generating axes.
So basically, I encapsulated the chart code into a function that contains basic accessors.
Here is some code showing the core of my chart function.
function chart(selection){
selection.each(function(data){
var x = d3.scale.ordinal().rangeRoundBands([0, width], 0.5);
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")
.ticks(10);
x.domain(data.map(function(d) { return d.name; }));
y.domain([0, d3.max(data, function(d) { return d.value; })]);
var div = d3.select(this)
var svg = div.selectAll("svg")
.data([data]).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 + ")");
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)" );
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 5)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Frequency");
svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("fill", "#1CE6FF")
.attr("x", function(d) { return x(d.name); })
.attr("width", x.rangeBand())
.attr("y", function(d) { return y(d.value); })
.attr("height", function(d) { return height - y(d.value); });
});
So I have two questions: 1) Is it okay to use multiple code blocks invoking svg.append("g") like shown above in the code?
2) When I replace the following code :
var svg = div.selectAll("svg")
.data([data]).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 + ")");
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)" );
With this (which is basically merging the two blocks in a single chain code chunk):
var svg = div.selectAll("svg")
.data([data]).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 + ")").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)" );
...why is my y axis not showing at the right place? The <g class="y axis">...</g> tag appears nested into of a <text> tag itself nested in the x axis tag. Why the 2-block code vs single block code not behaving the same?
The structure of the code defines the structure of the resulting SVG. In the function chart() the variable svg holds the reference to the lastly appended <g> not to the previously appended <svg> itself. Note, how all statements after its first creation append to that same outer group of svg. That's one perfectly fine way of doing this.
If you concatenate the statements, however, like you did in your last snippet, the value of svg changes because the right hand side, i.e. the return value of the expression, changes. Since you last selected all text elements of the appended x-axis svg contains exactly those texts. Given that, it is pretty easy to understand that this breaks the rest of the code which will now append other contents to the text elements. Even if it was syntactically valid, which it is not, it would most likely still break the layout.
If you want to rearrange the code structure you have to make sure to have the correct references at the time they are needed.
I'd like to make only the x-axis labels disappear. Currently, I have the following code, but it makes the entire x-axis disappear, and I'd like to keep the horizontal line.
Is there a way to just target the ticks and the text alone?
// Make x-axis
let xAxis = d3.axisBottom(scaleX);
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + height + ")")
.attr("opacity", 0.7)
.call(xAxis);
// Target x-axis; but this targets the entire axis
let setXAxisOpacity = (opacity, duration=120) => {
d3.select(".axis")
.transition()
.duration(duration)
.ease(d3.easeLinear)
.style("opacity", opacity)
}
you need to target the text and line parts of the axis
d3.select('.axis').selectAll('text')
.transition()
.duration(duration)
.ease(d3.easeLinear)
.style("opacity", opacity);
d3.select('.axis').selectAll('line')
.transition()
.duration(duration)
.ease(d3.easeLinear)
.style("opacity", opacity);
I want to format the x axis text with more padding and space and I am not able to achieve this. The actual text is around 6-7 characters long, but it is only showing 3. Here is the extract of the code and a screenshot:
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.selectAll("text")
.attr("transform", "rotate(90)");
d3 bar chart
When rotating the text in the ticks, you have to set the x and y. Also, it's a good idea changing the text-anchor. Something like this:
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.selectAll("text")
.attr("y", 0)//tweak this value
.attr("x", 10)//tweak this value
.attr("dy", ".35em")//tweak this value
.attr("transform", "rotate(90)")
.style("text-anchor", "start");
Check the demo:
var svg = d3.select("body")
.append("svg")
.attr("width", 400)
.attr("height", 100);
var data = ["foo", "bar", "baz", "foobar", "foobaz"];
var scale = d3.scaleBand()
.range([0, 400])
.domain(data);
var axis = d3.axisBottom(scale);
var gX = svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0,30)")
.call(axis)
.selectAll("text")
.attr("y", 0)//tweak this value
.attr("x", 10)//tweak this value
.attr("dy", ".35em")//tweak this value
.attr("transform", "rotate(90)")
.attr("font-size", 14)
.style("text-anchor", "start");
<script src="https://d3js.org/d3.v4.min.js"></script>
I am using d3 js to draw a bar graph. I have x and y axis too.
The x axis would hold the 'names' and y axis the 'marks'. I am using ordinal scale for x axis.
In my json input dataset_rule_errors, I have 10 entries.
My code is
var svgd = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
var x_domain = dataset_rule_errors.map(function(d) { return d.Rulename; })
var xScale = d3.scale.ordinal()
.domain(dataset_rule_errors.map(function (d) { return d.Rulename; }))
.rangeBands([padding_rule, wsvg]);
var xaxeScale = d3.scale.ordinal()
.domain(x_domain)
.rangePoints([padding_rule, wsvg]);
var xAxis = d3.svg.axis()
.scale(xaxeScale)
.tickValues(x_domain)
.orient("bottom");
//drawing rectangles
svgd.append("g")
.selectAll("rect") //based on the data in the dataset[] array, append rectangles s.t.
.data(dataset_rule_errors) //
.enter()
.append("rect")
.attr("x", function (d, i) {
return xScale(d.Rulename); // x position of rect as per i->0,1,2,3,...
})
.attr("y", function (d) {
return (h_rule - yScale(d.NumRuleFailed)); //y position of rect as per (h-value) to prevent inverted range
})
.attr("width", xScale.rangeBand())//"10") //depending upon domain->no of inputs - with of band is decided acc. to fit in svg
.attr("height", function (d) {
return yScale(d.NumRuleFailed); //depending upon domain->value of inputs - with of band is decided acc. to fit in svg
})
.attr("fill", function (d, i) { //colour based on values -> more errors - dark coloured bars
if(i%2==0)
return "rgb(" + 255 + "," + 255 + "," + 200 + ")";
else
return "rgb(" + 0 + "," + 0 + "," + 200 + ")";
})
.attr("stroke", "black");
//drawing x axis with ticks
svgd.append("g")
.attr("class", "x axis")
.attr("transform", "translate(" + 0 + "," + (h_rule) + ")")
.call(xAxis)
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", ".15em")
.attr("text-anchor", "start")
.attr("transform", function (d) {
return "rotate(-90)"
})
.selectAll(".tick text")
.style("text-anchor", "start");
The problem I am facing is that my rectangles and the ticks of x-axis do not align with one another.
The reason is because I have 10 bars and therefore, I should be having 11 ticks including the one at the beginning and the end. But I have only 10 ticks, which distribute evenly along the axis length, so they do not coincide with the rectangle beginnings just like in this question Unable to align ticks with d3.js.
But the solution for this question did not work out for me. What can I do?
dataset_rule_errors = data I retrieve from my database
[{"Rulename":"A","NumRuleFailed":34321},{"Rulename":"B","NumRuleFailed":43},{"Rulename":"C","NumRuleFailed":45522},
{"Rulename":"D","NumRuleFailed":43643},{"Rulename":"E","NumRuleFailed":152},{"Rulename":"F","NumRuleFailed":152}]
I could not reproduce the issue you said you were having but I highly recommend using rangeRoundBands for a bar chart.
You can achieve the bar chart with the following setup:
var x = d3.scale.ordinal()
.rangeRoundBands([0, width], .2);
var y = d3.scale.linear()
.range([height, 0]);
// Finding domain of x (all our rulenames)
x.domain(data.map(function(d) {
return d.Rulename;
}));
// Finding domain of y (min and max values)
y.domain([d3.min(data, function(d) {
return d.NumRuleFailed;
}), d3.max(data, function(d) {
return d.NumRuleFailed;
})]);
var xAxis = d3.svg.axis()
.scale(x)
// no need yo specify ticks, x scale
// will take care of that
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
And the following for axis and rect rendering:
// Render xAxis
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", "-.50em")
.attr("text-anchor", "start")
.attr("transform", "rotate(-90)")
.selectAll(".tick text")
.style("text-anchor", "start")
// Render yAxis
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("NumRuleFailed");
// Render rects
svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("x", function(d) {
return x(d.Rulename);
})
.attr("width", x.rangeBand())
.attr("y", function(d) {
return y(d.NumRuleFailed);
})
.attr("fill", function(d, i) { //colour based on values -> more errors - dark coloured bars
return (i % 2) ? 'green' : 'red';
})
.attr("height", function(d) {
return height - y(d.NumRuleFailed);
});
Full Plnkr: https://plnkr.co/edit/jmtMGcRyT9hM5efwwTOb?p=preview
I am trying to draw a path that means line chart using d3.js. I am using the following code
var data;
d3.csv("myfile.csv",function(datagot){data=datagot;});
var format = d3.time.format("%Y/%m/%d %H:%M:%S");
data.forEach(function (e){
e.dist = +e.dist;
e.speed = +e.speed;
e.lat=+e.lat;
e.lon=+e.lon;
e.dd=format.parse(e.time);
});
var xScale = d3.time.scale().range([margin.left, width - margin.right]).domain([d3.min(dataset,function(d){return d.dd}),d3.max(dataset,function(d){ return d.dd})]),
yScale = d3.scale.linear().range([height - margin.top, margin.bottom]).domain([d3.min(dataset,function(d){return d.dist;}),d3.max(dataset,function(d){return d.dist;})]),
xAxis = d3.svg.axis()
.scale(xScale).ticks(10).tickFormat(d3.time.format("%H:%M")).tickPadding(2),
yAxis = d3.svg.axis()
.scale(yScale).orient("left").tickPadding(5).ticks(5);
var svg = d3.select("body").append("svg")
.attr("width", width-50 )
.attr("height", height -60);
svg.append("svg:g")
.attr("class","axis")
.attr("transform", "translate(0," + (height - margin.bottom) + ")")
.call(xAxis);
// x axis label
svg.append("text")
.attr("x", width / 2 )
.attr("y", height - 60)
.style("text-anchor", "middle")
.text("Time");
svg.append("svg:g")
.attr("class","axis")
.attr("transform", "translate(" + (margin.left-10) + ",-90)")
.call(yAxis);
// Y axis label
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 15)
.attr("x",70- (height / 2))
.attr("dy", "1em")
.style("text-anchor", "middle")
.text("Distance");
// svg.append("g")
//.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var x = d3.scale.linear()
.range([0, width]);
var y = d3.scale.linear()
.range([height, 0]);
lineh = d3.svg.line().x(function(d) {
return x(d.dd);
}).y(function(d) {
return y(d.dist);
});
var line = svg.append("g").attr("transform", "translate(" + margin.left + "," + (-margin.top) + ")").selectAll(".hour")
.data(outputf)
.enter().append("path")
//.attr("x", function(d) { return (d.dd.getMinutes())*15 ; })
// .attr("cx", function(d) { return (d.dd.getMinutes())*10 ; })
//.attr("y", function(d) { return (d.dist)*50 ; })
// .attr("cy", function(d) { return height-100-(d.dist)*50 ; })
//.attr("r",3)
/* .attr("rx", 2)
.attr("ry", 2)*/
.attr("d",lineh)
.attr("class", "line");
But its actually not plotting anything. I am giving the jsfiddle for your help to understand. http://jsfiddle.net/1b0gn0r2/. The jsfiddle is not well organized but it contains my code and the csv data I am using is at the bottom. Can anyone help me to find the error?
In my actual code the output is the following
There's a lot that seems to be wrong in your code! Having various things missing (like margin, height, width, other variables, the CSS etc), makes it very painful to answer your question.
To start with, if you're struggling with jsfiddle you may prefer Plunker, which makes it easier to organise your code and data.
I've created a plunk that works with your code here: http://plnkr.co/edit/ZEi7U6qQ8pxq06FdDIW6?p=preview
...but it involved quite a few changes, which I'll try to summarise:
Loading is asynchronous, so this line:
d3.csv("myfile.csv",function(datagot){data=datagot;});
will not do what you expect. The rest of the code will go off and execute (eg drawing axes) without data having been properly set. So you try to draw the line before the data is ready. Fix this by inserting the main code into the data load function.
Defining scales. You seem to define 4 different scales:
xScale = d3.time.scale()
yScale = d3.scale.linear()
x = d3.scale.linear()
y = d3.scale.linear()
That causes problems later in your line drawing function (more to come on that). I've got rid of x and y here
The line drawing function lineh expects the scales to be x and y, but I suggest using the xScale and yScale ones you've defined before.
// line function
lineh = d3.svg.line()
.x(function(d) {
return xScale(d.dd); // <- do not use d(d.dd)
})...
Calling the line function should be done in a completely different way: Your code:
var line = svg.append("g")
.data(outputf)
.enter().append("path")
.attr("d",lineh)
.attr("class", "line");
Not sure what outputf is so I ignored that. lineh (defines how to draw the line) should be called with the data. I changed the call to:
var line = svg.append("g")
.append("path")
.attr("d", lineh(dataset)) // <- Note the change
.attr("class", "line");
Finally I reorganised the code to bring some variable definitions to the top of the file and make it easier to follow.