d3 bar chart is upside down - javascript

I have a simple bar chart drawn in d3, with vertical bars: http://jsfiddle.net/philgyford/LjxaV/2/
However, it's drawing the bars down, with the baseline at the top of the chart.
I've read that to invert this, drawing up from the bottom, I should change the range() on the y-axis. So, change this:
.range([0, chart.style('height')]);
to this:
.range([chart.style('height'), 0]);
However, that looks like it's drawing the inverse of the chart - drawing in the space above each of the bars, and leaving the bars themselves (drawn from the bottom) transparent. What am I doing wrong?

Per the d3 basic bar chart :
http://bl.ocks.org/mbostock/3885304
You are correct in inverting the range.
Additionally, your rectangles should be added like this:
.attr('y', function(d) { return y(d.percent); } )
.attr('height', function(d,i){ return height - y(d.percent); });

The x and y coordinates for svg start in the top left. You want the y to start on the bottom. The code below assumes you're appending to some function along the lines of:
svg.selectAll('rect')
.data(dataset)
.enter()
.append('rect')
To make the bar plot act as you desire, set the y attribute to begin at distance data[i] above the axis:
.attr('y', function(d) { return height - d; })
Then, you must make the distance extend the remaining data[i] to the axis.
.attr('height', function(d) { return d; })
And that's it!

Setting the y attribute seems to work:
.attr('y', function(d){ return (height - parseInt(y(d.percent))); })
jsfiddle here

Related

Rotate label text on D3

Im creating a mekko chart and wanted to rotate the legends on x axis.
Example of what i did: https://codepen.io/fabioTester/pen/JjYeJEv
I want to rotate the elements with class "labelTitle" on the example...
I tried using the following code to rotate:
// rotation code
svg.selectAll(".month .labelTitle")
.attr("transform", function (d) {
return "translate(0) rotate(-25)"
});
I'm guessing my issue is the calculation of the translate, but can't figure out how to fix it.
Thanks in advance for any suggestions.
I noticed the labels seem to rotate around a point that is quite far away from their actual position, so a small increase in rotation would quickly rotate them out of sight.
If you set the transform-origin of every individual label to its x and y position, it will rotate the individual labels around that point instead.
svg
.selectAll(".month")
.append("text")
.text(function (d) {
return d.key;
})
.attr("x", 5)
.attr("y", function (d) {
return height - (margin * 2);
})
.attr('transform-origin', `5 ${height - (margin * 2)}`)
.attr("class", "labelTitle");
svg.selectAll('.labelTitle')
.attr('transform', d => 'translate(0, 10), rotate(25)')
I also noticed the y-value of your labels didn't respect the margin, so I fixed that as well.
I came up with the following codepen: https://codepen.io/pitchblackcat/pen/OJyawaV
It seems like you forgot to append deg to rotate's value.
Try this:
// rotation code
svg.selectAll(".month .labelTitle")
.attr("transform", function (d) {
return "translate(0) rotate(-25deg)"
});

Making more space for a graph legend in d3js

