automatically set dimensions of d3/dimple data visualization for legibility - d3.js

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

Related

d3.js - Dendrogram display adjusted to the tree diagram

With d3.js I have created d3 dendrograms to visualize hierachicals relations between objects. Dimensions and margins of the graph are defined with fixed height and width values.
var width = 1000,
height = 800,
boxWidth = 150,
boxHeight = 35,
gap = {
width: 50,
height: 12
},
margin = {
top: 16,
right: 16,
bottom: 16,
left: 16
},
svg;
With a few relations, display is ok but with many relations it's doesn't fit, graph is 'cut' and I can't see the entire graph. How to set this width and height properties dynamically and adjusted to the size of the graph ?
An example with a correct display : Codepen
An example with an incorrect display : Codepen
Let's work this out, you need to know the bounding box of your content first and then adjust the svg size. To do that, in this particular case, you only have to look at the boxes or nodes and can ignore the links.
With that in mind you can do the following after populating the Nodes in your renderRelationshipGraph function and return the calculated value:
function renderRelationshipGraph(data) {
// ...
var bbox = Nodes.reduce(function (max, d)
{
var w = d.x + boxWidth;
var h = d.y + boxHeight;
if (w > max[0]) {max[0] = w}
if (h > max[1]) {max[1] = h}
return max
}, [0,0])
return bbox
}
then on the main code change use it to update height and width of the svg:
svg = d3.select("#tree").append("svg")
.attr("width", width)
.attr("height", height);
svg.append("g");
var bbox = renderRelationshipGraph(data);
svg.attr("width", bbox[0])
.attr("height", bbox[1]);
You can add a transition and limit the height but this does what you requested with a really large end result.

plotting tick text above the axis

I am using d3 v4 for ploting the graph. And currently the tick text on the x-axis is coming below the axis. and I want that text on above the axis.
//Set the Xaxis scale Range
let x = scaleLinear().rangeRound([0, width]);
let x_axis = axisBottom(x);
x.domain(extent(graphData, function (d) {
return d.weeks;
}));
g.append("g").attr("transform", "translate(0," + height + ")").call(axisBottom(x).ticks(5)).attr("transform", "translate(0, 120)");
so can you help me how to put the tick text above the x-axis.
If you want the ticks on top of the axis, you should use axisTop, instead of axisBottom.
The names are pretty easy to understand and the API is very clear:
d3.axisTop(scale): In this orientation, ticks are drawn above the horizontal domain path.
d3.axisBottom(scale): In this orientation, ticks are drawn below
the horizontal domain path. (emphases mine)
Here is a demo, the first axis uses axisTop, and the second one, below, uses axisBottom:
var svg = d3.select("svg");
var x = d3.scaleLinear().range([20, 280]);
var xAxisTop = d3.axisTop(x)(svg.append("g").attr("transform", "translate(0,50)"))
var xAxisBottom = d3.axisBottom(x)(svg.append("g").attr("transform", "translate(0,100)"))
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg></svg>

how to enforce some spacing between the elements of 'addCategoryAxis'

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

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));
}

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