Is it possible to put D3 code into a function and then call the function?
For example, I am interested in using this histogram code
http://bl.ocks.org/3048450
If I put code in a function and call like
function hist(bin, data) {
//the D3 histogram plotting code
// Generate an Irwin–Hall distribution of 10 random variables.
var values = d3.range(1000).map(d3.random.irwinHall(10));
// A formatter for counts.
var formatCount = d3.format(",.0f");
var margin = {top: 10, right: 30, bottom: 30, left: 30},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.scale.linear()
.domain([0, 1])
.range([0, width]);
// Generate a histogram using twenty uniformly-spaced bins.
var data = d3.layout.histogram()
.bins(x.ticks(20))
(values);
var y = d3.scale.linear()
.domain([0, d3.max(data, function(d) { return d.y; })])
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
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 bar = svg.selectAll(".bar")
.data(data)
.enter().append("g")
.attr("class", "bar")
.attr("transform", function(d) { return "translate(" + x(d.x) + "," + y(d.y) + ")"; });
bar.append("rect")
.attr("x", 1)
.attr("width", x(data[0].dx) - 1)
.attr("height", function(d) { return height - y(d.y); });
bar.append("text")
.attr("dy", ".75em")
.attr("y", 6)
.attr("x", x(data[0].dx) / 2)
.attr("text-anchor", "middle")
.text(function(d) { return formatCount(d.y); });
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
}
hist(...); //call the function
it doesn't plot. Why is that?
I found the cause of the bug. I misspell "function" as "function"
Where are you including and running that code? If you're including and runningit in the <head> element, then when the script executes the browser will not be aware of the <body> element. d3.select("body") will return an empty selection, and therefore there will be nothing to which to append and svg element.
Try putting the script within the <body> or use a library like jQuery (i.e., $(document).ready()) to ensure that the document has been loaded before executing your script.
Related
I'm new to d3js and i'm trying to manipulate a simple graph with 2 axis and some rect to show some data.
I've set the range of data to my y axis with some object name. This object has also a type "technical" or "canonical".
I'm trying to replace this "technical" or "canonical" with a bootstrap's glyphicon.
I've tried to replace the datas from the range with a internal text containing the proper glyphicon but without success
//datas is the data structure containing my chart datas.
//objects will be the array use for the domain
var objects = datas.map(function (d) {
return d.object + getExchangeObjectType(d.type);
});
var margin = {top: 40, right: 20, bottom: 200, left: 400},
width = 1200 - margin.left - margin.right,
height = (objects.length*30) - margin.top - margin.bottom;
var canonical = "<span class='glyphicon glyphicon-copyright-mark'></span>";
var technical = "<span class='glyphicon glyphicon-wrench'></span>";
function getExchangeObjectType(type){
if (type == 'Technical')
return technical;
else
return canonical;
}
//datas is the data structure containing my chart datas.
//objects will be the array use for the domain
var objects = datas.map(function (d) {
return d.object + getExchangeObjectType(d.type);
});
var x = d3.scale.ordinal().rangePoints([0, width]);
var y = d3.scale.ordinal().rangeBands([height, 0],.1,.1);
// define x & y axis
var xAxis = d3.svg.axis()
.scale(x)
.orient("top")
.ticks(percents.length)
;
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.ticks(objects.length)
;
// define the domain of datas
y.domain(objects);
x.domain(percents);
Here is the svg part:
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 + ")");
// draw x axis
svg.append("g")
.attr("class", "x axis")
.call(xAxis)
.selectAll("text")
.attr("x",20)
.style("text-anchor", "end")
;
// draw y axis
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.selectAll("text")
.style("text-anchor", "end")
;
svg.selectAll("bar")
.data(datas)
.enter().append("rect")
.style("fill", function (d) { return getColor(d.value);})
.attr("y", function(d){return d.object + getExchangeObjectType(d.type);})
.attr("height", y.rangeBand())
.attr("x", 0 )
.attr("width", function(d) { return ( d.value * width) / 100 ; })
;
I've found out my solution use the unicode !
I've added my type in the label text and then replace it with an unicode :
.text(function(d){
var oldText=d.split(":");
if (oldText[1]=="Canonical")
return oldText[0]+" \ue194";
else
return oldText[0]+" \ue136"
})
here's my d3.js function to visualize a bar graph:
function barGraph(data1)
{
// console(data1.count);
var i = 0;
data1.forEach(function(d){
while(i>0){
d.avspeed= +d.avspeed;
d.duration = +d.duration;
i--;
}
})
//console.log(data1.avspeed);
// console.log(data1.avspeed);
var margin = {top: 40, right: 20, bottom: 30, left: 40},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
//console.log(data1.avspeed);
console.log("hey1");
var formatPercent = d3.format("");
console.log("hey2");
var x = d3.scale.ordinal()
.rangeRoundBands([0, width], .1);
var y = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
console.log("hey");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.tickFormat(formatPercent);
console.log("hey3");
var tip = d3.tip()
.attr('class', 'd3-tip')
.offset([-10, 0])
.html(function(d) {
return "<strong>Avg Speed:</strong> <span style='color:red'>" + d.avspeed + "</span>";
})
var svg = d3.select("#rightside").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 + ")");
svg.call(tip);
console.log("heyllo ");
x.domain(data1.map(function(d) { return d.tripid; }));
y.domain([0, d3.max(data1, function(d) { return d.avspeed; })]);
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")
.text("Avg Speed");
svg.selectAll(".bar")
.data(data1)
.enter().append("rect")
.attr("class", "bar")
.attr("x", function(d) { return x(d.tripid); })
.attr("width", x.rangeBand())
.attr("y", function(d) { return y(d.avspeed); })
.attr("height", function(d) { return height - y(d.avspeed); })
.on('mouseover', tip.show)
.on('mouseout', tip.hide)
console.log(d.avspeed);
console.log("hello234");
function type(d) {
d.avspeed = +d.avspeed;
return d;
}
}
It displays a graph based on the selected region on the map dynamically. If i select new region, another graph is being created below the old graph. I want the old graph to clear and new graph to be in place of old graph. How do I achieve that.
I am new to d3.js
Thanks in advance.
You can clear away anything inside of the parent container with .html("") while creating your svg:
var svg = d3.select("#rightside").html("").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 + ")");
remove also works but I prefer this slightly terser way of doing it.
Before appending the new avg, just call d3.select('svg').remove(); That will remove the first svg on the page (I'm assuming you only have one). If you don't have an svg, it won't fail, so you can always call it.
Looking at this Histogram chart using d3 example I plugged in my data but it had some strange side effects e.g. after refreshing to a new dataset, some information from the previous dataset i.e. x-axis scale was retained. I tried deleting and appending a new x-axis etc but nothing worked.
This happened due to the fact that my datasets had completely different x-axis ranges and scales. The only way I found to make it work was to select the whole svg element, remove it and re-append everything anew. However, this doesn't make a pleasant transition for the user so I was wondering how can this be improved to make it refreshable using transitions as in the original example even when having datasets with different x-scales and ranges.
This was my last approach which is a bit harsh to the eye:
// delete old
d3.select("#" + divId).select("svg").remove();
// then recreate all new
And this was my refresh attempt (integrated with AngularJS). Note how it has some common initialization and then if the SVG doesn't exist appends everything new otherwise tries to update it. I went bit by bit but can't see why the refresh doesn't remove all the previous dataset information of the x-axis scale:
var divId = $scope.histogramData.divId;
var color = $scope.histogramData.color;
var values = $scope.histogramData.data[$scope.histogramData.selected];
var svg = $scope.histogramData.svg;
// plot common initialization
var margin = {top: 40, right: 20, bottom: 20, left: 20},
width = 450 - margin.left - margin.right,
height = 370 - margin.top - margin.bottom;
var max = d3.max(values);
var min = d3.min(values);
var x = d3.scale.linear()
.domain([min, max])
.range([0, width]);
// generate a histogram using twenty uniformly-spaced bins.
var data = d3.layout.histogram()
.bins(x.ticks(10))
(values);
var yMax = d3.max(data, function(d){ return d.length });
var yMin = d3.min(data, function(d){ return d.length });
var colorScale = d3.scale.linear()
.domain([yMin, yMax])
.range([d3.rgb(color).brighter(), d3.rgb(color).darker()]);
var y = d3.scale.linear()
.domain([0, yMax])
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
// ===================================================================
// If the SVG doesn't exist then adds everything new
// ===================================================================
if (svg === undefined) {
var svg = d3.select("#" + divId)
.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 + ")");
$scope.histogramData.svg = svg;
var bar = svg.selectAll(".bar")
.data(data)
.enter()
.append("g")
.attr("class", "bar")
.attr("transform", function(d) { return "translate(" + x(d.x) + "," + y(d.y) + ")"; });
bar.append("rect")
.attr("x", 1)
.attr("width", (x(data[0].dx) - x(0)) - 1)
.attr("height", function(d) { return height - y(d.y); })
.attr("fill", function(d) { return colorScale(d.y) });
bar.append("text")
.attr("dy", ".75em")
.attr("y", -12)
.attr("x", (x(data[0].dx) - x(0)) / 2)
.attr("text-anchor", "middle")
.text(function(d) { return formatCount(d.y); });
var gTitle = svg.append("text")
.attr("x", 0)
.attr("y", 0 - (margin.top / 2))
.attr("text-anchor", "left")
.classed("label", true)
.text($scope.histogramData.spec[selected]);
$scope.histogramData.gTitle = gTitle;
var gAxis = svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
$scope.histogramData.gAxis = gAxis;
} else {
// ===================================================================
// If the SVG does exist then tries refreshing
// ===================================================================
var bar = svg.selectAll(".bar").data(data);
// remove object with data
bar.exit().remove();
bar.transition()
.duration(1000)
.attr("transform", function(d) { return "translate(" + x(d.x) + "," + y(d.y) + ")"; });
bar.select("rect")
.transition()
.duration(1000)
.attr("height", function(d) { return height - y(d.y); })
.attr("fill", function(d) { return colorScale(d.y) });
bar.select("text")
.transition()
.duration(1000)
.text(function(d) { return formatCount(d.y); });
var gTitle = $scope.histogramData.gTitle;
gTitle.transition()
.duration(1000)
.text($scope.histogramData.spec[selected]);
var gAxis = $scope.histogramData.gAxis;
gAxis.transition()
.duration(1000)
.call(xAxis);
}
I would suggest to keep this d3 code inside one angularJS directive and keep a watch on the json which you are using to plot that graph. As soon as values are changing the directive will be called again and the graph will be plotted. Hope it helps.
I am trying to populate a data set into D3's Bar chart data. I am using this example from the d3:
https://bl.ocks.org/mbostock/1134768
var causes = ["wounds", "other", "disease"];
var parseDate = d3.time.format("%m/%Y").parse;
var margin = {top: 20, right: 50, bottom: 30, left: 40},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
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 + ")");
d3.tsv("data.csv", function(error, crimea) {
if (error) throw error;
var layers = d3.layout.stack()(causes.map(function(c) {
return crimea.map(function(d) {
return {x: parseDate(d.date), y: +d[c]};
});
}));
var x = d3.scale.ordinal()
.domain([0,1])
.rangeRoundBands([0, width], 0.1, 0);
var y = d3.scale.linear()
.rangeRound([height, 0]);
var z = d3.scale.category10();
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.tickFormat(d3.time.format("%b"));
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
console.log(layers);
x.domain(layers[0].map(function(d) { return d.x; }));
y.domain([0, d3.max(layers[layers.length - 1], function(d) { return d.y0 + d.y; })]).nice();
var ticks = x.domain().filter(function(d,i){ return !(i%20); } );
xAxis.tickValues( ticks );
var layer = svg.selectAll(".layer")
.data(layers)
.enter().append("g")
.attr("class", "layer")
.style("fill", function(d, i) { return z(i); });
layer.selectAll("rect")
.data(function(d) { return d; })
.enter().append("rect")
.attr("x", function(d) { return x(d.x); })
.attr("y", function(d) { return y(d.y + d.y0); })
.attr("height", function(d) { return y(d.y0) - y(d.y + d.y0); })
.attr("width", x.rangeBand() - 1);
svg.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "axis axis--y")
.attr("transform", "translate(" + 0 + ",0)")
.call(yAxis);
});
This issue I am getting is that I have some white space which looks ugly. This space come before the 1st bar and also after the last bar of the chart. I have tried tweaking the x value of the bar, But I think that is not a good way to do.
This space does not come when the data set is small. But when dataset is large then this space comes up. How can I remove this space from the start and from end.
JSFiddle For the Above code is
https://jsfiddle.net/7qnngbdc/
See here --> https://github.com/mbostock/d3/wiki/Ordinal-Scales#ordinal_rangeRoundBands
"Note that rounding necessarily introduces additional outer padding which is, on average, proportional to the length of the domain. For example, for a domain of size 50, an additional 25px of outer padding on either side may be required. Modifying the range extent to be closer to a multiple of the domain length may reduce the additional padding."
After you've set the domain, try this -->
var mult = Math.max (1, Math.floor (width / x.domain().length));
x.rangeRoundBands ([0, (x.domain().length * mult)], 0.1, 0);
Changed in https://jsfiddle.net/7qnngbdc/1/
I am trying to essentially rotate this horizontal bar chart into a vertical bar chart, but can't figure out how to do so. I can create a normal column chart, but once I try to put in the negative values and compute the y and height, all hell breaks loose. Here's my fiddle. (At least I was able to create the y-axis (I think).)
What am I doing wrong here?
var data = [{"letter":"A",'frequency':10},{"letter":"B","frequency":-5},{"letter":"C","frequency":7}];
var margin = {top: 20, right: 20, bottom: 30, left: 40}, width = 750 - margin.left - margin.right, height = 500 - margin.top - margin.bottom;
var x = d3.scale.ordinal().rangeRoundBands([0, width], .1);
var y = d3.scale.linear().range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
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 x0 = Math.max(-d3.min(data), d3.max(data));
x.domain(data.map(function(d) { return d.letter; }));
y.domain([d3.min(data, function(d) { return d.frequency; }), d3.max(data, function(d) { return d.frequency; })]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
svg.selectAll(".bar")
.data(data).enter().append("rect")
.attr("class", "bar")
.attr("x", function(d, i) { return x(d.letter); })
.attr("y", function(d, i) { return x(Math.min(0, d.frequency));})
.attr("width", x.rangeBand())
.attr("height", function(d) { return Math.abs(x(d.frequency) - x(0)); });
Looks like there are two problems here:
The typos: .attr("y", function(d, i) { return x(...);}) should now be .attr("y", function(d, i) { return y(...);}). Same is true for the scales in your height attribute.
The change from a 0 base on the X axis to a 0 base on the Y axis. With a zero-based bar on the X axis, the x attribute of the bar is x(0). With a 0 based bar on the Y axis, the y attribute of the bar is not y(0), but y(value) (because the "base" of the bar is no longer the leading edge of the rectangle) - so in this code you need to use Math.max(0, value) (which will give y(value) for positive values) instead of Math.min(0, value):
svg.selectAll(".bar")
// ...snip...
.attr("y", function(d, i) { return y(Math.max(0, d.frequency));})
.attr("height", function(d) { return Math.abs(y(d.frequency) - y(0)); });
See updated fiddle: http://jsfiddle.net/pYZn8/5/