d3 linear scale with strange output values - d3.js

Recently I have began exploring D3 and I'm having some issues with scales.
I'm in a earlier stages of a simple bar chart and my yScale is outputting some stranges values.
I've notice that this doesn't happen if I simple define the domain like .domain([0, 8000]) which is not very dynamic…
Here's the link for the csv file:
Google Transparency Report: User data requests
And here's the code:
var dataset;
var w = 500;
var h = 300;
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
d3.csv("google-user-data-requests.csv", function(data) {
dataset = data;
generateVis();
});
var generateVis = function () {
var barValue = function(d) {
return d["User Data Requests"];
};
var xScale = d3.scale.ordinal()
.domain(d3.range(dataset.length))
.rangeRoundBands([0, w], 0.05);
var yScale = d3.scale.linear()
.domain([0, d3.max(dataset, barValue)])
.range([0, h]);
var bars = svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
var barsAttr = bars
.attr("x", function(d, i) {
return xScale(i);
})
.attr("y", function(d) {
return h - yScale(barValue(d));
})
.attr("width", xScale.rangeBand())
.attr("height", function(d) {
return yScale(barValue(d));
});
};
What am I missing?

What you need to do is convert your strings to numbers, i.e.
d3.csv("google-user-data-requests.csv", function(data) {
dataset = data;
dataset.forEach(function(d) {
d['User Data Requests'] = +d['User Data Requests'];
});
generateVis();
});
You probably also want to parse the dates and format them as such.

Related

D3 - Using JSON data to create multiple bars

I'm learning D3 and have JSON data. I want to build multiple bars from this JSON data to draw graph like this already built in excel. I can draw one line of Pax_Rev on SVG but I'm not sure how to add other lines from the data. When I do console.log(dataset.length), it shows me 0 which means only one item in dataset which is expected.
<script>
var dataset = [{"Pax_Rev": 1000, "Crg_Rev": 500,
"Fixed_Costs": 800, "Variable_Costs": 200}];
var width = 500;
var height = 1000;
var barPadding = 1;
var svg = d3.select("body")
.append("svg")
.append("g")
.attr("width", width)
.attr("height", height)
.attr("class", "svg")
svg3.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", 0)
.attr("y", function(d){
return height - d.Pax_Rev // How to add other items like Crg_Rev etc?
})
.attr("width", 20)
.attr("height", function(d){
return d.Pax_Rev
});
</script>
As I explained in your previous question, this is the expected behaviour. Since you have just one object in your data array, D3 "enter" selection will create just one element.
If you look at the API, you'll see that selection.data():
Joins the specified array of data with the selected elements[...] The specified data is an array of arbitrary values (e.g., numbers or objects). (emphases mine)
Therefore, we have to convert that huge object in several objects. This is one of several possible approaches:
var dataset = [{
"Pax_Rev": 1000,
"Crg_Rev": 500,
"Fixed_Costs": 800,
"Variable_Costs": 200
}];
var data = [];
for (var key in dataset[0]) {
data.push({
category: key,
value: dataset[0][key]
})
}
console.log(data)
Now, we have a data array, with several objects, one for each bar, and we can create our bar chart.
Here is a demo:
var dataset = [{
"Pax_Rev": 1000,
"Crg_Rev": 500,
"Fixed_Costs": 800,
"Variable_Costs": 200
}];
var data = [];
for (var key in dataset[0]) {
data.push({
category: key,
value: dataset[0][key]
})
}
var svg = d3.select("svg");
var yScale = d3.scaleLinear()
.domain([0, d3.max(data, function(d) {
return d.value
})])
.range([120, 10]);
var xScale = d3.scaleBand()
.domain(data.map(function(d) {
return d.category
}))
.range([40, 280])
.padding(0.2);
var rects = svg.selectAll(null)
.data(data)
.enter()
.append("rect")
.attr("fill", "steelblue")
.attr("x", function(d) {
return xScale(d.category)
})
.attr("width", xScale.bandwidth())
.attr("y", function(d) {
return yScale(d.value)
})
.attr("height", function(d) {
return 120 - yScale(d.value)
});
var yAxis = d3.axisLeft(yScale);
var xAxis = d3.axisBottom(xScale);
svg.append("g").attr("transform", "translate(40,0)").call(yAxis);
svg.append("g").attr("transform", "translate(0,120)").call(xAxis);
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg></svg>