The names of the legend don't fully show.They are cut off, if I increase the width, the bars just grow bigger. How can I accommodate more space for my legend?
I tried appending the legend to the 'svg' tag instead of the 'g' tag but still not the desired results. I even plotted the axis, bars and legend on the 'svg' tag but its still not working.
javascript
const g= svg.append('g')
.attr("transform", `translate(${margin.left},${margin.top})`)
const xAxis= g.append('g')
.call(d3.axisBottom(x).tickSizeOuter(0))
.attr("transform", "translate(0," + innerHeight + ")")
const yAxis= g.append('g')
.call(d3.axisLeft(y).tickSizeOuter(0))
//stack the data? --> stack per subgroup
var stackedData = d3.stack()
.keys(subgroups)
(data)
var legend = g.append('g')
.attr('class', 'legend')
.attr('transform', `translate(${210},${20})`);
legend.selectAll('rect')
.data(subgroups)
.enter()
.append('rect')
.attr('x', 0)
.attr('y', function(d, i){
return i * 20;})
.attr('width', 14)
.attr('height', 14)
.attr('fill', function(d, i){
return color(i);
});
legend.selectAll('text')
.data(subgroups)
.enter()
.append('text')
.text(function(d){
return d;
})
.attr('x',18)
.attr('y', function(d, i){
return i * 18;
})
.attr('text-anchor', 'start')
.attr('alignment-baseline', 'hanging');
//below is my plotted data
telescope,allocated,unallocated
IRSF,61,28
1.9-m,89,0
1.0-m,64,23
// width=300 and heigh=300 for svg
I want to show the full names of the legends just next to the right of the bars for the graph.
Link to the graph is here.
How do I solve the problem?
Your issue is that the SVG's width is too narrow to accomodate showing all of the legend texts.
If your graph follows D3 conventions, space for elements that are auxilliary to graph itself (axis names, ticks, legends, etc.) is made using an margin object. Although I can't see how your graph is made, it looks like yours is setup to use a margin object as well. Following D3 conventions the margin values are fixed, which would explain why changing the width value just makes the bars wider yet still doesn't make space for the legend texts.
Therefore, locate the margin object and change its right value to something higher. Inspecting your example, it looks like doubling it should do it.
Hope this helps!

Axis Zooming - translation issue - d3.js version 4 -

short version:
I am using Axis Zooming and normal Zooming. I combine both together and the Zooming works fine. Only problem is, that the translation is not working as I want it. The translation one the axes is not 1 to 1. It depends on the scale factor of the normal zoom, how the translation behaves.
my status:
I have a line graph, which has normal zooming. Additional to that I have Axis-Zooming. So if I am in the Y-axis area, I only want to zoom the Y-axis and only move the Y-axis around. The same for the X-Axis.
For that I used d3.zoom instance and called(zoom) on 3 different rectangles.
is covering the whole graph area
is covering only x-axis
is only covering y-axis
The transform is saved on the elements.
The zoom function applies all 3 different zoom transforms to the scale, when triggered.
Setting everything up:
graph.zoomXY = d3.zoom()
.scaleExtent([-10, 1000]);
graph.overlayX = graph.g//.select(".axis.x")
.append("rect")
.attr("fill", "rgba(255,0,0,0.5)")
.attr("width", graph.rectWidth)
.attr("height", 15)
.attr("y", graph.rectHeight)
.call(graph.zoomXY);
graph.overlayY = graph.g//.select(".axis.y")
.append("rect")
.attr("fill", "rgba(255,0,0,0.5)")
.attr("width", 150)
.attr("height", graph.rectHeight)
.attr("x", -150)
.call(graph.zoomXY);
//append the rectangle to capture zoom
graph.overlayRect = graph.g.append("rect")
.attr("class", "overlay-rect")
.attr("width", graph.rectWidth)
.attr("height", graph.rectHeight)
.style("fill", "none")
.call(graph.zoomXY)
.on("dblclick.zoom", function() {
resetZoom();
} );
Calculating Scale:
function zoomed() {
getZoomedScales();
updateGraph();
}
function getZoomedScales() {
var transformX = d3.zoomTransform(graph.overlayX.node());
var transformY = d3.zoomTransform(graph.overlayY.node());
var transformXY = d3.zoomTransform(graph.overlayRect.node());
graph.yScaleTemp = transformXY.rescaleY(transformY.rescaleY(graph.yScale));
graph.xScaleTemp = transformXY.rescaleX(transformX.rescaleX(graph.xScale));
}
The Zooming is working fine. But the translation on the axes Zoom (graph.overlayY and graph.overlayX) is influenced by the Scaling factor of the zoom applied to graph.overlayRect. If I change the order, the issue will be just flipped. The axes Zoom's scale factor (graph.overlayY and graph.overlayX), messes up the translation of the Zoom on graph.overlayRect.
Open the fiddle and change the Zooming, while over the graph area. Then mousedown and mousemove on one of the axes. Repeat and see how it changes the translation.
Here is a fiddle:
https://jsfiddle.net/9j4kqq1v/

