d3 bar chart scale reversed on y-axis, doesn't look like the classic problem, why is it acting odd? - d3.js

Drawing a histogram in d3 (vertical bar chart), works fine except the y-axis scale is reversed. Here is the code that (almost) works:
let maxFrequency = d3.max(histData, d=> d.length);
let yScale = d3.scaleLinear()
.range([0,config.height - config.margin.top - config.margin.bottom])
.domain([0,maxFrequency]); //<-- this is wrong, right? It should be [maxFrequency,0]
let xScale = d3.scaleBand()
.range([0, (config.width - config.margin.left - config.margin.right)])
.domain(histData.map(d => d.Name))
.padding(0.2);
//Draw the histogram bars
let bars = config.svgContainer.selectAll("rect")
.data(histData);
bars.enter()
.append("rect")
.attr("width", xScale.bandwidth())
.attr("height", (d)=> yScale(d.length))
.attr("x", (d) => xScale(d.Name) + config.margin.left)
.attr("y", (d)=> config.height - config.margin.bottom - yScale(d.length))
.attr("fill", "red");//"#2a5599");
let axisX = d3.axisBottom(xScale)
config.svgContainer.append("g")
.style("transform", `translate(${config.margin.left}px,${config.height -
config.margin.bottom}px)`)
.call(axisX)
let axisY = d3.axisLeft(yScale)
config.svgContainer.append("g")
.style('transform',`translate(${config.margin.left}px,${config.margin.top}px)`)
.call(axisY);
This code produces this graph.
enter image description here
Normally you need to reverse the order of the domain attribute for the y-axis. Here is the graph when I use the ".domain([maxFrequency,0])" code with no other changes.
enter image description here
You can see that the y-axis now is correct but the bars look wonky. On closer inspection you can see that the data (1,9,40,80) is still being represented, but the bars are a now reversed. The bars are now height 79 (80-1), 71 (80-9), 40 (80-40), and 0 (80-80).
I hope this is something simple I'm not seeing. I can't find any posted solutions for something that sounds like this.

Related

How to control the spacings of the x Axis label positions in a stacked bar chart?

I took bits from this and this solutions.
I then do the following to show my x axis label ticks:
var x = d3.scaleBand().range([0, width]).padding(.1).domain(d3.range(1993, 2002));
svg.append("g")
.attr('class', 'axis')
.attr('transform', 'translate(-10,' + (height - 20) + ')')
.call(d3.axisBottom(x));
However, I then get the following output where x axis ticks are shifted further to the right each time like if their gap doesn't match the bar's.
The following snippet shows how the rects and axis's ticks line up correctly.
Need to be careful on how d3.range generates arrays, in that d3.range(1993, 2002) will create [1993, 1994, ... , 2001].
Also, I'm not sure why the g element that contains the X axis was shifted left, so that is set to 0
var width = 800
var height = 100
//this makes an array 1993 to 2001. Is that what you want?
var data = d3.range(1993, 2002)
var x = d3.scaleBand()
.range([0, width])
.padding(.1)
.domain(data);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
svg.append("g")
.attr('class', 'axis')
//.attr('transform', 'translate(-10,' + (height - 20) + ')')
.attr('transform', 'translate(0,' + (height - 20) + ')')
.call(d3.axisBottom(x));
svg.selectAll('rect')
.data(data)
.enter()
.append('rect')
.attr('x', d => x(d))
.attr('y', 0)
.attr('width', x.bandwidth())
.attr('height', x.bandwidth())
<script src="https://d3js.org/d3.v4.min.js"></script>
I don't have enough S.O to 'comment' but I think it may be helpful to use the xScale:
-in the xAxis (something like...
xAxis = d3.axisBottom()
.scale(xScale)
)
-in the x position of each bar ( something like...
'x', d => xScale(year)
)
-in the width of each bar, seems like you might need to use something like...
('width', x.bandwidth())
looks like the xScale in the (1)axis and (2)bars are not working as expected together.
Maybe this helps?!

D3 v4 - make a horizontal bar chart with fixed width