Unable to get scaleLinear() to populate values into SVG Rectangle's width

Please look at this index.html with simple d3.js scaling:
<body>
<script>
var canvasWidth = 750;
var canvasHeight = 600;
// Setup scales
d3.json("tt.json",
function(data)
{
var widthScale = d3.scaleLinear()
.domain([0, 3])
.range(0, canvasWidth);
var colorScale = d3.scaleLinear()
.domain([0, 10])
.range(["red", "blue"]);
var canvas = d3.select("body")
.append("svg")
.attr("width", canvasWidth)
.attr("height", canvasHeight)
canvas.selectAll("rect")
.data(data)
.enter()
.append("rect")
.attr("width", function(d) { return widthScale(d.t_count); })
.attr("height", 30)
.attr("y", function(d, i) { return i * 50; })
.attr("fill", "blue")
canvas.selectAll("text")
.data(data)
.enter()
.append("text")
.attr("fill", "black")
.attr("y", function(d, i) { return (i * 50 + 22); })
.text(function(d) { return d.ttext; })
}) // of function(data)
</script>
</body>
The .json file on the server looks like this:
[{"ttext":"Architect","t_count":"1"},
{"ttext":"Entertainment","t_count":"2"},
{"ttext":"Food","t_count":"2"},
{"ttext":"Gujarati","t_count":"1"},
{"ttext":"Laundry","t_count":"1"},
{"ttext":"Milk","t_count":"2"},
{"ttext":"Rajasthani","t_count":"1"}]
The output is just this:
(nothing but the ttext attribute from the json file.)
A hardcoded factor i.e., return (d.t_count * 30); does plot the rectangle. But when the scaling is introduced, the text is all that is output.
Here's the SVG DOM that is generated (strangely missing width in the rect):
Struggling with this for a day almost. Can anyone review and please help?
In a scale, both domain and range have to be arrays.
So, change to this:
var widthScale = d3.scaleLinear()
.domain([0, 3])
.range([0, canvasWidth]);//range as an array

How to set date range when using d3.time.scale()

