how to enforce some spacing between the elements of 'addCategoryAxis' - d3.js

My goal is to make a bar chart that demonstrates the number of jobs advertised in particular locations.
I'm using this code, it draws on d3 and also dimple:
<script type="text/javascript">
function draw(data) {
/*
D3.js setup code
*/
"use strict";
var margin = 75,
width = 1400 - margin,
height = 600 - margin;
var svg = d3.select("body")
.append("svg")
.attr("width", width + margin)
.attr("height", height + margin)
.append('g')
.attr('class','chart');
/*
Dimple.js Chart construction code
*/
var myChart = new dimple.chart(svg, data);
myChart.addCategoryAxis("x", "Location");
myChart.addMeasureAxis("y", "Jobs");
myChart.addSeries(null, dimple.plot.bar);
myChart.draw();
};
</script>
It more or less works, but the thing is- the result is pretty useless because the X axis is so crowded that each individual location is essentially invisible.
Is there a way to enforce some reasonable amount of spacing there so that the different locations remain legible in such a way that it can withstand more records being added at a later date- so- with some kind of dynamism.

Ok, so- on the advice of #thisOneGuy I started playing around with increasing the width, and it worked.
At first I tried to increase the width too much and the chart just disappeared (if anyone knows why that happened I would be interested to hear about it in the comments perhaps)
from width = 1400 - margin, to width = 14000 - margin, it disappears
but width = 9000 - margin, was ok.
you can find the result here

Related

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

automatically set dimensions of d3/dimple data visualization for legibility

