After a while I finally figured how to scale data in d3 linear.
But if you look at the following screenshot you might notice that numbers do not seem to scale appropriately:
So: e.g. 4.55 and 16.2 are almost same length, which they shouldn't be (also 4 should be much closer to the left as I scale between 0 and 565)
so this is how I create the linear scale:
var scaledData = d3.scale.linear()
.domain(d3.extent(data, function(d){return d.Betrag;}))
.range([0, width-35]);
and this is how I draw the bars:
var bar = chart.selectAll("g")
.data(data)
.enter().append("g")
.attr("transform", function(d, i) { return "translate(0," + i * barHeight + ")"; });
bar.append("rect")
.attr("width", function(d) { return scaledData(Math.abs(Math.round(d.Betrag))); })
.attr("height", barHeight - 1);
bar.append("text")
.attr("x",function(d) { return scaledData(Math.abs(Math.round(d.Betrag)))+3; })
.attr("y", barHeight / 2)
.attr("dy", ".35em")
.text(function(d) { return Math.abs(d.Betrag); });
});
I have tried around different things with this part scaledData(Math.abs(Math.round(d.Betrag))) but no matter what I do it doesn't do what I think it should... Perhaps as a newbie I still don't understand the scales thing completely... What I figure is that the problem comes from how I set the domain. if I replace the d3.extend function by [0,2000] it's all good...
You'll have to show your data to know for sure, but I think you have negative values in your data, so your domain looks like it goes from about [-600, 1800]. But when you calculate your width you first take the absolute value of your data, so your lowest possible value is 0. The solution is in your d3.extent accessor function, to evaluate the absolute value of your data (if that's actually what you want).
Related
I have a map already drawed. I would like to add a legend using d3.js. For example when filering by length, the map should show differents colors. Since a week, I couldn't achieve this task. My map color seem to be good but the legend does not match.
Could anybody help me with my draw link function ?
https://jsfiddle.net/aba2s/xbn9euh0/12/)
I think it's the error is about the legend function.
Here is the function that change my map color Roads.eachLayer(function (layer) {layer.setStyle({fillColor: colorscale(layer.feature.properties.length)})});
function drawLinkLegend(dataset, colorscale, min, max) {
// Show label
linkLabel.style.display = 'block'
var legendWidth = 100
legendMargin = 10
legendLength = document.getElementById('legend-links-container').offsetHeight - 2*legendMargin
legendIntervals = Object.keys(colorscale).length
legendScale = legendLength/legendIntervals
// Add legend
var legendSvg = d3.select('#legend-links-svg')
.append('g')
.attr("id", "linkLegendSvg");
var bars = legendSvg.selectAll(".bars")
//.data(d3.range(legendIntervals), function(d) { return d})
.data(dataset)
.enter().append("rect")
.attr("class", "bars")
.attr("x", 0)
.attr("y", function(d, i) { return legendMargin + legendScale * (legendIntervals - i-1); })
.attr("height", legendScale)
.attr("width", legendWidth-50)
.style("fill", function(d) { return colorscale(d) })
// create a scale and axis for the legend
var legendAxis = d3.scaleLinear()
.domain([min, max])
.range([legendLength, 0]);
legendSvg.append("g")
.attr("class", "legend axis")
.attr("transform", "translate(" + (legendWidth - 50) + ", " + legendMargin + ")")
.call(d3.axisRight().scale(legendAxis).ticks(10))
}
D3 expects your data array to represent the elements you are creating. It appears you are passing an array of all your features: but you want your scale to represent intervals. It looks like you have attempted this approach, but you haven't quite got it.
We want to access the minimum and maximum values that will be provided to the scale. To do so we can use scale.domain() which returns an array containing the extent of the domain, the min and max values.
We can then create a dataset that contains values between (and including) these two endpoints.
Lastly, we can calculate their required height based on how high the visual scale is supposed to be by dividing the height of the visual scale by the number of values/intervals.
Then we can supply this information to the enter/update/exit cycle. The enter/update/exit cycle expects one item in the data array for every element in the selection - hence why need to create a new dataset.
Something like the following shold work:
var dif = colorscale.domain()[1] - colorscale.domain()[0];
var intervals = d3.range(20).map(function(d,i) {
return dif * i / 20 + colorscale.domain()[0]
})
intervals.push(colorscale.domain()[1]);
var intervalHeight = legendLength / intervals.length;
var bars = legendSvg.selectAll(".bars")
.data(intervals)
.enter().append("rect")
.attr("class", "bars")
.attr("x", 0)
.attr("y", function(d, i) { return Math.round((intervals.length - 1 - i) * intervalHeight) + legendMargin; })
.attr("height", intervalHeight)
.attr("width", legendWidth-50)
.style("fill", function(d, i) { return colorscale(d) })
In troubleshooting your existing code, you can see you have too many elements in the DOM when representing the scale. Also, Object.keys(colorscale).length won't produce information useful for generating intervals - the keys of the scale are not dependent on the data.
eg
I am entering d3.js at ground level. There's a steep learning curve ahead of me, I know, but I didn't quite expect to get stuck on the second simple tutorial I tried. Now's the change to make the maximal marginal contribution to a fellow programmer's understanding!
I'm trying to get a simple svg bar chart to work using my data, which is virtually identical to the sample.
Bostock's data
name value
Locke 4
Reyes 8
Ford 15
Jarrah 16
Shephard 23
Kwon 42
My data
year value
year2013 2476
year2014 7215
year2015 23633
year2016thru229 21752
Note that our second columns have the same name. Both contain numbers. I gave my dataset the same name he did. Thus I should be able to run his code (below) without changing a thing, but it returns this:
Unexpected value NaN parsing width attribute.
...uments);null==e?this.removeAttribute(n):this.setAttribute(n,e)}function
d3.min.js (line 1, col 2575)
Unexpected value NaN parsing x attribute.
...uments);null==e?this.removeAttribute(n):this.setAttribute(n,e)}function
d3.min.js (line 1, col 2575)
His code as well as the tutorial are here.
You're not feeding D3 your data in the correct format.
https://jsfiddle.net/guanzo/kdcLj5jj/1/
var data = [
{year:'year2013',value:2476},
{year:'year2014',value:7215},
{year:'year2015',value:23633},
{year:'year2016thru229',value:21752}
]
var width = 420,
barHeight = 20;
var x = d3.scale.linear()
.range([0, width]);
var chart = d3.select(".chart")
.attr("width", width);
x.domain([0, d3.max(data, function(d) { return d.value; })]);
chart.attr("height", barHeight * data.length);
var bar = chart.selectAll("g")
.data(data)
.enter().append("g")
.attr("transform", function(d, i) { return "translate(0," + i * barHeight + ")"; });
bar.append("rect")
.attr("width", function(d) { return x(d.value); })
.attr("height", barHeight - 1);
bar.append("text")
.attr("x", function(d) { return x(d.value) - 3; })
.attr("y", barHeight / 2)
.attr("dy", ".35em")
.text(function(d) { return d.value; });
function type(d) {
d.value = +d.value; // coerce to number
return d;
}
I'm new to D3.js and using following example from D3.js to create a simple dashboard for one of my web application.
http://bl.ocks.org/NPashaP/96447623ef4d342ee09b
My requirement is to rotate top value labels of each bar vertically by 90 degrees.
I changed following method by adding "transform" attribute. Then the labels do not align properly.
//Create the frequency labels above the rectangles.
bars.append("text").text(function(d){ return d3.format(",")(d[1])})
.attr("x", function(d) { return x(d[0])+x.rangeBand()/2; })
.attr("y", function(d) { return y(d[1])-5; })
.attr("text-anchor", "middle")
.attr("transform", function(d) { return "rotate(-90)" });
I tried to find a solution for long time but couldn't. Links to my codes are given below.
https://jsfiddle.net/vajee555/7udmyj1k/
Can anybody please give me an idea how to archive this?
Thanks!
EDIT:
I have solved the problem here.
http://jsfiddle.net/vajee555/7udmyj1k/5/
Remember that when you rotate an element, the x and y coordinates are changed: they are no longer with respect to that of the chart, but with respect to the new rotated orientation of the element. Therefore, you will need to compute the x and y attributes differently.
By rotating -90deg, your x axis will be flipped to y, and the y will be flipped to -x:
I have made some small pixel adjustments to make it appear aesthetically pleasing, such as the +8 I have added to the y coordinate and the +5 I have added to the x coordinate, but the fine tuning is up to you.
// Create the frequency labels above the rectangles.
bars.append("text").text(function(d){ return d3.format(",")(d[1])})
.attr('transform', 'rotate(-90)')
.attr("y", function(d) { return x(d[0]) + x.rangeBand()/2 + 4; })
.attr("x", function(d) { return -y(d[1]) + 5; });
Also, change how the coordinates are calculated in the hG.update() function:
// transition the frequency labels location and change value.
bars.select("text").transition().duration(500)
.text(function(d){ return d3.format(",")(d[1])})
.attr("x", function(d) { return -y(d[1]) + 5; });
See working fiddle here: https://jsfiddle.net/teddyrised/7udmyj1k/2/
//Create the frequency labels above the rectangles.
bars.append("text").text(function(d){ return d3.format(",")(d[1])})
.attr("x", function(d) { return x(d[0])+x.rangeBand()/2; })
.attr("y", function(d) { return y(d[1])-5; })
.attr("text-anchor", "middle")
.attr("transform", "rotate(-90,0,0)" );
Change the last line as above.
I am still pretty new to d3.js and I am diving into a wordcloud example using the
d3-cloud repo : https://github.com/jasondavies/d3-cloud
The example which is in there works for me, I turned it into a function so I can call it when data updates:
wordCloud : function(parameters,elementid){
var p = JSON.parse(JSON.stringify(parameters));
var fill = d3.scale.category20();
if (d3.select(elementid).selectAll("svg")[0][0] == undefined){
var svg = d3.select(elementid).append("svg")
.attr("width", 500)
.attr("height", 500)
.append("g")
.attr("transform", "translate(300,300)");
}else var svg = d3.select(elementid).selectAll("svg")
.attr("width", 500)
.attr("height", 500)
.select("g")
.attr("transform", "translate(300,300)");
d3.layout.cloud().size([300, 300])
.words(p.data)
.padding(5)
.rotate(function(d) {return ~~(Math.random()) * p.cloud.maxrotation; })
.font("Impact")
.fontSize(function(d) { return d.size; })
.on("end", draw)
.start();
function draw(words) {
console.log(words)
console.log(words.length)
svg.selectAll("text")
.data(words)
.enter().append("text")
.style("font-size", function(d) {return d.size + "px"; })
.style("font-family", "Impact")
.style("fill", function(d, i) { return fill(i); })
.attr("text-anchor", "middle")
.text(function(d) {console.log("enter text " + d.text) ; return d.text; });
svg.selectAll("text")
.data(words).transition().duration(2000).attr("transform", function(d) {
return "translate(" + [d.x, d.y] + ")rotate(" + Math.random() * p.cloud.maxrotation + ")";
})
}
}
The code works for me.
element id = the elements you bind to
parameters = all parameters which i should be able to set, including data (parameters.data).
Except for the packaging the code wasn't altered much from the original:
https://github.com/jasondavies/d3-cloud/blob/master/examples/simple.html
However when I add a new word to the wordcloud (so when I update the data), the new word is not recognized. I have put log output on several places and apparently in the draw function the data is incorrect but before it is ok.
for example:
original: [{"text":"this","size":5},{"text":"is","size":10},{"text":"a","size":50},{"text":"sentence","size":15}]
(the code adds other properties but this is for simplicity of explanation)
I add: "testing" with a size of 5
correct would be
[{"text":"this","size":5},{"text":"is","size":10},{"text":"a","size":50},{"text":"sentence","size":15},{"text":"testing","size":5}]
but I get results like:
[{"text":"a","size":50},{"text":"testing","size":5},{"text":"this","size":5},{"text":"sentence","size":15}]
--> new word added , an older one removed (don't know why) and array was mixed up.
QUESTION:
Anybody have an idea what I am doing wrong?
or
Does anybody have a working example of a d3.js wordcloud which you can update with new words by means of lets say an input box?
I think u got the same problem with me. The size of all the words do not fit in your svg and d3.layout.cloud somehow remove the oversized word. Try to increase the width and height of your svg or decrease the size of your word. What I done is check whether the x,y,width and height of the word is it out of the box. If yes, decrease the size or increase the width and height. Correct me if i am wrong
I am working with a bar chart that was based off of this, and I want to modify the x-axis labels (either rotate them or only show every other or every third...something that will allow me to fit labels without their overlapping). I've spent quite a bit of time looking into this, but it seems easier said than done (although I am pretty new to d3). I believe the code segments that are in question are among these:
var x = d3.scale.ordinal()
.rangeRoundBands([0, width], .1);
...
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
...
x.domain(data.map(function(d) { return d.State; }));
...above is where the mapping happens (I assume based on the 'map' function)
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
...
var state = svg.selectAll(".state")
.data(data)
.enter().append("g")
.attr("class", "g")
.attr("transform", function(d) { return "translate(" + x(d.State) + ",0)"; });
...
state.selectAll("rect")
.data(function(d) { return d.ages; })
.enter().append("rect")
.attr("width", x.rangeBand())
.attr("y", function(d) { return y(d.y1); })
.attr("height", function(d) { return y(d.y0) - y(d.y1); })
.style("fill", function(d) { return color(d.name); });
The problem is that I don't necessarily want an x-axis label for every bar in the chart (it gets too cluttered). I have already tried several different ways of using
axis.ticks
axis.tickValues
axis.tickSubdivide
axis.tickSize
but I think they fail because d.State is married to its respective column (my graph uses dates instead of the name of a state)...any insight?
Sorry for the messy question. I welcome edits by those who understand what I'm asking to make my question more clear (I'm not sure of the best way to ask it).
The line
x.domain(data.map(function(d) { return d.State; }));
Is setting the domain for the scale that the axis uses. Because the scale is ordinal the axis defaults to using all the values for ticks. You should be able to set the actual tick values using the axis.tickValues function you mention above. You will just have to set it after the call that sets the scale domain because I suspect setting the domain resets the tickValues. Simply add something like:
xAxis.tickValues(data.map( function(d,i)
{
if(i % 2 ===0 ) return d.State;
})
.filter(function (d)
{ return !!d; } ));
Here I am mapping the data data to a single array of selected State values interspersed with "undefined" and then filtering out all the undefineds. The remaining State values become the ticks.
If you are using underscore you can probably make the line a lot prettier.
You can see the working solution here.