My aim is pretty simple, I would like to create a timeline using dates from a CSV. I would then like to plot circles at different points along this timeline.
Full code
data
,,Name,First names,s,r,Nat,born,starting point,starting date,arrival date,days,km,Assist,Support,Style,note,
1,1,KAGGE,Erling,,,Nor,1/15/1963,Berkner Island,11/18/1992,1/7/1993,50,appr. 1300,n,n,solo,first solo unassisted,
2,2,ARNESEN,Liv,f,,Nor,6/1/1953,Hercules Inlet,11/4/1994,12/24/1994,50,1130,n,n,solo,first woman unassisted,
The dates I would like to use are ['starting point']
I think the problem is here: I'm unsure what the domain should look like (and how to find min and max)
var x = d3.time.scale().domain(['11/18/1992', '10/25/2013']).range([0, w]);
I am attempting to plot the circles using cx
.attr('cx', function(d) {
return x(d['starting date'])
})
code
d3.csv("data.csv", function(data) {
var cd = data.filter(function(d) {
return (d.Style == "solo")
});
var de = cd.sort(function(a, b) {
return a.days - b.days
})
console.log(de)
var h = 400;
var w = 500;
var x = d3.time.scale().domain(['11/18/1992', '10/25/2013']).range([0, w]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.ticks(10)
var svg = d3.select("body").append("svg")
.attr("width", w)
.attr("height", h)
.append("g")
svg.append("g")
.attr("class", "x axis")
.call(xAxis);
svg.selectAll('.start')
.data(de)
.enter()
.append('circle')
.attr('cx', function(d) {
return x(d['starting date'])
})
.attr('cy', 10)
.attr('r', 5)
.style('fill', 'red')
})
I'm not sure how d3 will coerce your date strings into dates so I'd suggest converting them explicitly. I think that should fix your problem.
First in the source data e.g.
var timeFormat = d3.time.format("%m/%d/%Y");
var cd = data.filter(function(d) {
return (d.Style == "solo")
})
.map(function(d){
d["starting date"] = timeFormat.parse(d["starting date"]);
d["arrival date"] = timeFormat.parse(d["arrival date"]);
return d;
});
And then similarly when you create your domain...
var x = d3.time.scale()
.domain([timeFormat.parse('11/18/1992'), timeFormat.parse('10/25/2013')])
.range([0, w]);
To determine the extent of the dates automatically you could use d3.array.extent (docs)

D3: How to dynamically refresh a graph by changing the data file source?

How do I update the data on demand by changing the file d3 accesses? With a click, for example, it would read data from a new data file and add more nodes to the graph like AJAX.
I use d3.tsv to read in data.tsv, one of many files of the same format.
I made a simple graph to illustrate my question. Thanks in advance.
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
var width = 400,
height = 200;
var x = d3.scale.linear().range([0, width]),
y = d3.scale.linear().range([height, 0]);
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
d3.tsv("data.tsv", function(error, data) {
if (error) console.warn(error);
x.domain(d3.extent(data, function(q) {return q.xCoord;}));
y.domain(d3.extent(data, function(q) {return q.yCoord;}));
svg.selectAll(".dot")
.data(data)
.enter().append("circle")
.attr("r", 10)
.attr("cx", function(d) { return x(d.xCoord); })
.attr("cy", function(d) { return y(d.yCoord); })
});
</script>
update the graph
Try this.
var width = 400,
height = 200;
var x = d3.scale.linear().range([0, width]),
y = d3.scale.linear().range([height, 0]);
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
var dataSource = 'data.tsv',
dataSource2 = 'data2.tsv';
function updateChart(sourcefile) {
d3.tsv(sourcefile, function(error, data) {
if (error) console.warn(error);
x.domain(d3.extent(data, function(q) {return q.xCoord;}));
y.domain(d3.extent(data, function(q) {return q.yCoord;}));
svg.selectAll(".dot")
.data(data)
.enter().append("circle")
.attr("r", 10)
.attr("cx", function(d) { return x(d.xCoord); })
.attr("cy", function(d) { return y(d.yCoord); })
});
}
updateChart(dataSource);
//here is where you change the data..
d3.select(#button).on("click", function() {
updateChart(dataSource2)
})

Access key/value in a paired-bar-chart - D3.js

I'm attempting to make a Paired Bar Graph between glob and local within my JS Object/Array. I've made bar graphs in D3 previously, but haven't used objects. I'm finding it difficult to access the correct data.
Eventually, the keyword data will be used in the axis. And the cpc will be used as a tooltip.
Here's the code that I have so far: (or see my JSFiddle)
var w = 600;
var h = 400;
var colors = ["#377EB8", "#4DAF4A"];
var dataset = {"keyword": ["payday loans", "title loans", "personal loans"],
"glob": ["1500000", "165000", "550000"],
"local": ["673000", "165000", "301000"],
"cpc": ["14.11", "12.53", "6.14"]
};
var series = 2; // Global & Local
var x0Scale = d3.scale.ordinal()
.domain(d3.range(dataset.glob.length))
.rangeRoundBands([0, w], 0.05);
var yScale = d3.scale.linear()
.domain([0, d3.max(dataset, function(d) {return d.glob;})])
.range([0, h]);
var glob = function(d) {
return d.glob;
};
//SVG element
var svg = d3.select("#searchVolume")
.append("svg")
.attr("width", w)
.attr("height", h);
// Graph Bars
svg.selectAll("rect")
.data(dataset, glob) //access the series here?
.enter()
.append("rect")
.attr("x", function(d, i){
return x0Scale(i);
})
.attr("y", function(d) {
return h - yScale(d.glob);
})
.attr("width", x0Scale.rangeBand())
.attr("height", function(d) {
return yScale(d.glob); // ***************
})
.attr("fill", colors[1]);
Currently, the chart doesn't get populated. I assume I am not accessing values correctly. I'm simply trying to get data from glob to make sure I'm accessing things correctly - and then from there I was going to populate both series, etc. Is my issue not accessing key/values correctly?
Take a look at this: http://jsfiddle.net/juY5E/2/
I was able to get three bars by changing .data(dataset, glob) to .data(dataset.glob) and then changing d.glob to +d for the 'y' attr, the 'height' attr and in yScale.domain
to be able to switch between glob and local, you may want to restructure the data.

Resources