I'm trying to create a bar chart to visualize the number of Data-Science jerbs around the Federal Republic of Germany- right now it looks like this:
It's getting there, but it's not legible, which is a problem.
I want it to render in such a way that it will display the data in a comprehensible way no matter the size of the input file, i.e. it should be dynamic.
I know that data.length is the number of rows- but- what is meant by "rows", the number of lines in my input csv?
I've been recommended to use something like chart.setBounds(100, 100, data.length * k, 300) where k should be the height of the label + some margin. What is the best way to determine k?
I suppose that k is related to the y-axis and that really it is, or should be, just set by the maximum value of the inputs and that there isn't really much else I can do about that. Is it so?
I've been playing around with trying different values heuristically, i.e. trial and error- but that is clearly suboptimal.
What's a maintainable and effective way of always generating a map where the indices of the x-axis are all clearly readable and the y-axis is determined by the max value of inputs?
Here is the code I'm using to generate the bar chart.
<!DOCTYPE html>
<html>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://dimplejs.org/dist/dimple.v1.1.1.min.js"></script>
<script type="text/javascript">
function draw(data) {
"use strict";
var margin = 75,
width = 9000 - margin,
height = 600 - margin;
var svg = d3.select("body")
.append("svg")
.attr("width", width + margin)
.attr("height", height + margin)
.append('g')
.attr('class','chart');
var chart = new dimple.chart(svg,data);
chart.setBounds(100, 100, 500, 300);
var x = chart.addCategoryAxis("x", '"loc"');
var y = chart.addMeasureAxis("y", '"title"');
var lines = chart.addSeries(["project"], dimple.bar, [x, y]);
chart.draw();
};
</script>
<body>
<script type="text/javascript">
d3.csv("Germany.csv", draw);
</script>
</body>
</html>
(This is a link to the data file Germany.csv on my GitHub).
Firstly I am iterating over the data that you have provided to remove all double quotes in the key and value of the json like this.
datas = [];
data.forEach(function(d) {
var ob = {};
for (var key in d) {
var k = key.replace(/"/g, "").trim();//remove all double quotes and trim
var v = d[key].replace(/"/g, "").trim();//remove all double quotes and trim
ob[k]=v;
}
datas.push(ob)
})
Then I make the width of the svg based on the data length.
var width = data.length*5 -margin;//5 is a constant width of the text label font
if (width < 500)//set the minimum width in case data set is low.
width =600;
Set the width of svg an d chart like this
var svg = d3.select("body")
.append("svg")
.attr("width", width + margin)//set the calcuated width to svg
//set width to the chart object
chart.setBounds(100, 100, width -margin, 300);
working code 110 points here
working code with all points here

How can I get a time axis onto an object, using D3?

I'm very new to d3 and trying to learn by building a visualization.
My goal right now is to make a circle and color the circle based on some temporal data. I've made the circle, and want to add a timescale to it. The circle I have created fine using d3.arc() on an svg element. I have also created a time scale (seen below). My question is, how can I "attach" this time scale to the circle? I want to be able to say that at xyz point in time, my data holds this value, so now color the circle based on a color scale.
Or...am I going about this wrong?
var time = d3.scale.ordinal()
.domain(d3.extent(data, function(d) {
return d.date;
}))
I think you may need to use a quantitative scale instead of ordinal.
https://github.com/mbostock/d3/wiki/Ordinal-Scales says -
Ordinal scales have a discrete domain, such as a set of names or categories
and in your code, you use the "extent" of the date property, which only gives you 2 values - the earliest and most recent date in your data. That is a discrete domain, but a very limited one, and wouldn't represent your data very well. The scale will only output at most 2 values.
var now = Date.now();
var then = now - 1000;
var colors = d3.scale.ordinal()
.domain([then, now])
.range(['#ff0000','#0000ff']);
colors(then); // red
colors(now); // blue
colors(now - 500); // red... expecting violet
change 'ordinal' to 'linear' and leave the rest as is.
var now = Date.now();
var then = now - 1000;
var colors = d3.scale.linear()
.domain([then, now])
.range(['#ff0000','#0000ff']);
colors(then); // red
colors(now); // blue
colors(now - 500); // violet
The tricky part (at least for me) was remembering that the output of d3.scale.linear() (the 'colors' variable above) is a function. It can be called just like any other function.
var fakeData = d3.range(then, now, 10);
var svg = d3.select('body')
.append('svg')
.attr({ height: 500, width: 500 });
var circle = svg.append('circle')
.attr({ r: 100, cx: 250, cy: 250 });
function changeTime(time){
circle.attr('fill', colors(time));
}

Combining translate and rotate with D3

Quite possibly this repeats some of this SO question, but the code is overly-complicated and the OP hasn't added solution code. And this related question is no longer replicable.
I'm trying to figure out how to combine rotations and translations in the right order. It's possible to rotate around the origin as in this example. But when we follow this with a translation the original rotation is undone.
Is it possible to structure this for correct sequential application?
jsfidle code:
HTML:
<script src="http://d3.geotheory.co.uk/d3-transform.js"></script>
SVG:
var svg = d3.select("body").append("svg")
.attr("width", 400)
.attr("height", 300);
//Draw the Rectangle
var rect = svg.append("rect")
.attr("x", 0).attr("y", 0)
.attr("width", 50).attr("height", 100)
.style("fill", "purple");
var rotate = d3.svg.transform().rotate(-45);
var translate = d3.svg.transform().translate(200, 100);
rect.attr('transform', rotate);
var rect2 = rect.attr('transform', rotate);
rect2.attr('transform', translate);
If you're looking to do this in D3 version 4.x+ you can do it like so:
.attr('transform', 'translate(200,100)rotate(-45)')
https://bl.ocks.org/mbostock/1345853
You're creating two different transformations. Assigning one doesn't add to the other. That is, in doing
rect2.attr('transform', translate);
you're undoing the first one, as it is overwritten.
To have both, add them both to one transition, e.g.
var rotateTranslate = d3.svg.transform().rotate(-45).translate(200, 100);
rect2.attr('transform', rotateTranslate);
To do this dynamically, you'll need to do something like this.
.attr("transform", function() {
return d3.svg.transform()
.translate(200, 100)
.rotate(-45)
.translate(-d3.select(this).attr("width")/2, -d3.select(this).attr("height")/2)();
}
Complete jsfiddle here.

d3 pie chart transition with attrtween

i'm trying to somehow sweep in a half-donut-chart, meaning starting with a blank screen the chart starts drawing at -90 degree (or 270) and performs a halfcircle until reaching 90 degree. the code looks like:
var width = 800;
var height = 400;
var radius = 300;
var grad=Math.PI/180;
var data = [30, 14, 4, 4, 5];
var color = d3.scale.category20();
var svg = d3.select("body").append("svg").attr("width", width).attr("height",
`height).append("g").attr("transform", "translate(" + radius*1.5 + "," + radius*1.5 +
")");
var arc = d3.svg.arc().innerRadius(radius - 100).outerRadius(radius - 20);
var pie = d3.layout.pie().sort(null);
svg.selectAll("path").data(pie(data)).enter().append("path").attr("d",
arc).attr("fill",
function(d, i) { return color(i); }).transition().duration(500).attrTween("d", sweep);
function sweep(a) {
var i = d3.interpolate({startAngle: -90*grad, endAngle: -90*grad},{startAngle: -90*grad, endAngle: 90*grad});
return function(t) {
return arc(i(t));
};
}
looking at several examples i managed to get the animation, however, i fail at binding (or converting) the data to the arc. my feeling is that there is only one path drawn and then it stops.
if i change the interpolation to start/end -90/90 and a, i get different colors but not all of them. adding the start/end-angle to the pie-var gives me a transition where a one-colored-arc is shown at the beginning and then the other parts slide in (which would be correct if there was no arc at the beginning - the proportions also seem a bit wrong). setting the initial color to white does not help because then everything stays white.
i'm afraid i'm missing an obvious point, but so far i'm stuck, maybe someone can point me in the right direction.
after quite some variations and tests it somehow started to work, using these to lines of code:
var pie = d3.layout.pie().sort(null).startAngle(-90*grad).endAngle(90*grad);
var i = d3.interpolate({startAngle: -90*grad, endAngle: -90*grad},a);
one final "problem" was that the height of the svg was too small and so some segments got cut off, so changing it to
var height = 800;
ended my search. thanks for any considerations.
A small typo on the
var svg = d3.select("body").append("svg").attr("width", width).attr("height", `height)
should be:
var svg = d3.select("body").append("svg").attr("width", width).attr("height", height)

Resources