I have made a horizontal bar chart using d3 v4, which works fine except for one thing. I am not able to make the bar height fixed. I am using bandwidth() currently and if i replace it with a fixed value say (15) the problem is that it does not align with the y axis label/tick http://jsbin.com/gigesi/3/edit?html,css,js,output
var w = 200;
var h = 400;
var svg = d3.select("body").append("svg")
.attr("width", w)
.attr("height", h)
.attr("transform", "translate(80,30)");
var data = [
{Item: "Item 1", count: "11"},
{Item: "Item 2", count: "14"},
{Item: "Item 3", count: "10"},
{Item: "Item 4", count: "14"}
];
var xScale = d3.scaleLinear()
.rangeRound([0,w])
.domain([0, d3.max(data, function(d) {
return d.count;
})]);
var yScale = d3.scaleBand()
.rangeRound([h,0]).padding(0.2)
.domain(data.map(function(d) {
return d.Item;
}));
var yAxis = d3.axisLeft(yScale);
svg.append('g')
.attr('class','axis')
.call(yAxis);
svg.selectAll('rect')
.data(data)
.enter()
.append('rect')
.attr('width', function(d,i) {
return xScale(d.count);
})
.attr('height', yScale.bandwidth())
.attr('y', function(d, i) {
return yScale(d.Item);
}).attr("fill","#000");
The y axis seemed to be off SVG in the link you provided. (Maybe you have overflow: visible; for the SVG.
Anyway, I've added a few margins to the chart so that the whole chart is visible. Here it is (ignore the link description):
DEMO: H BAR CHART WITH HEIGHT POSITIONING TO THE TICKS
Relevant code changes:
As you are using a scale band, the height is computed within. You just need to use .bandWidth().
.attr('height', yScale.bandwidth())
Added a margin and transformed the axis and the bars to make the whole chart visible :
: I'm assigning margins so that the y-axis is within the viewport of the SVG which makes it easier to adjust the left margin based on the tick value as well. And I think this should be a standard practice.
Also, if you notice, the rects i.e. bars are now a part of <g class="bars"></g>. Inspect the DOM if you'd like. This would be useful for complex charts with a LOT of elements but it's always a good practice.
var margin = {top: 10, left: 40, right: 30, bottom: 10};
var xScale = d3.scaleLinear()
.rangeRound([0,w-margin.left-margin.right])
var yScale = d3.scaleBand()
.rangeRound([h-margin.top-margin.bottom,0]).padding(0.2)
svg.append('g')
.attr('class','axis')
.attr('transform', 'translate(' + margin.left+', '+margin.top+')')
Try changing the data and the bar height will adjust and align according to the ticks. Hope this helps. Let me know if you have any questions.
EDIT:
Initially, I thought you were facing a problem placing the bars at the center of the y tick but as you said you needed fixed height bars, here's a quick addition to the above code that lets you do that. I'll add another approach using the padding (inner and outer) sometime soon.
Updated JS BIN
To position the bar exactly at the position of the axis tick, I'm moving the bar from top to the scale's bandwidth that is calculated by .bandWidth() which will the position it starting right from the tick and now subtracting half of the desired height half from it so that the center of the bar matches the tick y. Hope this explains.
.attr('height', 15)
.attr('transform', 'translate(0, '+(yScale.bandwidth()/2-7.5)+')')

How to limit the text of polygons in Voronoi diagram with D3.js?

I've see the Example of D3.js-Voronoi Tessellation.But I want to put some text in each of polygons instead of a circle,Here is my js code:
var width = 600, height = 400;
var vertices = d3.range(20).map(function(d){
return [Math.random() * width, Math.random() * height]
});
var voronoi = d3.geom.voronoi();
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
path = svg.append("g").selectAll("path");
svg.selectAll("info")
.data(vertices.slice(1))
.enter().append("text")
.attr("transform", function(d) {
return "translate(" + d + ")";
})
.text("someText")
.attr("shape-rendering","crispEdges")
.style("text-anchor","middle");
redraw();
function redraw(){
path = path
.data(voronoi(vertices), polygon);
path.exit().remove();
path.enter().append("path")
.attr("class", function(d, i) {return "q" + (i % 9) + "-9";})
.attr("d", polygon);
path.order();
}
function polygon(d){
return "M" + d.join("L") + "Z";
}
I have a JSFiddle for that basic example here:
my voronoi code
now, I want each of the polygons' text in the center of the polygon, and don't cross with the polygon's border. If the polygon have not enough space to contain the all text, just contain the first part of it!
Let me know if there is anything I can do to solve this issue, thank you!
PS:I'm so sorry to my English, yes, it's so poor! :)
Have a look at this example http://bl.ocks.org/mbostock/6909318 , you probably want to place the text at the polygon centroid and not the seed (point) used to determine the voronoi tessellation.
That should fix the majority of your layout issues.
Automatically scaling the text to fit is a little bit harder, if you are willing to scale and rotate the text you can use a technique similar to the following to determine the length of the line at that point:
https://mathoverflow.net/questions/116418/find-longest-segment-through-centroid-of-2d-convex-polygon
Then you need to determine the angle of the line. I have a plugin that should help with that:
http://bl.ocks.org/stephen101/7640188/3ffe0c5dbb040f785b91687640a893bae07e36c3
Lastly you need to scale and rotate the text to fit. To determine the width of the text use getBBox() on the text element:
var text = svg.append("svg:text")
.attr("x", 480)
.attr("y", 250)
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.style("font", "300 128px Helvetica Neue")
.text("Hello, getBBox!");
var bbox = text.node().getBBox();
Then you use the angle you calculated earlier to scale and rotate your text:
text.attr("transform", "rotate(40) scale(7)")
I would love to give a complete example but this is quite a bit of work to get it right.
There are other options to achieve the same effect but none of them are simple (ie you could anneal the layout similar to the way d3 does the Sankey layout)

