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.
Related
In this codepen I am trying to create a column chart with scaleBand for the x and width. But, I have a large gap between the second and third columns. Why is it doing that? The full D3 code is below but the codepen also has the data that I am using for the column chart. Thank you.
data.forEach(function(d) {
d.height = +d.height;
});
var w = 400;
var h = 400;
var xScale = d3.scaleBand()
.domain(["Burj Khalifa", "hanghai Tower", "Abraj Al-Bait Clock Tower",
"Ping An Finance Centre", "Lotte World Tower", "One World Trade Center",
"Guangzhou CTF Finance Center"])
.range([0, 400])
.paddingInner(0.3)
.paddingOuter(0.3);
var yScale = d3.scaleLinear()
.domain([0,828])
.range([0,400]);
var svg = d3.select("#chart-area").append("svg")
.attr("width", w)
.attr("height", h);
var rects = svg.selectAll("rect")
.data(data);
rects.enter().append("rect")
.attr("width", xScale.bandwidth)
.attr("height", (d) => yScale(d.height))
.attr("x", (d) => xScale(d.name))
.attr("y", (d) => h - yScale(d.height))
.attr("fill", "blue");
It seems your domain array was not mapped correctly. Rather use your xScale using computed arrays which will avoid any spelling mistakes / special character interpretation.
Using this solves it.
var xScale = d3.scaleBand()
.domain(data.map(d => d.name))
.range([0, 400])
.paddingInner(0.3)
.paddingOuter(0.3);
Here is the working codepen.
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>
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)
I have problem adding text in my histogram. I can do this in more simple example.
I try to do this:
// try to add bar value
var barnum = g.selectAll('text')
.data(layout)
.enter()
.append("text")
.attr('y',-10)
.attr('x',10)
.attr("text-anchor", "middle")
.style("fill","black")
.text('testtest')
.style("pointer-events", "none")
;
barnum.transition();
I can't see any text in my figure. The code include definition is here:
var dateFormat = d3.time.format("%Y-%m-%d");
var g;
var data;
var margin = {top: 30, right: 30, bottom: 80, left: 80},
width = 500 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var cx = 10;
var numberBins = 5;
var dispatch = d3.dispatch(chart, "hover");
function chart(container) {
g = container;
update();
}
chart.update = update;
function update() {
// create hist layout
var hist = d3.layout.histogram()
.value(function(d) { return d.selectvar })
.range([d3.min(data, function(d){ return d.selectvar }) , d3.max(data, function(d){ return d.selectvar }) ])
.bins(numberBins);
var layout = hist(data);
var maxLength = d3.max(layout, function(d) { return d.length });
var widthScale = d3.scale.linear()
.domain([0, maxLength])
.range([0, width])
var yScale = d3.scale.ordinal()
.domain(d3.range(numberBins))
.rangeBands([height, 0], 0)
var colorScale = d3.scale.category20();
// create svg
var rects = g.selectAll("rect")
.data(layout)
rects.enter().append("rect")
rects .transition()
.duration(500)
.attr({
y: function(d,i) {
return yScale(i)
},
x: 50,
height: yScale.rangeBand(),
width: function(d,i) {
return widthScale(d.length)
},
fill: function(d, i) { return colorScale(i) }
});
rects.exit().transition().remove();
// try to add bar value
var barnum = g.selectAll('text')
.data(layout)
.enter()
.append("text")
.attr('y',-10)
.attr('x',10)
.attr("text-anchor", "middle")
.style("fill","black")
.text('testtest')
.style("pointer-events", "none")
;
barnum.transition();
is there something wrong with my way to create svg element? I found out some successful case use append('g') from the beginning. New to d3.js! thank you.
You're using d3.dispatch, which is documented on a page titled Internals. That doesn't mean you shouldn't use it, but rather, it shouldn't be your first choice.
You're correct that there's "something wrong with my way to create svg element" -- you're not creating one! Try:
var svg = d3.select("body").append("svg");
var g = svg.append("g");
At this point, you need to have a good understanding of the DOM, the SVG standard, CSS selectors, and D3's selection API to make things work. You don't tell D3 to put labels on your bars and that's it. You have to instruct it what elements to create, and where, keeping track of translates and offsets and stuff like that. You're best off copying and studying one of Mike Bostock's many examples.
D3 is not learned quickly. You need to invest time learning it before you can make any chart you like.
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.