D3 brushing on grouped bar chart

I am trying to get brushing to work similar to this example, but with a grouped bar chart: http://bl.ocks.org/mbostock/1667367
I don't really have a good understanding of how brushing works (I haven't been able to find any good tutorials), so I'm a bit at a loss as to what is going wrong. I will try to include the relevant bits of code below. The chart is tracking the time to fix broken builds by day and then grouped by portfolio. So far the brush is created and the user can move and drag it, but the bars in the main chart are re-drawn oddly and the x axis is not updated at all. Any help you can give would be greatly appreciated. Thank you.
// x0 is the time scale on the X axis
var main_x0 = d3.scale.ordinal().rangeRoundBands([0, main_width-275], 0.2);
var mini_x0 = d3.scale.ordinal().rangeRoundBands([0, main_width-275], 0.2);
// x1 is the portfolio scale on the X axis
var main_x1 = d3.scale.ordinal();
var mini_x1 = d3.scale.ordinal();
// Define the X axis
var main_xAxis = d3.svg.axis()
.scale(main_x0)
.tickFormat(dateFormat)
.orient("bottom");
var mini_xAxis = d3.svg.axis()
.scale(mini_x0)
.tickFormat(dateFormat)
.orient("bottom");
After binding the data...
// define the axis domains
main_x0.domain(data.result.map( function(d) { return d.date; } )
.sort(d3.ascending));
mini_x0.domain(data.result.map( function(d) { return d.date; } )
.sort(d3.ascending));
main_x1.domain(data.result.map( function(d) { return d.portfolio; } )
.sort(d3.ascending))
.rangeRoundBands([0, main_x0.rangeBand() ], 0);
mini_x1.domain(data.result.map( function(d) { return d.portfolio; } )
.sort(d3.ascending))
.rangeRoundBands([0, main_x0.rangeBand() ], 0);
// Create brush for mini graph
var brush = d3.svg.brush()
.x(mini_x0)
.on("brush", brushed);
After adding the axis's, etc.
// Create the bars
var bar = main.selectAll(".bars")
.data(nested)
.enter().append("g")
.attr("class", function(d) { return d.key + "-group bar"; })
.attr("fill", function(d) { return color(d.key); } );
bar.selectAll("rect").append("rect")
.data(function(d) { return d.values; })
.enter().append("rect")
.attr("class", function(d) { return d.portfolio; })
.attr("transform", function(d) { return "translate(" + main_x0(d.date) + ",0)"; })
.attr("width", function(d) { return main_x1.rangeBand(); })
.attr("x", function(d) { return main_x1(d.portfolio); })
.attr("y", function(d) { return main_y(d.buildFixTime); })
.attr("height", function(d) { return main_height - main_y(d.buildFixTime); });
Here is the brush function (trying several different options)...
function brushed() {
main_x1.domain(brush.empty() ? mini_x1.domain() : brush.extent());
//main.select("rect")
//.attr("x", function(d) { return d.values; })
//.attr("width", function(d) { return d.values; });
bar.select("rect")
.attr("width", function(d) { return main_x1.rangeBand(); })
.attr("x", function(d) { return main_x1(d.portfolio); });
//.attr("y", function(d) { console.log(d); return main_y(d.buildFixTime); })
//.attr("height", function(d) { return main_height - main_y(d.buildFixTime); });
main.select(".x.axis").call(main_xAxis);
}
The problem comes from trying to use the brush to set the x-scale domain, when your x-scale is an ordinal scale. In other words, the expected domain of your x-axis is a list of categories, not a max-min numerical extent. So the problem is right at the top of the brushing function:
function brushed() {
main_x0.domain(brush.empty() ? mini_x0.domain() : brush.extent());
The domain set by brush.extent() is an array of two numbers, which then completely throws off your ordinal scale.
According to the wiki, if one of the scales attached to a brush function is an ordinal scale, the values returned by brush.extent() are values in the output range, not in the input domain. Ordinal scales don't have an invert() method to convert range values into domain values.
So, you have a few options on how to proceed:
You could re-do the whole graph using a linear time scale for your main x-axes instead of an ordinal scale. But then you have to write your own function to figure out the width of each day on that axis instead of being able to use .rangeBand().
You can create your own "invert" function to figure out which categorical values (dates on the mini_x0.domain) are included in the range returned by brush.extent(). Then you would have to both reset the main_x0.domain to only include those dates on the axis, and filter out your rectangles to only draw those rectangles.
Or you can leave the domain of main_x0. be, and change the range instead. By making the range of the graph larger, you space out the bars greater. In combination with a clipping path to cut off bars outside the plotting area, this has the effect of only showing a certain subset of bars, which is what you want anyway.
But what should the new range be? The range returned by brush.extent() is the beginning and end positions of the brushing rectangle. If you used these values as the range on the main graph, your entire graph would be squished down to just that width. That's the opposite of what you want. What you want is for the area of the graph that originally filled that width to be stretched to fill the entire plotting area.
So, if your original x range is from [0,100], and the brush covers the area [20,60], then you need a new range that satisfies these conditions:
the 20% mark of the new range width is at 0;
the 60% mark of the new range width is at 100.
Therefore,
the total width of the new range is ( (100-0) / (60-20) )*(100-0) = 250;
the start of the new range is at (0 - (20/100)*250) = -50;
the end of the new range is at (-50) + 250 = 200.
Now you could do all the algebra for figuring out this conversion yourself. But this is really just another type of scaling equation, so why not create a new scale function to convert between the old range and the zoomed-in range.
Specifically, we need a linear scale, with its output range set to be the actual range of the plotting area. Then set the domain according to the range of the brushed area that we want to stretch to cover the plotting area. Finally, we figure out the range of the ordinal scale by using the linear scale to figure out how far off the screen the original max and min values of the range would be. And from there, we-can resize the other ordinal scale and reposition all the rectangles.
In code:
//Initialization:
var main_xZoom = d3.scale.linear()
.range([0, main_width - 275])
.domain([0, main_width - 275]);
//Brushing function:
function brushed() {
var originalRange = main_xZoom.range();
main_xZoom.domain(brush.empty() ?
originalRange:
brush.extent() );
main_x0.rangeRoundBands( [
main_xZoom(originalRange[0]),
main_xZoom(originalRange[1])
], 0.2);
main_x1.rangeRoundBands([0, main_x0.rangeBand()], 0);
bar.selectAll("rect")
.attr("transform", function (d) {
return "translate(" + main_x0(d.date) + ",0)";
})
.attr("width", function (d) {
return main_x1.rangeBand();
})
.attr("x", function (d) {
return main_x1(d.portfolio);
});
main.select("g.x.axis").call(main_xAxis);
}
Working fiddle based on your simplified code (Note: you still need to set a clipping rectangle on the main plot):
http://fiddle.jshell.net/CjaD3/1/

d3 clip-path not working for filled line graph

I have a filled line graph and I am trying to set a clip-path. Right now the filled area descends below the graph border and into the axis notations.
Here is a JS fiddle of my code: http://jsfiddle.net/HgP6D/6/
Even setting the clip-path to a small value doesn't seem to change anything:
// Add the clip path.
g.append("clipPath")
.attr("id", "rect")
.append("rect")
.attr("width", 100)
.attr("height", 100)
Anyone have any idea? Thanks.
I don't know about the clip path, but this fixes the area so it doesn't descend below 0:
var area = d3.svg.area()
.interpolate( "monotone" )
.x(function(d,i) { return x(i); })
.y0(-y(0))
.y1(function(d) { return -y(d); })

Categories

Resources