After using data().enter().append() in D3.js, individual columns/values in a dataset can be retrieved by simply using d.valuename. But I want to find the maximum value in a CSV column for a linear scale. Since the scale is not preceeded by any data calls, I'm really uncertain on how to specify the right column from which to find the maximum value.
This is my failed attempt. What do I substitute d.column1 with?
d3.csv("file.csv", function (data) {
var x = d3.scale.linear()
.domain([0, d3.max(d.column1)])
.range([0, 960]);
EDIT:
Ok, I got a bit further by looking at a similar example. I don't understand why my code is not working now.
d3.csv("file.csv", function (data) {
data.forEach(function(d) {
d.column1 = +d.column1;
});
var max = d3.max(data, function(d) { return d.column1; });
var x = d3.scale.linear()
.domain([0, max])
.range([0, 960]);
EDIT #2:
In case the problem derives from the data itself, here's a snippet. I have tried removing all quotes, and also keeping the quotes for everything but the top row, but nothing works.
"product", "price"
"bread",20
"meat",100
"meat, sausage",200
EDIT #3:
Ok, I promise, last edit. But just to be absolutely clear; I'm using the x-scale to determine the width of the bars in a bar chart: .attr("width", x) and the scale returns NaN.
To explain why your code is working:
data.forEach(function(d) {
d.column1 = +d.column1;
});
This above statement turns all your column1 values into an integer (the + sign in front of d.column1 coerces the type to a Number).
Once your values are all numbers, d3 can now compare them as numbers instead of strings. This way 10 will be larger than 9. If they were strings, "9" would be larger than "10".
var max = d3.max(data, function(d) { return d.column1; });
The above line simply takes the max of your data array. Since your data array is made up of objects, d3 has no way of knowing how to compare one object to another. The second parameter is a function that gives d3 a value to each object so that d3 can compare each object. In your case you are telling d3 to use column1 to determine the max value. This is similar to a comparator function.
I just realized what the problem was. No wonder we couldn't figure this one out together. All the code above is fine. It's just that when I called the x-scale to determine the width of my bars, I didn't specify which column in the data would be input to the scale. I had
.attr("width", x)
But now I changed that row to:
.attr("width", function(d) { return x(d.column1); })
Thanks for your help and patience.
Related
I'm working on a heatmap which basically plots the graph between taxIDs and KeywordNames from an external JSON. While I'm able to plot the values I see many blank spaces on the graph and clueless how I can plot them with the available data.
Here's the link to codesandbox: https://codesandbox.io/s/40mnzk9xv4
On the X-Axis I'm plotting the TaxIDs which are being calculated within the given range. I did try using the function rangeBands() but I get an error everytime.
Its the similar case with Y-Axis where I'am plotting the keywordIDs which are also being calculated within a range. I'm trying to print all the KeywordNames on Y axis and all taxIDs on the X-Axis and plot their corresponding spectracount on graph.
Please help me where have I gone wrong.
The output I'm looking for is something similar to this: https://bl.ocks.org/Bl3f/cdb5ad854b376765fa99
Thank you.
Some things to help you get you one your way:
First, your scales should use scaleBand(), not scaleLinear(), as they have discrete domains (i.e. categories of something, rather than continuous)
Second, your scale domains is taking every value of taxId and keywordName in your data as a possible value. But some values are repeated more than once. You need to be filtering them so you only have unique values. So your scale code should be:
const xValues = d3.set(data.map(d => d.taxId)).values();
const yValues = d3.set(data.map(d => d.keywordName)).values();
const xScale = d3.scaleBand()
.range([0, width])
.domain(xValues); //X-Axis
const yScale = d3.scaleBand()
.range([0, height])
.domain(yValues); //Y-Axis
Finally, your code that places the heatmap tiles needs to be calling the scale functions so it works out the position of each rect correctly:
chart.selectAll('g')
.data(data).enter().append('g')
.append('rect')
.attr('x', d => { return xScale(d.taxId) })
.attr('y', d => { return yScale(d.keywordName) })
That should get you most of the way there. You'll want to also reduce cellSize and remove the tickFormat calls on the axes as they are trying to convert your text labels to numbers.
I'm trying to recreate the following in D3
And I've got the following so far: http://codepen.io/jpezninjo/pen/dNwmVK
I looked it up and the best I could find was this answer: Show every other tick label on d3 time axis?, but I'm not using a class to create each column label. I think the following two lines are the ones that control my labels, but I'm not sure how to go about this.
var x = d3.scaleBand()
.range([0, width])
.padding(0.1);
x.domain(data.map(function(d) { return d.key; }));
I'm also trying to figure out how I can put some padding on the left and right of the bars
At least two possible ways:
Make your X axis a time axis, and use d3.timeDay.every(2) to specify every 2nd day. That approach is shown here: http://codepen.io/anon/pen/YNdaRB.
Key part: var axisBottom = d3.axisBottom(x).ticks(d3.timeDay).tickArguments([d3.timeDay.every(2)]);.
To make this work, I also had to (a) make d.key equal to the result from parseDate instead of the formatted date string, (b) hard-code a width for the bars instead of using x.bandwidth(), and (c) translate the x axis by width/2 px to center it under the bars (line 94). Might be nicer ways to do (b) and (c) but I mainly wanted to show d3.timeDay.every(2)'s ability (docs).
Use your current approach but apply a style to every 2nd tick. d3 adds some classes automatically so selecting the ticks is easy. This is what they described in the post you linked to. http://codepen.io/anon/pen/qRLogy?editors=1010
Key part: d3.selectAll(".tick text").style("display", function (d, i) { return i % 2 ? "none" : "initial" })
I can't wrap my head around manipulating the data object in d3js. I'm planning to create a chart composed of horizontal bars to hold data elements. Each data element is a circle. I figured out how to insert circles into the different bars, but I'm stuck on how to equally space the circles in each bar. For example, if the width is 800 and there are 8 circles, the x attribute should be 100*i.
Here's a link to my project: https://plnkr.co/edit/fHrdJsItEqA5qc35iUxG?p=preview
I think the problem is how to reference the data object in this block of code. Anyways, I would like to equally space the circles using scaleBand which I defined as variable x earlier in my code:
var x = d3.scaleBand()
.range([0,width]);
I think the solution would look something like this: .attr("x",x.domain(data.map(function(d,i){return d[i]})); x.bandwidth(), but obviously data is not the right object.
Selecting each bar and inserting circles:
bar.selectAll("rect")
.data(function(d,i){console.log(data_group[i].values.length);return data_group[i].values})
.enter().append("circle")
.attr("class","circle")
.attr("width", width)
//.attr("x",) //how to equally space these circle elements???
.attr("height",20)
.attr("y", y.bandwidth())
console.log(y.bandwidth());
As always, I would really appreciate your help.
There are a number of issues with your code that are preventing it from working, including:
You aren't setting a domain for your x scale.
You are attempting to place <circle>s inside of <rect>s but you cannot nest shapes in SVGs. You should place both inside of a <g>.
A <circle>'s position is set using the cx and cy attributes (and you also need to provide it an r radius attribute).
To address your question, you will need to determine how you want your items laid out. Because you are referencing the index in your question, I will use that.
You are breaking your data into nested groups where each one has a values array. You are rendering a <circle> for each datum in that array, so you will want to determine the length of the longest values array.
var longest = data_group.reduce(function(acc, curr) {
return curr.values.length > acc ? curr.values.length : acc;
}, -Infinity);
Once you have the length of the longest values array, you can set the domain for your x scale.
You are using d3.scaleBand (d3.scalePoint would probably work better here), which is an ordinal scale. Ordinal scales work on discrete domains, which means that you will need to have a domain value for each possible input (the indices). For this, you will need to generate an array of the possible indices from 0 to longest-1.
var domainValues = d3.range(longest);
Now that you have the input domain values, you can set them for the x scale.
x.domain(domainValues);
Then, for each <circle>, you will set its cx value using the index of the circle in its group and the x scale.
.attr('cx', function(d,i) { return x(i); })
As I mentioned in the beginning, there are other errors in your code, so just fixing this won't get it running correctly, but it should push you in the right direction.
Good afternoon.
I am using this D3 bar chart example with my data (that is in the same format whereof in the
jsfiddle example).
The chart works well but i have some problems that i do not know i solve.
The xAxis have the number of index of the key parameter and not the key parameters values;
When I sort the data after pressing the "sort" button;
The numbers over the bars after the sort disappear or appear the the left side insted of over the correspondent bars
var dataset = {key: [0, 30], value:[60, 30]};
http://jsfiddle.net/3HL4a/75/
Here is the link where you can see the code and perform the changes.
Thanks
To reference the key values, you have to use those instead of the index:
var xScale = d3.scale.ordinal()
.domain(dataset.key)
.rangeRoundBands([0, w], 0.05);
// ...
.attr("x", function(d, i) {
return xScale(dataset.key[i]);
})
You can make this nicer by merging the key and value arrays such that both are part of the same data element.
As for the other problem, this is because you're selecting text elements, which include the axis labels. Even with the key function this doesn't work here, as one of the axis labels is the same as the label you want to display on the bar. Assigning a special class to these labels and selecting accordingly fixes this.
See here for the complete jsfiddle.
Im relatively new to d3 and nvd3 and wanted to create a simple scatterplot, just like the example but with an ordinal y-axis. So y axis values are categorical strings. This is what I thought I needed to do:
var xfun = function (d) { return d.Pos } //simple ints
, yfun = function (d) { return d.Title } //the ordinal values
var chart = nv.models.scatterChart()
.showDistX(true)
.showDistY(true)
.color(d3.scale.category10().range())
.margin({ top: 30, right: 20, bottom: 50, left: 130 })
.tooltips(false)
.x(xfun)
.y(yfun);
// create an ordinal scale with some test values
var ys = d3.scale.ordinal()
.domain(["this","is","an","ordinal","scale"])
.range(5);
// tell nvd3 to use it
chart.yAxis.scale(ys);
// add to the page
nv.addGraph(function () {
d3.select(selector).datum(data).transition().duration(500).call(chart);
nv.utils.windowResize(chart.update);
return chart;
});
However, no luck:
Error: Invalid value for <circle> attribute cy="NaN" d3.js:690
Error: Invalid value for <line> attribute y1="NaN" d3.js:690
Error: Invalid value for <line> attribute y2="NaN" d3.js:690
Error: Invalid value for <circle> attribute cy="NaN" d3.js:7532
Uncaught TypeError: Cannot read property '0' of undefined
..
And the y-axis simply shows a linear axis from -1 to 1. Iterestingly there are some circles plotted at y=-1 and y=1 (the extremes).
To manually force correct values for cy I tried adding after call(chart):
d3.selectAll("#mychart circle").attr("cy", function(d){
return = ys(yfun(d));
});
But still the same error. How do I get the ordinal scale to work properly? Note I also need it to update correctly when I use the nvd3 legend to switch between dataseries (which will contain different x/y data).
There is a related question on github, but no solution.
Update: after some debugging I tried replacing chart.yAxis.scale(ys) with chart.scatter.y(ys) and this gets rid of the errors. I can also drop the manual selectAll.
However, the y-axis still shows a linear scale from 0.99-1.01 and all points are plotted at y=1. So a step closer but no ordinal scale yet.
In case somebody else stumbles upon this, here's what worked for me: instead of trying to force an ordinal scale on the axis (X, in my case), I used a linear scale, but provided a custom tickFormat function that returned the desired label.
chart.xAxis.tickFormat(function(d){
return labelValues[d];
});
Where labelValues maps between the numerical value and the desired label.
There is an easy solution by #robinfhu.
Don't use an ordinal scale!
Instead, set your x function to return the index of the element:
chart.x(function(d,i){ return i;})
And set your tickFormat function to read the proper label:
tickFormat(function(d) {return that.data[0].values[d].x;})
Working example: Plunker
Copy and paste:
nv.models.lineChart()
.x(function(d, i) {
return i;
}).xAxis
.tickFormat(function(d) {
return that.data[0].values[d].x;
});
I haven't used nvd3.js, but from my d3 experience, the error seems to be the range on your scale. For an ordinal scale, you need to define an array of corresponding values for the domain to map to. For example, I could set the following scale:
var myScale = d3.scale.ordinal()
.domain(['this','is','an','ordinal','scale'])
.range([1,2,3,4,5])
This scale would map my domain to numbers in the range 1:5, with 'this' -> 1, 'is' -> 2, etc. If I didn't want to set each point individually, I could also define my range with rangePoints, as follows:
var myScale = d3.scale.ordinal()
.domain(['this','is','an','ordinal','scale'])
.rangePoints([1,5])
rangePoints gives me an evenly spaced range of points from the minimum specified value to the max specified value, so this would result in the same range being generated.
I've made some circles that illustrate how to do this here.
When you change to a different data series, you'll have to update your domain. Since range corresponds to where on the page your points are mapping to, you won't need to update it, unless nvd3.js applies a secondary mapping and does ordinalDomain -> integers -> pointLocation in two steps.
Another option is to apply the scale in the function definition.
yfun = function (d) { return ys(d.Title) }