Calculating the number of bars in a bar chart in dc.js - d3.js

I want to adjust width of bar charts based on number of bars appearing on my bar chart. Here is my chart which is displaying enough records which are fit to the width and is displayed nicely.
Here is another scenario in which only two bars are displayed in same width, Which looks odd.
For this situation I need to adjust the width of charts according to the number of bars, so that my chart will look pretty.
Here is the part of my script
var width = ??; // Here how should I calculate width based on the number of bars?
redrawCustomerCodeBarChart
.width(width)
.height(400)
.margins({top: 10, right: 10, bottom: 70, left: 30})
.dimension(customerNamer)
.group(customerNameGroupr)
.x(d3.scale.ordinal().domain(newCoh.map(function (d) {return d.customerName; })))
.xUnits(dc.units.ordinal)
.elasticY(true)
.renderHorizontalGridLines(true)
.on('renderlet',function(chart){
chart.selectAll("g.x text")
.attr('dx', '-15')
.attr('transform', "rotate(-55)");
})
.on('filtered.monitor', function(d) {
// return d.key + ' (' + d.value + ')';
if(d.value !== undefined)
size= d.value;
})
.controlsUseVisibility(true);

In most dc.js charts, the data is read from group.all(). So the number of bars is simply
customerNameGroupr.all().length
and then multiply appropriately.
If the number of bars may change as stuff is filtered (using remove_empty_bins for example), then you'd want to set the width prior to each redraw:
chart.on('preRedraw', function(chart) {
chart.width(chart.group().all().length * BAR_WIDTH);
});
That might be messy though.

Related

How to create proportionally spaced horizontal grid lines?

I have a demo here
It's a simple D3 chart in an Angular app.
I would like to have four horizontal grid lines across the chart and have them proportionally space so a line at 25%, 50%, 75% and 100% the height of the chart.
I'm not concerned about the scale on the y-axis I just ned them proportionally space on the height on the chart.
I sort of have it working here but using some dodge math
const lines = chart.append('g')
.classed('lines-group', true);
lines.selectAll('line')
.data([0,1.33,2,4])
.enter()
.append('line')
.classed('hor-line', true)
.attr("y1", (d) => {
return height/d
})
.attr("y2", (d) => {
return height/d
})
.attr("x1", 0)
.attr("x2", width)
Is there a better way to do this or a proper D3 way to space the lines
Use your y scale. If you want to keep the data as percentages, all you need is:
lines.selectAll('line')
.data([25, 50, 75, 100])
.enter()
.append('line')
.attr("y1", (d) => {
return y(y.domain()[1] * (d / 100))
})
.attr("y2", (d) => {
return y(y.domain()[1] * (d / 100))
})
//etc...
As you can see we're just multiplying the maximum value in the y scale domain, which is y.domain()[1], by any value you want (in this case the percentage, represented by d / 100).
Here is the forked StackBlitz: https://stackblitz.com/edit/d3-start-above-zero-9b389s
You can customize a d3.axisRight to get this to work. Instead of adding the custom lines, try adding something like this after you add your axisBottom:
const maxVal = d3.max( graphData, (d) => d.value )
yAxis.call(
d3.axisRight(y).tickSize(width).tickValues([0.25*maxVal, 0.5*maxVal, 0.75*maxVal, maxVal])
).call(
g => g.select(".domain").remove()
).call(
g => g.selectAll(".tick text").remove()
)
Note that I am passing in the exact tick values you want using .tickValues, which allows this customization. See this post for more details on customization.

Margins on composite Chart

