I read "Interactive Data Visualization for the web" by Scott Murray, but this book use d3 version3. I've tried to fix it, but some problems happen, and my code is below. I got errors about "y: Expected length, "NaN".", and maybe my stack function doesn't work. However, I don't know how to solve it. I need someone to help me.
// declare variable
var svgWidth = 500,
svgHeight = 300,
svgData = [],
maxValue = 16;
svgData = getData(svgData);
// set stack color
var color = d3.scaleOrdinal(d3.schemeCategory10);
// create stack layout
var stack = d3.stack();
stack(svgData);
// define x,y scale
var xScale = d3.scaleBand()
.domain(d3.range(svgData[0].length))
.rangeRound([0, svgWidth])
.paddingInner(0.05),
yScale = d3.scaleLinear()
.domain([0, d3.max(svgData, function(d){
return d3.max(d, function(d){
d.y0 + d.y;
});
})])
.range([0, svgHeight])
.clamp(true);
// create svg
var svg = d3.select("body")
.append("svg")
.attr("width", svgWidth)
.attr("height", svgHeight);
// add group and fill color for each row of data
var group = svg.selectAll("g")
.data(svgData)
.enter()
.append("g")
.style("fill", function(d, i){
return color(i);
});
// add a rect for each data value
var rect = group.selectAll("rect")
.data(function(d){
return d;
})
.enter()
.append("rect")
.attr("x", function(d, i){
return xScale(i);
})
.attr("y", function(d){
return yScale(d.y0);
})
.attr("width", xScale.bandwidth())
.attr("height", function(d){
return yScale(d.y);
});
// get data
function getData(data){
var temp =0,
tempArr = [];
data = [];
for(var i=0; i<3; i++){
tempArr = [];
for(var j=0; j<5; j++){
temp = Math.round(Math.random() *maxValue);
tempArr.push( { x: j, y: temp });
}
data.push(tempArr);
}
return data;
}
Related
I am new to D3.js and trying to make a bar chart with GDP on Y axis and date on X axis.
I am trying to make a xScale with d3.timeScale() but for some reason the code keeps returning undefined when using it. Below is my code, what am I doing wrong?
var data = [
[
"2011-01-01",
15238.4 ]
,
["2015-07-01",
18064.7
]
];
var w = 1000;
var h = 300;
var barPadding = 1;
var svg = d3.select("#chart")
.append("svg")
.attr("height", h);
var maxDate = d3.max(data, function(d){
return d[0];
});
var minDate = d3.min(data, function(d){
return d[0];
});
var maxGDP = d3.max(data, function(d){
return d[1];
});
var minGDP = d3.min(data, function(d){
return d[1];
});
minDate = new Date(minDate);
maxDate = new Date(maxDate)
var xScale = d3.scaleTime()
.domain([minDate, maxDate])
.range(0,w)
var yScale = d3.scaleLinear()
.domain([minGDP, maxGDP])
.range(0,h)
.attr("width", w)
var bars = svg.selectAll("rect")
.data(data)
.enter()
.append("rect")
var barAttributes = bars
.attr("x", function(d) { return xScale(d[0]);})
.attr("y", function(d) { return h - (d[1]/100);})
.attr("width", function(d,i) { return w/data.length;})
.attr("height", function(d) { return (d[1]/100) *4 ;})
.attr("fill", "#8e44ad");
In D3, both domain and range have to be an array:
If domain is specified, sets the scale’s domain to the specified array of numbers.
And also:
If range is specified, sets the scale’s range to the specified array of values.
Thus, instead of:
var xScale = d3.scaleTime()
.domain([minDate, maxDate])
.range(0,w);
It should be:
var xScale = d3.scaleTime()
.domain([minDate, maxDate])
.range([0,w]);//an array here
This question already has answers here:
d3 time scale - last bar on graph is appearing outside of the graph
(2 answers)
How to use x and width in a bar chart with scaleTime?
(1 answer)
Closed 4 years ago.
I am trying to graph a bar chart using a simple dataset by using D3.js. I managed to get most done, but I have one problem left. The bars on the very right GO OVER the y-axis and will not show correctly.
This is the code:
codepen
$(document).ready(function(){
var dataset;
var data;
var date;
var temp1 = [];
var temp2 = [];
var url = "https://raw.githubusercontent.com/FreeCodeCamp/ProjectReferenceData/master/GDP-data.json";
d3.json(url, function(json) {
dataset = json.data;
for (var j = 0; j < dataset.length; j++) {
temp1[j] = dataset[j][1]
}
data = temp1;
for (var j = 0; j < dataset.length; j++) {
temp2[j] = dataset[j][0]
}
date = temp2;
minDate = new Date(date[0]);
maxDate = new Date(date[date.length - 1]);
var svgWidth = 1000;
var svgHeight = 450;
var barWidth = (svgWidth/data.length);
var svg = d3.select("svg")
.attr("width", svgWidth)
.attr("height", svgHeight);
var xScale = d3.scaleTime()
.domain([minDate, maxDate])
.range([0, svgWidth]);
var yScale = d3.scaleLinear()
.domain([0, d3.max(data)])
.range([svgHeight, 0]);
var x_axis = d3.axisBottom()
.scale(xScale);
var y_axis = d3.axisRight()
.scale(yScale);
var xAxisTranslate = svgHeight - 20;
svg.append("g")
.attr("transform", "translate(0 " + xAxisTranslate +")")
.call(x_axis);
svg.append("g")
.attr("transform", "translate(0, -20)")
.call(y_axis);
var barChart = svg.selectAll("rect")
.data(data)
.enter()
.append("rect")
.attr("y", function(d) {
return yScale(d);
})
.attr("height", function(d) {
return svgHeight - yScale(d);
})
.attr("width", barWidth)
.attr("transform", function (d, i) {
var translate = [barWidth * i, - 20];
return "translate("+ translate +")";
});
});
});
How do I get the bar chart to draw values in my dataset correctly/fully?
I'm trying to create a globe (Orthographic projection) with a drag, which also has circles on it.
I've been able to create the globe with a drag, and add circles. The problem is that when I drag the circles don't move with the globe.
Here is my bl.ock where you can find my code: http://bl.ocks.org/JulienAssouline/3caf0d6e01aa8220a8d4027cb9158d7e
I've looked at other bl.ock examples such as this one:https://bl.ocks.org/larsvers/f8efeabf480244d59001310f70815b4e
and this one:
https://bl.ocks.org/curran/115407b42ef85b0758595d05c825b346
but I haven't been able to get it to work for me. Their methods seem to be quite different than mine, and I don't completely understand their code.
Would anyone know what method, or what I need to add to my code?
Thanks.
Here is my javascript code:
(function(){
var h = 600;
var w = 900;
var i = 0;
var map = void 0;
var world = void 0;
var US = void 0;
var margin = {
top: 10,
bottom: 40,
left: 0,
right: 30
};
var circleScale = d3.scaleSqrt()
.domain([0, 4445])
.range([0.5, 10])
var width = w - margin.left - margin.right;
var height = h - margin.top - margin.bottom;
var dragging = function(d){
var c = projection.rotate();
projection.rotate([c[0] + d3.event.dx/6, c[1] - d3.event.dy/6])
map.selectAll('path').attr('d', path);
}
var drag = d3.drag()
.on("drag", dragging)
var projection = d3.geoOrthographic().clipAngle(90);
var path = d3.geoPath().projection(projection);
var svg = d3.select("body")
.append("svg")
.attr("id", "chart")
.attr("width", w)
.attr("height", h)
d3.json("world.json", function(json){
d3.csv("arms_transfer_2012_2016_top - arms_transfer_2012_2016_top.csv", function(error, data){
var countries = topojson.feature(json, json.objects.countries).features
var US = countries[168]
map = svg.append('g').attr('class', 'boundary');
world = map.selectAll('path').data(countries);
US = map.selectAll('.US').data([US]);
Circles = map.selectAll(".circles").data(data)
console.log(countries[168])
world.enter()
.append("path")
.attr("class", "boundary")
.attr("d", path)
US.enter()
.append("path")
.attr("class", "US")
.attr("d", path)
.style("fill", "lightyellow")
.style("stroke", "orange")
Circles.enter()
.append("circle")
.attr("class", "importer")
.attr("r", function(d){
return circleScale(d.Millions)
})
.attr("cx", function(d){
var coords = projection([d.Longitude_imp, d.Latitude_imp])
return coords[0];
})
.attr("cy", function(d){
var coords = projection([d.Longitude_imp, d.Latitude_imp])
return coords[1];
})
.style("fill", "#cd0d0e")
svg.append("rect")
.attr("class", "overlay")
.attr("width", w)
.attr("height", h)
.call(drag)
})
})
})();
You have to update the position of the circles in your dragging function:
var dragging = function(d) {
var c = projection.rotate();
projection.rotate([c[0] + d3.event.dx / 6, c[1] - d3.event.dy / 6])
map.selectAll('path').attr('d', path);
map.selectAll(".circles").attr("cx", function(d) {
var coords = projection([d.Longitude_imp, d.Latitude_imp])
return coords[0];
})
.attr("cy", function(d) {
var coords = projection([d.Longitude_imp, d.Latitude_imp])
return coords[1];
})
}
Also, select them using the correct class.
Regarding performance, if you don't want to calculate coords twice, you can use an each:
var dragging = function(d) {
var c = projection.rotate();
projection.rotate([c[0] + d3.event.dx / 6, c[1] - d3.event.dy / 6])
map.selectAll('path').attr('d', path);
map.selectAll(".circles").each(function(d) {
var coords = projection([d.Longitude_imp, d.Latitude_imp])
d3.select(this).attr("cx", function(d) {
return coords[0];
})
.attr("cy", function(d) {
return coords[1];
})
})
}
Here is your bl.ocks with that change: http://bl.ocks.org/anonymous/dc2d4fc810550586d40d4b1ce9088422/40c6e199a5be4e152c0bd94a13ea94eba41f004b
PS: You have a problem with the circles at the far side of the globe... however, this is another issue, already addressed here at S.O. For instance, this answer of mine: https://stackoverflow.com/a/46441983/5768908
I am working on a d3.js Dashboard (so only one dashboard and many layouts). I wanted to display a scatterplot animated, with the dots moving regarding some filter I am applying.
But in order, I have to display something first which is not happening.
Here are my program, which is a scatterplot.js file in an MVC format.
function scatterplot(DOMElement){
var scatObj = {}; // main object
scatObj.setTooltipText = function(f){
tooltipText = f;
//updateInteractions();
return scatObj;
}
scatObj.setMouseclickCallback = function(f){
mouseclickCallback = f;
//updateInteractions();
return scatObj;
}
// function loading data and rendering it in scatter chart
// parameters:
// - dataset in following format:
// [{"key": year, "value": money}, {...}, ...]
// returns:
// - scatObj for chaining
scatObj.loadAndRender = function(data){
dataset = data;
render();
return scatObj;
}
// ---- PRIVATE VARIABLES
// sizing vars
var dataset = []; // Initialize empty array
var numDataPoints = dataset.length;
for(var i=0; i<numDataPoints; i++) {
var newNumber1 = d.value.x; // New random integer
var newNumber2 = d.value.y; // New random integer
dataset.push([newNumber1, newNumber2]); // Add new number to array
}
// Setup settings for graphic
var canvas_width = 500;
var canvas_height = 300;
var padding = 30; // for chart edges
// Create scale functions
var xScale = d3.scaleLinear() // xScale is width of graphic
.domain([0, d3.max(dataset, function(d) {
return d[0]; // input domain
})])
.range([padding, canvas_width - padding * 2]); // output range
var yScale = d3.scaleLinear() // yScale is height of graphic
.domain([0, d3.max(dataset, function(d) {
return d[1]; // input domain
})])
.range([canvas_height - padding, padding]); // remember y starts on top going down so we flip
// scales
// Define X axis
var xAxis = d3.axisBottom()
.scale(xScale)
// Define Y axis
var yAxis = d3.axisLeft()
.scale(yScale)
var svg = d3.select("DOMElement") // This is where we put our vis
.append("svg")
.attr("width", canvas_width)
.attr("height", canvas_height);
var tooltip = d3.select(DOMElement).append("div")
.classed("tooltip", true);
// interaction settings
var tooltipText = function(d, i){return "tooltip over element "+i;}
var mouseoverCallback = function(d, i){ };
var mouseoutCallback = function(d, i){ };
var mouseclickCallback = function(d, i){ console.log(d,i)};
var keySelected = null;
// ---- PRIVATE FUNCTIONS
function render(){
GUP_scat();
}
function GUP_scat(){
// GUP
// Create Circles
svg.selectAll("circle")
.data(dataset)
.enter()
.append("circle") // Add circle svg
.attr("cx", function(d) {
return xScale(d[0]); // Circle's X
})
.attr("cy", function(d) { // Circle's Y
return yScale(d[1]);
})
.attr("r", 2); // radius
// Add to X axis
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + (canvas_height - padding) +")")
.call(xAxis);
// Add to Y axis
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + padding +",0)")
.call(yAxis);
// On click, update with new data
d3.select(DOMElement)
.on("click", function() {
keySelected = (keySelected == d.key) ? null : d.key;
var numValues = dataset.length; // Get original dataset's length
dataset = []; // Initialize empty array
for(var i=0; i<numValues; i++) {
var newNumber1 = d.value.x; // Random int for x
var newNumber2 = d.value.y; // Random int for y
dataset.push([newNumber1, newNumber2]); // Add new numbers to array
}
// Update scale domains
xScale.domain([0, d3.max(dataset, function(d) {
return d[0]; })]);
yScale.domain([0, d3.max(dataset, function(d) {
return d[1]; })]);
// Update circles
svg.selectAll("circle")
.data(dataset) // Update with new data
.transition() // Transition from old to new
.duration(1000) // Length of animation
.each("start", function() { // Start animation
d3.select(this) // 'this' means the current element
.attr("fill", "red") // Change color
.attr("r", 5); // Change size
})
.delay(function(d, i) {
return i / dataset.length * 500; // Dynamic delay (i.e. each item delays a little longer)
})
//.ease("linear") // Transition easing - default 'variable' (i.e. has acceleration), also: 'circle', 'elastic', 'bounce', 'linear'
.attr("cx", function(d) {
return xScale(d[0]); // Circle's X
})
.attr("cy", function(d) {
return yScale(d[1]); // Circle's Y
})
.each("end", function() { // End animation
d3.select(this) // 'this' means the current element
.transition()
.duration(500)
.attr("fill", "black") // Change color
.attr("r", 2); // Change radius
});
// Update X Axis
svg.select(".x.axis")
.transition()
.duration(1000)
.call(xAxis);
// Update Y Axis
svg.select(".y.axis")
.transition()
.duration(100)
.call(yAxis);
mouseclickCallback(d, i);
});}
return scatObj; // returning the main object
}
I am calling it like this in my main.js file :
dash.append('div').attr('id', 'scat1').classed("scatterplot", true)
.append("h3").text("Per Scateub");
var scat1 = scatterplot("div#scat1")
.setTooltipText(function(d, i){
return "<b>"+d.data.key +" : "+d3.format(",.2f")(d.value)+"</b> /10 ";
})
.loadAndRender(dataManager.getFteOverall());
scat1.setMouseclickCallback(function(d, i){
dataManager.setNameFilter(d.key);
redraw();
})
and here is my nest :
dmObj.getFteOverall= function(){
var nestedData = d3.nest()
.key(function(d){ return d.naAsses;})
.rollup(function (v) { return {
x: d3.sum(v,function(d){
return (d.four*4,d.three*3,d.two*2,d.one)/100 }),
y: d3.sum(v, function(d){
return (d.fte)
}
)
};
})
.sortKeys(d3.ascending)
.entries(filteredData());
return nestedData;
}
I know it can seem a bit a stupid question, but I have been struggling with it, and I have already been stuck for a while.
I hope I am clear and you will be able to help me (just in case I am working with d3 V4).
Thanks to advance guys.
I'm trying to make a pie chart using d3. To do this I am send my data in a JSON format to my view and then I'd like to try and total up three of the parameters and then use these in the pie chart. So far I have managed to get the data to the view but now I need to use a foreach to total up the parameters. I'm only working on one at the moment.
Script:
var width = 960,
height = 500,
radius = Math.min(width, height) / 2;
var color = d3.scale.ordinal()
.range(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56", "#d0743c"]);
var arc = d3.svg.arc()
.outerRadius(radius - 10)
.innerRadius(0);
var pie = d3.layout.pie()
.sort(null)
.value(function (d) { return d.Query; });
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var data = #Html.Raw(#Model.output);
var QueryTotal = data.forEach(function(d) {
d.Query = +d.Query;
});
console.log(QueryTotal);
var g = svg.selectAll(".arc")
.data(pie(data))
.enter().append("g")
.attr("class", "arc");
g.append("path")
.attr("d", arc)
.style("fill", function (d) { return color("Query"); });
g.append("text")
.attr("transform", function (d) { return "translate(" + arc.centroid(d) + ")"; })
.attr("dy", ".35em")
.style("text-anchor", "middle")
.text(function (d) { return "Query"; });
How do I use a for each to total up the values, I've given it an attempt which you can see above. It just returns undefined.
To sum the values, use reduce()
var QueryTotal = data.reduce(function(prev, d) {
return prev + d.Query;
}, 0);
Or, using your existing structure:
var QueryTotal = 0;
data.forEach(function(d) {
QueryTotal += d.Query;
});
The D3 way to do this is to use d3.sum():
var QueryTotal = d3.sum(data, function(d) { return d.Query; });