D3 - Vertical axis is not displaying correctly

I have a CSV file containing the French population by department. I can correctly display a map colored with population density but I encounter problems with the associated legend.
You can see the current result here: http://i.stack.imgur.com/Y26JT.jpg
After loading the CSV, here is my code to add the legend :
legend = svg.append('g')
.attr('transform', 'translate(525, 150)')
.attr('id', 'legend');
legend.selectAll('.colorbar')
.data(d3.range(9))
.enter().append('svg:rect')
.attr('y', function(d) { return d * 20 + 'px'; })
.attr('height', '20px')
.attr('width', '20px')
.attr('x', '0px')
.attr("class", function(d) { return "q" + d + "-9"; })
.attr('stroke', 'none');
legendScale = d3.scale.linear()
.domain([0, d3.max(csv, function(e) { return +e.POP; })])
.range([0, 9 * 20]);
legendAxis = d3.svg.axis()
.scale(legendScale)
.orient('right')
.tickSize(1);
legendLabels = svg.append('g')
.attr('transform', 'translate(550, 150)')
.attr('class', 'y axis')
.call(legendAxis);
Colors are obtain using ColorBrewer CSS (https://github.com/mbostock/d3/tree/master/lib/colorbrewer)
I have two problems:
The Hyphen '-' (a SVG line) is not displayed for each value of my axis
I cannot choose the number of values in my axis, I would like one at the beginning of each color block.
Thanks in advance for any help.
The reason your solution isn't showing the 'hyphen' (svg lines) is because you have the tickSize set very small. Try not setting it and it will default to 6 (according to the API docs - https://github.com/mbostock/d3/wiki/SVG-Axes).
To choose the number of values in your axis, you can add a call to ".ticks(N)", where N is the number of ticks you want. D3 will try to show that many ticks. You could also call ".tickValues([...])" and pass in the exact array of values to use for the ticks.
Here's a JSFiddle that corrects the issues in your example: http://jsfiddle.net/TRkGK/3/
And a sample of the part that fixes your issues:
var legendAxis = d3.svg.axis()
.scale(legendScale)
.orient('right')
.tickSize(6) // Tick size controls the width of the svg lines used as ticks
.ticks(9); // This tells it to 'try' to use 9 ticks
UPDATED:
You also want to make sure you're setting the CSS correctly. Here's what I use:
.y.axis line { stroke: #ccc; }
.y.axis path { display: none; }
In your example, when you add the larger tickSize, you are seeing the path in which the tick lines are defined. If you hide the path and give the lines a color, you'll see the ticks rather than the area in which the ticks are defined.
Hope this helps!
It looks like it could be a css issue, did you look at reblace's css in his fiddle?

D3 log scale displaying wrong numbers

I'm trying to wrap my head around the log scales provided by D3.js. It should be noted that as of yesterday, I had no idea what a logarithmic scale was.
For practice, I made a column chart displaying a dataset with four values: [100, 200, 300, 500]. I used a log scale to determine their height.
var y = d3.scale.log()
.domain([1, 500])
.range([height, 1]);
This scale doesn't work (at least not when applied to the y-axis as well). The bar representing the value 500 does not reach the top of the svg container as it should. If I change the domain to [100, 500] that bar does reach the top but the axis ticks does not correspond to the proper values of the bars. Because 4e+2 is 4*10^2, right?
What am I not getting here? Here is a fiddle.
Your scale already reverses the range to account for the SVG y-coordinates starting at the top of the screen -- ie, you have domain([min, max]) and range([max, min]). This means your calcs for the y position and height should be reversed because your scale already calculated y directly:
bars.append("rect")
.attr("x", function (d, i) { return i * 20 + 20; })
.attr("y", function (d) { return y(d); })
.attr("width", 15)
.attr("height", function (d) { return height - y(d); });
Here's an updated Fiddle: http://jsfiddle.net/findango/VeNYj/2/

Resources