I'm trying to duplicate this code found on bl.ocks.org, but I can't get my bar graph to render. I'm pulling from a csv instead of a tsv, but I don't think that would made a difference. There aren't any errors written to the console. Where am I going wrong?
<script type="text/javascript">
$( document ).ready(function() {
loadChart();
});
function loadChart() {
var svg = d3.select("svg");
var margin = { top: 20, right: 20, bottom: 30, left: 40},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom;
var x = d3.scaleBand().rangeRound([0,width]).padding(0.1),
y = d3.scaleLinear().rangeRound([height, 0]);
var g = svg.append(g).attr("transform", "translate(" + margin.left + "," + margin.top + ")");
d3.csv("Data/data.csv", function(d) {
d.NumProducts = +d.NumProducts;
return d;
}, function(error, data) {
if (error) throw error;
x.domain(data.map(function(d) { return d.Category; }));
y.domain([0, d3.max(data, function(d) { return d.NumProducts; })]);
g.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
g.append("g")
.attr("class", "axis axis--y")
.call(d3.axisLeft(y).ticks(10))
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("Dy", "0.71em")
.attr("text-anchor", "end")
.text("Number of Products");
g.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class" , "bar")
.attr("x", function(d) { return x(d.Category); })
.attr("y", function(d) { return y(d.NumProducts); })
.attr("width", x.bandwidth())
.attr("height", function(d) { return height - y(d.NumProducts); });
});
}
</script>
The csv:
Category,NumProducts
Beverages,12
Condiments,12
Confections,13
Dairy Products,10
Grains/Cereals,7
Meat/Poultry,6
Produce,5
Seafood,12
The main issue that you have is down to missing speech marks.
var g = svg.append(g)
Should read:
var g = svg.append("g")
If you were to inspect your DOM you would see
<svg>
<undefined transform="translate(40,20)">
... (correct DOM)
</undefined>
</svg>
The reason that nothing renders after this point, is because the browser doesn't know how to render an <undefined> element, therefore it stops trying to render.
This immediately indicated that one of the append calls wasn't working because the DOM didn't understand the type of element given to it. Making the change produces this:
Related
This question already has an answer here:
D3: .transition() not working with events
(1 answer)
Closed 2 years ago.
So I have a bar chart with buttons that will update preset input data and now I want to include tooltip so that it will show data from y-axis when hovered. I tried to follow some tutorials that I could find but still failed. Below is what I've done so far but I took out most of the data to avoid confusion.
I got unknown type:mouseover for the following code
var AU = [
{group: "NSW", value: 871.8},
{group: "VIC", value: 736.8},
{group: "QLD", value: 517.9},
{group: "SA", value: 460.1},
{group: "WA", value: 498.5},
{group: "TAS", value: 451.4},
{group: "NT", value: 410.1},
{group: "ACT", value: 699.1},
{group: "Australia", value: 678.5}
];
// set the dimensions and margins of the graph
var margin = {top: 30, right: 30, bottom: 70, left: 60},
width = 600 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
// append the svg object to the body of the page
var svg = d3.select("#my_dataviz")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
// Initialize the X axis
var x = d3.scaleBand().range([ 0, width ])
.paddingInner(0.2);
var xAxis = svg.append("g")
.attr("transform", "translate(0," + height + ")")
// Initialize the Y axis
var y = d3.scaleLinear()
.range([ height, 0]);
var yAxis = svg.append("g")
.attr("class", "myYaxis")
// A function that create / update the plot for a given variable:
function update(data) {
// Update the X axis
x.domain(data.map(function(d) { return d.group; }))
xAxis.call(d3.axisBottom(x))
// text label for the x axis
// text label for the x axis
svg.append("text")
.attr("transform",
"translate(" + (width/2) + " ," +
(height + margin.top + 20) + ")")
.style("text-anchor", "middle")
.text("Postcode");
// Update the Y axis
y.domain([0, d3.max(data, function(d) { return d.value }) ]);
yAxis.transition().duration(1000).call(d3.axisLeft(y));
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 0 - margin.left)
.attr("x",0 - (height / 2))
.attr("dy", "1em")
.style("text-anchor", "middle")
.text("Price ('000k)");
// Create the u variable
var u = svg.selectAll("rect")
.data(data)
u
.enter()
.append("rect") // Add a new rect for each new elements
.merge(u) // get the already existing elements as well
.transition() // and apply changes to all of them
.duration(1000)
.attr("x", function(d) { return x(d.group); })
.attr("y", function(d) { return y(d.value); })
.attr("width", x.bandwidth())
.attr("height", function(d) { return height - y(d.value); })
.attr("fill", "#69b3a2")
.on("mouseover", function(d){tooltip.text(d); return tooltip.style("visibility", "visible");})
.on("mousemove", function(){return tooltip.style("top", (d3.event.pageY-10)+"px").style("left",(d3.event.pageX+10)+"px");})
.on("mouseout", function(){return tooltip.style("visibility", "hidden");});
// If less group in the new dataset, I delete the ones not in use anymore
u
.exit()
.remove()
}
// Initialize the plot with the first dataset
update(AU)
The reason for this error is that by calling .transition(), the selected element changes from the result of d3.select to the currently active transition. And while transition.on supports events ("start", "end", "interrupt"), it does not support "mouseover". You can fix this by moving the .on() to before your call to .transition(). I've illustrated below how this error occurs.
const svg = d3.select("body")
.append("svg");
svg
.append("rect")
.attr("width", 100)
.attr("height", 100)
.attr("fill", "blue")
.on("mouseover", function() { d3.select(this).attr("fill", "red"); });
svg
.append("rect")
.attr("x", 100)
.attr("width", 100)
.attr("height", 100)
.attr("fill", "white")
.transition()
.duration(500)
.attr("fill", "green")
.on("mouseover", function() { d3.select(this).attr("fill", "red"); });
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
I wanted to produce a graph with the years range from 2012-2017 and the number of incidents occurs in the year. I have nested the data. I am not able to produce a bar graph and was given the errors : Error: attribute height: Expected length, "NaN". and Error: attribute width: Expected length, "15,430". I tried to fix the error using previous posts suggestions but no luck. Any help with the code or suggestions would be appreciated it. thanks
js:
//set the dimensions and margins of the graph
var margin = {
top: 20,
right: 20,
bottom: 30,
left: 50
},
width = 500 - margin.left - margin.right,
height = 300 - margin.top - margin.bottom;
padding = 15;
// Fomat timeStamp to year
var dateFormat = d3.timeFormat("%Y");
//append the svg object to the body of the page
var svg = d3.select("#graph").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g").attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
// Get the data
d3.json("https://moto.data.socrata.com/resource/jfwn-iu5d.json",
function(data) {
// Objects
data.forEach(function(data) {
data.incident_description = data.incident_description;
data.incident_datetime = dateFormat(new
Date(data.incident_datetime));
});
// Nest data by year of incident
var NestbyDate = d3.nest()
.key(function(d) {
return d.incident_datetime;
})
.key(function(d) {
return d.incident_description + " " + d.incident_datetime;
})
.rollup(function(leaves) {
return d3.sum(leaves, function(d) {
return (d.incident_description)
});
})
.entries(data);
console.log(NestbyDate);
data.forEach(function(data) {
data.incident_description = +data.incident_description;
data.incident_datetime = +data.incident_datetime;
});
var x_domain = d3.extent(NestbyDate, function(d) {
return d.key;
});
var y_domain = d3.extent(NestbyDate, function(d) {
return d.values;
});
// set the ranges
var x = d3.scaleLinear().domain(x_domain).range([padding, width]);
var y = d3.scaleLinear().domain([0, 550]).range([height, 0]);
// Add the X Axis
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x).ticks(6));
// Add the Y Axis
svg.append("g")
.attr("class", "axis")
.call(d3.axisLeft(y));
// Text label for the x-axis
svg.append("text")
.attr("x", width / 2)
.attr("y", height + margin.top + 9)
.style("text-anchor", "center")
.text("Day Date Format")
.text("Year");
// Text Label for y-axis
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 0 - margin.left)
.attr("x", 0 - (height / 2))
.attr("dy", "1em")
.style("text-anchor", "middle")
.text("Number of crime incidents");
// Draw the bars
svg.selectAll(".rect")
.data(NestbyDate)
.enter()
.append("rect")
.attr("class", "bar")
.attr("x", function(d) {
return (d.key);
})
.attr("y", function(d) {
return (d.values);
})
.attr("width", x.range())
.attr("height", function(d) {
return height - y(d.values);
});
});
You did a few things wrong.
var x should be a d3.scaleBand() not a linear scale.
Therefore the domain for your x-axis should look like this in this case:
var x = d3.scaleBand().domain(NestbyDate.map(d => d.key))
and since you're nesting the data you should change the y attr to d.values.length (you also forgot to add the y() and x() in some of the rect attr) Here are these changes:
svg.selectAll(".rect")
.data(NestbyDate)
.enter()
.append("rect")
.attr("class", "rect")
.attr("x", function(d) {
return x(d.key);
})
.attr("y", function(d) {
return y(d.values.length);
})
.attr("width", x.bandwidth())
.attr("height", function(d) {
return y(0) - y(d.values.length);
});
});
I didn't look super closely at the data but it should give you some idea what you need to change at least
Here's a working Plunker
Is there a way to minimize the padding between ranges in a grouped horizontal bar chart?
I am building a horizontal bar chart directive for AngularJS and at the moment I am pretty close but it's the spacing that I am not exactly happy with. If there is a better way to build the vertical ranges I would very much appreciate any tips. Here's my result so far:
angular.module('MissionControlApp').directive('d3GroupedHorizontalBarChart2', ['d3', '$timeout', function(d3, $timeout) {
return {
restrict: 'E',
scope: {
data: '=',
onClick: '&d3OnClick'
},
link: function(scope, ele) {
var refreshScope = function() {
scope.$apply();
};
var svg = d3.select(ele[0])
.append("svg")
.attr("width", "100%");
// on window resize, re-render d3 canvas
window.onresize = function() {
return scope.$apply();
};
scope.$watch(function(){
return angular.element(window)[0].innerWidth;
}, function(){
return scope.render(scope.data);
}
);
// watch for data changes and re-render
scope.$watch("data", function(newVals) {
if(!newVals) return;
return scope.render(newVals);
}, true);
// define render function for grouped bar charts
scope.render = function(data){
if(!data) return;
// remove all previous items before render
svg.selectAll("*").remove();
// setup variables
var margin = {top: 25, right: 40, bottom: 70, left: 150};
var width = d3.select(ele[0])._groups[0][0].offsetWidth - margin.left - margin.right;
var height = (scope.data.length * 60);
svg.attr('height', height + margin.top + margin.bottom);
var y0 = d3.scaleBand()
.rangeRound([0, height])
.paddingInner(0.01);
var y1 = d3.scaleBand()
.padding(0.01);
var x = d3.scaleLinear()
.rangeRound([0, width]);
var color = d3.scaleLinear()
.domain([0, 25, 50, 75, 100])
.range(["#51b75d", "#90eb9d","#ffff8c","#f5c93f","#c45c44"])
.interpolate(d3.interpolateHcl);
var xAxis = d3.axisBottom(x)
.tickSizeInner(-(height-5))
.tickPadding(8);
var keys = d3.keys(data[0]).filter(function(key) { return key !== "user"; });
y0.domain(data.map(function(d) { return d.user; }));
y1.domain(keys).rangeRound([0, y0.bandwidth()]);
x.domain([0, 100]);
// Define bars
var bar = svg.selectAll(".bar")
.data(data)
.enter().append("g")
.attr("transform", function(d) { return "translate(" + margin.left + "," + (y0(d.user) + (y0.bandwidth()/2) + margin.top - y1.bandwidth()) + ")"; });
var barEnter = bar.selectAll("rect")
.data(function(d) { return d.values; })
.enter();
barEnter.append("rect")
.attr("height", y1.bandwidth())
.attr("y", function(d) {return y1(d.name); })
.attr("x", 0)
.attr("value", function(d){return d.name;})
.attr("width", 0)
.attr("fill", function(d) { return color(d.value); })
.on("mouseover", function() { d3.select(this).style("cursor", "pointer");})
.on("mouseout", function() { d3.select(this).style("cursor", "default");})
.on("click", function(d){
scope.onClick({item: d});
d3.select(".selectedBlueFill").classed("selectedBlueFill", false);
d3.select(this).classed("selectedBlueFill", true);
$timeout(refreshScope, 0, false); // flush the digest cycle
})
.transition()
.duration(1000)
.attr("width", function(d) { return x(d.value); });
barEnter.append("text")
.attr("fill", "#000")
.attr("y", function(d){return y1(d.name) + (y1.bandwidth() / 2);})
.attr("x", function(d){return x(d.value);})
.attr("dx", 5)
.attr("dy", ".35em")
.text(function(d){return parseFloat(d.value).toFixed(0) + "%";})
.attr("fill-opacity", 0)
.transition()
.duration(1500)
.attr("fill-opacity", 1);
// Set up x axis
svg.append("g")
.attr("class", "axisHorizontal")
.attr("transform", "translate(" + margin.left + "," + (height + margin.top) + ")")
.call(xAxis);
// Set up y axis
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.call(d3.axisLeft(y0));
// Draw the legend
// Create the gradient for the legend
svg.append("defs")
.append("linearGradient")
.attr("id", "legend-traffic")
.attr("x1", "0%").attr("y1", "0%")
.attr("x2", "100%").attr("y2", "0%")
.selectAll("stop")
.data(color.range())
.enter().append("stop")
.attr("offset", function(d,i) { return i/(color.range().length-1); })
.attr("stop-color", function(d) { return d; });
// Legend variables
var legendWidth = width * 0.6;
var legendHeight = 10;
// Legend container
var legendSvg = svg.append('g')
.attr("class", "legendWrapper")
.attr("transform", "translate(" + ((width + margin.left + margin.right)/2) + "," + (height + margin.top + margin.bottom) + ")");
// Draw the rectangle
legendSvg.append("rect")
.attr("class", "legendRect")
.attr("x", -legendWidth/2)
.attr("y", -30)
.attr("width", legendWidth)
.attr("height", legendHeight)
.attr("fill", "url(#legend-traffic)");
// Append title
legendSvg.append("text")
.attr("class", "legendTitle")
.attr("x", 0)
.attr("y", -35)
.attr("text-anchor", "middle")
.text("Worksets Opened %");
// Set scale for x-axis
var xScale = d3.scaleLinear()
.range([0, legendWidth])
.domain([0,100]);
// Define x-axis
var legendAxis = d3.axisBottom(xScale).ticks(5);
// Set up x-axis
legendSvg.append("g")
.attr("class", "axisLegend")
.attr("transform", "translate(" + (-legendWidth/2) + "," + (legendHeight-30) + ")")
.call(legendAxis);
};
}
};
}]);
However the result I am getting is with large inner spacing. I am setting the .paddingInner property to 0.001 and still pretty much end up with large spacing......ideas?
The idea would be to make groups, where each group hold unique bars for the group.
Now once you have the group you can alter the between distance with some maths, as shown below in snippet below:
var bar = chart
.selectAll("g")
.data(zippedData)
.enter().append("g")
.attr("transform", function(d, i) {
//here barHeight is the width of the bars.
return "translate(" + spaceForLabels + ","
+ (i * barHeight + gapBetweenGroups * (0.5 + Math.floor(i/data.series.length))) + ")";
});
By changing value of gapBetweenGroups you can regulate the distance of the groups.
Working code here
I am working on making a linegraph in D3 that displays data in JSON format that is retrieved from an API.
I found an example line graph project on D3's website and it seems relatively straight forward. The main difference between the example and my project is that the example uses data from a local csv file instead of JSON from an API request.
// This needs to be changed to my API request
d3.tsv("data.tsv", function(d) {
d.date = parseTime(d.date);
d.close = +d.close;
return d;
}, function(error, data) {
if (error) throw error;
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain(d3.extent(data, function(d) { return d.close; }));
g.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x))
.select(".domain")
.remove();
g.append("g")
.call(d3.axisLeft(y))
.append("text")
.attr("fill", "#000")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", "0.71em")
.attr("text-anchor", "end")
.text("Price ($)");
g.append("path")
.datum(data)
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("stroke-linejoin", "round")
.attr("stroke-linecap", "round")
.attr("stroke-width", 1.5)
.attr("d", line);
});
I changed the csv request part to this because I'm trying to load in my JSON from the API:
d3.json(url).get(function(d) {
for(var i = 0; i < d.Data.length; i++) {
coinValue.push(d.Data[i].close);
dates.push(convertUnixTime(d.Data[i].time));
}
return d;
}, function(error, data) {
if (error) throw error;
This isn't working. I'm confused about why there's a comma and then another function right after.
What is the proper way to make the exact same line graph but with a d3.json function/API request instead?
My JSON looks like this: https://min-api.cryptocompare.com/data/histoday?fsym=ETH&tsym=USD&limit=2&aggregate=3&e=CCCAGG
The difference between d3.csv and d3.json is the accessor (2nd argument).
d3.csv: d3.csv(url[[, row], callback]) in which row is nothing but each row of the data fetched i.e. if you add a function as follows:
d3.csv(url, function(d) {
// each row of the data
}, callback)
d3.json: d3.json(url[, callback]) DOES NOT provide this each row accessor. But you can do that within the callback as follows:
d3.json(url, function(err, data) {
data.forEach(function(row) {
// parse each row as required
});
})
Using the above syntax along with the provided example code and JSON URL, here's a code snippet drawing a line chart:
// This needs to be changed to my API request
d3.json("https://min-api.cryptocompare.com/data/histoday?fsym=ETH&tsym=USD&limit=2&aggregate=3&e=CCCAGG", function(error, d) {
var data = d.Data;
data.forEach(function(d){ d.time = new Date(d.time * 1000) });
//console.log(data);
if (error) throw error;
var svg = d3.select("svg"),
margin = {top: 20, right: 20, bottom: 30, left: 50},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom,
g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var parseTime = d3.timeParse("%d-%b-%y");
var x = d3.scaleTime()
.rangeRound([0, width]);
var y = d3.scaleLinear()
.rangeRound([height, 0]);
var line = d3.line()
.x(function(d) { return x(d.time); })
.y(function(d) { return y(d.close); });
x.domain(d3.extent(data, function(d) { return d.time; }));
y.domain(d3.extent(data, function(d) { return d.close; }));
g.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x))
.select(".domain")
.remove();
g.append("g")
.call(d3.axisLeft(y))
.append("text")
.attr("fill", "#000")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", "0.71em")
.attr("text-anchor", "end")
.text("Price ($)");
g.append("path")
.datum(data)
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("stroke-linejoin", "round")
.attr("stroke-linecap", "round")
.attr("stroke-width", 1.5)
.attr("d", line);
});
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg width="960" height="500"></svg>
Hope this helps.
I'm trying to draw a Area chart . I couldn't access to the data and I don't know WHY . In the firefox console " data is not defined". This is my code and data .
This data which is JSON format is located on the server side :
{"Id":466,"Name":"korea",
"Occurrences":[
{"OccurrenceDate":"\/Date(1398207600000+0100)\/","OccurrenceFrequency":27},
{"OccurrenceDate":"\/Date(1398726000000+0100)\/","OccurrenceFrequency":1},
{"OccurrenceDate":"\/Date(1398898800000+0100)\/","OccurrenceFrequency":4},
{"OccurrenceDate":"\/Date(1399071600000+0100)\/","OccurrenceFrequency":303}
]
}
This is my code
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://code.jquery.com/jquery-1.4.2.min.js"></script>
<script>
var margin = {top: 80, right: 80, bottom: 80, left: 80},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var parseDate1 = d3.time.format.iso.parse;
// Scales and axes. Note the inverted domain for the y-scale: bigger is up!
var x = d3.time.scale().range([0, width]),
y = d3.scale.linear().range([height, 0]),
xAxis = d3.svg.axis().scale(x).tickSize(-height).tickSubdivide(true),
yAxis = d3.svg.axis().scale(y).ticks(4).orient("right");
// An area generator, for the light fill.
var area = d3.svg.area()
.interpolate("monotone")
.x(function(d) { return x(d.OccurrenceDate); })
.y0(height)
.y1(function(d) { return y(d.OccurrenceFrequency); });
// A line generator, for the dark stroke.
var line = d3.svg.line()
.interpolate("monotone")
.x(function(d) { return x(d.OccurrenceDate); })
.y(function(d) { return y(d.OccurrenceFrequency); });
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var strURI = "http://tr-l6/STWebService/Service.svc/session/Fetchnodehistorybyname?
strnodename=korea";
// use jquery to get the json dataset because I cannot get d3.json to work with Firefox/Chrome (but it is OK with IE)
// this is probably a jsonp / cross domain issue that requires further tweaking in the WCF web,config file
// d3.json(strURI,function(error, graph) {
$.ajax({
type: "GET",
url: strURI,
dataType: 'jsonp',
success: function (graph) {
x
data.forEach(function(d) {
// Convert unix date format to regular format
var dc
//dc = (d.date).substring(1, 26);
dc = d.OccurrenceDate.substring(6, 16)
console.log(dc);
dc = new Date(dc*1000)
console.log(dc);
d.OccurrenceDate= parseDate1(dc);
d.OccurrenceFrequency = +d.OccurrenceFrequency;
return d
});
x.domain(d3.extent(data, function(d) { return d.OccurrenceDate; }));
y.domain([0, d3.max(data, function(d) { return d.OccurrenceFrequency; })]);
svg.append("path")
.datum(data)
.attr("class", "area")
.attr("d", area);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
}});
I dont know if it is the reason for the Error but you missed the </script> Script at the End