I've recently tried to add margins to composite charts and the charts are plotting to the left of the Y axis.
The charts are built dynamically. I tried more combinations of values for margins, but it didn't work. I want to add margin-left because some charts have big values on y axis.
Here is the esential code:
hist_margins = {top: 10, right: 50, bottom: 30, left: 40}
charts[i]
.compose([
dc.barChart(charts[i])
.dimension(dim)
.group(static_group)
.colors('#ccc')
.barPadding(0.1)
.controlsUseVisibility(true),
dc.barChart(charts[i])
.dimension(dim)
.colors('rgb(85, 160, 185)')
.group(group)
.barPadding(0.1)
.controlsUseVisibility(true)
.brushOn(false),
])
var min = dim.bottom(1)[0][it.variable],
max = dim.top(1)[0][it.variable];
charts[i]
.width(w_hist)
.height(h_hist)
.margins(hist_margins)
.dimension(dim)
.group(group)
.x(d3.scaleLinear().domain([min, max]))
.xAxisLabel(it.variable, 20)
.xUnits(dc.units.fp.precision(it.precision*1.5))
.brushOn(true)
.transitionDuration(0)
.renderTitle(true)
.title(function (d) {
return it.variable + ': ' + d.value
})
.controlsUseVisibility(true);
Any ideas?
As of dc.js 3.1.4, you would have to call .margins() before calling .compose() in order for the values to be reflected in the child charts.
However, thanks to a PR by Keith Dahlby, this limitation has been eliminated.
So, you can either upgrade to dc.js 3.1.5 (just released), or you can call .margins() first.

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)+')')

D3js linear color scale with custom values (ticks)?

Background concept: Elevation maps and their color keys for human readers require a more detailed coverage of lower elevation (<200m), where 80% of the world population lives.
D3 project:
Given such elevations threshold levels (m) as : 0, 50, 100, 200, 500, 1000, 2000, 3000, 4000, 5000m.
Based on a linear color scale
ticks every 20px (regular span)
color picking at specific thresholds: 0, 50, 100, 200, 500, 1000, 2000, 3000, 4000, 5000m
ticks with specific labels: 0, 50, 100, 200, 500, 1000, 2000, 3000, 4000, 5000m
Examples of such custom scale, scale.ordinal(), scale.log(), etc. welcome.
Comment: I currently goes with an unsatisfying linear ramp code, see this, which cut my scale into 10 equal spans of 500m :
I should actually have 4 differents greens standing for 0, 50, 100, and 200m and less browns/greys/whites.
Var color declaration. Set up the color ramp table by stating the points where I have color shifts :
// Color-values equivalence
var color_elev = d3.scale.linear()
.domain([0, 200, 2000, 5000]) // values elevation (m)
.range(["#ACD0A5", "#E1E4B5", "#AA8753", "#FFFFFF"]) // colors
.interpolate(d3.interpolateHcl)
each of these 3 spans should indeed have linear color changes.
Injection of my SVG polygons
// Data (getJSON: TopoJSON)
d3.json("data/topo/final.json", showData);
// ---------- FUNCTION ------------- //
function showData(error, fra) {
... // do my topojson to svg map injection
}
Create, push my Color ramp box and key
/* START LEGEND_RAMP */
// Color ramp
var x = d3.scale.linear()
.domain([0, 5000]) // legend elevation (m)
.range([0, 280]); // width (px)
// Color ramp place ? ? ?
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.tickSize(13)
.tickFormat(d3.format(".0f"));
// (JS shortcut)
var legend_key = svg.append("g")
.attr("class", "legend_key")
.attr("transform", "translate(" + (width - 300) + "," + (height - 30) + ")");
// Color ramp: white background
legend_key.append("rect")
.attr("x", -10)
.attr("y", -10)
.attr("width", 310)
.attr("height", 40)
.style("fill", "white")
.style("fill-opacity", 0.5)
// Color ramp: bricks
legend_key.selectAll(".color_ramp")
.data(d3.pairs(x.ticks(10))) // is this forcing a 10 equal sizes slicing ?
.enter().append("rect")
.attr("class", "elev_color_brick") // or "band"
.attr("height", 8)
.attr("x", function(d) { return x(d[0]); })
.attr("width", function(d) { return x(d[1]) - x(d[0]); })
.style("fill", function(d) { return color_elev(d[0]); });
// ?
legend_key.call(xAxis);
/* END LEGEND */
I'd comment this, but can't at present, have you looked at colorbrewer? Also, have you considered using an ordinal scale. You'd be able to control the colour ramp by mapping ranges to particular colours. If you're looking for a more automated way you could use an equal area distribution (histogram equalisation) method? Otherwise power or log scales could be an improvement over linear.

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