Related
I'm new to d3.js. I'm trying to plot to get the ordinal values on the x-axis, replacing the date values. I'm using the example from http://bl.ocks.org/hopelessoptimism/b8ef4734abad1c644221.
I'm having difficulty using d3.js: Ordinal scale.
I tried this link too and other alternatives with no success.
the data2 csv is:
timestamp,AJU,BEL,BPS,BSB,BVB,CGB,CGH,CGR,CNF,CWB,FLN,FOR,GIG,GRU,GYN,IGU,IMP,IOS,JOI,JPA,LDB,MAB,MAO,MCP,MCZ,NAT,NVT,PMW,POA,PVH,RAO,RBR,REC,SDU,SJP,SLZ,SSA,STM,THE,UDI,VCP,VIX
A,7,5,8,4,3,8,6,7,10,4,10,2,6,8,2,3,3,4,5,10,4,4,2,9,8,9,5,7,7,7,10,4,6,9,5,3,8,5,4,3,8,3
B,7,6,10,7,6,4,7,5,3,4,7,6,5,2,9,10,10,4,7,6,2,2,2,9,3,4,7,9,2,4,8,10,2,3,9,2,2,2,2,10,4,9
C,6,4,7,4,5,8,8,10,9,5,2,2,8,2,2,6,8,4,10,5,2,9,3,4,6,3,9,2,2,4,2,10,9,5,6,4,10,10,4,3,7,10
D,8,5,10,2,7,3,6,3,6,9,7,8,5,2,3,5,6,7,2,10,3,4,4,6,9,3,4,7,2,2,3,7,4,8,6,7,3,8,5,9,7,8
error pic
This is a part of the line code:
// maximum reviews
var max_y = d3.max(data[2], function(d) {
var max = 0;
d3.values(d).forEach(function(i) {
if (+i && (+i > max)) {
max = +i;
}
});
return max;
});
// Create y-axis scale mapping price -> pixels
var measure_scale = d3.scale.linear()
.range([height, 100])
.domain([0, max_y])
;
// Create D3 axis object from measure_scale for the y-axis
var measure_axis = d3.svg.axis()
.scale(measure_scale)
.orient("right");
// Append SVG to page corresponding to the D3 y-axis
d3.select('#chart').append('g')
.attr('class', 'y axis')
.attr("transform", "translate(" + width + " , -15)")
.call(measure_axis);
// add label to y-axis
d3.select(".y.axis")
.append("text")
.attr('class', 'label')
.text("Daily")
.attr("transform", "translate(45,110) rotate(90)");
// create a function to draw the timeseries for each neighborhood
var drawChart = function(field) {
// remove the previous chart
d3.select('#chart').select('.x.axis').remove();
d3.select('#chart').select('path').remove();
// update the title
d3.select('#heading')
.text(field);
// remove missing values
var neigh_data = data[2].filter(function(d) {
return d[field];
});
// get min/max dates
var time_extent = d3.extent(neigh_data, function(d){
return d['timestamp'];
;
});
// Create x-axis scale mapping dates -> pixels
var time_scale = d3.scale.ordinal()
.range([0, width - margin])
// .rangePoints([0, width - margin])
// .range([0, width - margin])
// .rangeBands([0, width], .1)
.domain(time_extent)
;
// Create D3 axis object from time_scale for the x-axis
var time_axis = d3.svg.axis()
.scale(time_scale)
// .ticks(10)
// .tickFormat(d3.format(""))
// .tickFormat(d3.time.format("%b '%y"))
;
// Append SVG to page corresponding to the D3 x-axis
d3.select('#chart').append('g')
.attr('class', 'x axis')
.attr('transform', "translate(" + margin + ',' + (height - 15) + ")")
.call(time_axis)
.selectAll("text")
.attr("y", 0)
.attr("x", 9)
.attr("dy", ".35em")
.attr("transform", "rotate(90)")
.style("text-anchor", "start");
// define the values to map for x and y position of the line
var line = d3.svg.line()
.x(function(d) { return time_scale(d['timestamp']); })
.y(function(d) { return measure_scale(+d[field]); });
;
// append a SVG path that corresponds to the line chart
d3.select('#chart').append("path")
.datum(neigh_data)
.attr("class", "line")
.attr("d", line)
.attr('transform', 'translate(' + margin + ', -15)');
};
drawChart(field);
// create a callback for the neighborhood hover
var mover = function(d) {
var neigh = d.iata;
d3.select('#map path.' + neigh).style('fill', '#9999ff');
drawChart(neigh);
};
Can someone tell me what am I doing wrong?
I am currently trying to transition both the x and y axis on this chart to 'grow out' from the bottom left corner from blank space into the way they currently look:
http://bl.ocks.org/maharlikans/304443d2da3f479e20e3
I want the chart to 'grow out' from the bottom left corner of the screen, mostly just for aesthetic purposes. I thought initially that I could just set the range on each axis to [0, 0] around these lines:
var x0 = d3.scale.ordinal()
.rangeRoundBands([0, width], .3);
var x1 = d3.scale.ordinal();
var y = d3.scale.linear()
.range([height, 0]);
and then call .transition().duration(someduration).range([0, myrange]) around these lines:
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("Count");
but that didn't work. I thought maybe I have the right idea but am executing the code wrong. Could anyone think of a way to do this?
To grow in an axis using a transition would be like this:
// after the rest of the plot is drawn
// y
y.range([height,height]); // set no axis
svg.select(".y.axis").call(yAxis); // draw it
y.range([height,0])
svg.select(".y.axis")
.transition()
.duration(1000)
.call(yAxis); // transition it to "grown"
// x
x0.rangeRoundBands([0, 0], .3);
svg.select(".x.axis").call(xAxis);
x0.rangeRoundBands([0, width], .3);
svg.select(".x.axis")
.transition()
.duration(1000)
.call(xAxis);
You have to do this after the rest of the plot is created since having a range of [height, height] or [0,0] will mess up the placement of other elements.
Example Here
Another possible solution that works on the yaxis and xaxis g elements and does not mess with the .range at all:
svg.select(".x.axis")
.transition()
.duration(1000)
.attrTween("transform", slideX);
function slideX() {
var pos = (500-margin.top-margin.bottom);
var interp = d3.interpolate( -pos * 2 , 0);
return function(t) {
return "translate("+interp(t)+","+ pos +")";
}
};
svg.select(".y.axis")
.transition()
.duration(1000)
.attrTween("transform", slideY);
function slideY() {
var pos = 960 - margin.left - margin.right;
var interp = d3.interpolate(pos * 2, 0);
return function(t) {
return "translate(0," + interp(t) +")";
}
};
Example Here
This example is a little more fun; with this you scale in the axis, rotate, skew it, etc...
I am trying do some path interpolation in D3. I'd like to produce an area plot like this, but I want to transition the area along the y-axis, starting from the bottom of the xaxis up to the final position shown in the example. Here's a quick sketch to explain what I'd like to do:
I'd like to start the transition with no area:
and transition it up along the y-axis:
Using the code, copied from the example, here's what I'd trying to do:
var margin = {top: 20, right: 20, bottom: 30, left: 50},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var parseDate = d3.time.format("%d-%b-%y").parse;
var x = d3.time.scale()
.range([0, width]);
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 area = d3.svg.area()
.x(function(d) { return x(d.date); })
.y0(height)
.y1(function(d) { return y(d.close); });
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.tsv", function(error, data) {
data.forEach(function(d) {
d.date = parseDate(d.date);
d.close = +d.close;
});
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain([0, d3.max(data, function(d) { return d.close; })]);
var dataSel = svg.selectAll('.area').data(data)
dataSel.exit().remove()
dataSel.enter()
.append('path')
.attr("class", "area")
.attr("d", 'M0,0h' + width) // my idea here was to draw a path that
// has no area along the x-axis and then
// interpolate the path up to the final area
dataSel.transition() // transition the path to its final position
.duration(1000)
.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")
.text("Price ($)");
});
Can anyone explain/show how I can transition a path along one axis as I'm trying to do?
D3 path transitions only really work if the starting and ending paths have the same number of control points. So, for example, D3 can't transition a line into an area. What you could do, however, is something like the following:
Use d3.area to generate the final path for the area.
Make a copy of the path and, in the copy, change all the control points that correspond to the "top" of the area to control points on the "bottom" of the area. (In other words, modify their y-values.)
Draw the area using this modified path.
Transition to the final path.
Okay, I'm starting to get a little more familiar with D3 but am still a little hazy on some things. I'm now trying to draw grid lines but am realizing that I may be hacking away versus doing it correctly. I tried to add some gridlines, using a tutorial, but ended up with a lot of code that I seem to be jimmy rigging in order to get it to line up properly. I was wondering if anyone could point me to a better way of writing this...
The original code is this.
<script type="text/javascript">
//Width and height
var w = 800;
var h = 400;
var padding = 20;
var border=1;
var bordercolor='black';
var dataset = [
[5, 20], [480, 90], [250, 50], [100, 33], [330, 95],[-50,-100],[50,-45],
[410, 12], [475, 44], [25, 67], [85, 21], [220, 88],[-480, -467], [3,-90],[468,481]
];
// create scale functions
var xScale = d3.scale.linear()
.domain([d3.min(dataset, function(d) { return d[0]; }), d3.max(dataset, function(d) { return d[0]; })])
.range([padding, w - padding * 2]);
var yScale = d3.scale.linear()
.domain([d3.min(dataset, function(d) { return d[0]; }), d3.max(dataset, function(d) { return d[1]; })])
.range([h - padding, padding]);
var rScale = d3.scale.linear()
.domain( [-100, d3.max(dataset, function(d) { return d[1]; })] )
.range([2,5]);
//Create SVG element
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h)
.attr("border",border)
;
//define X axis this is rly a function, remember, variables can hold functions in JS
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom")
.ticks(1)
.tickSize(-h, 0, 0)
; //Set rough # of ticks
//Define Y axis
var yAxis = d3.svg.axis()
.scale(yScale)
.orient("left")
.ticks(1)
.tickSize(-w, 0, 0)
;
//create the circles
svg.selectAll("circle")
.data(dataset)
.enter()
.append("circle")
.attr("cx", function(d) {
return xScale(d[0]);
})
.attr("cy", function(d) {
return yScale(d[1]);
})
.attr("r", 3);
// draw axes here
svg.append("g")
.attr("class", "axis") //assign "axis" class
.attr("transform", "translate(0," + (h - padding) +")")
.call(xAxis);
svg.append("g")
.attr("class", "axis") //assign "axis" class
.attr("transform", "translate(" + padding + ",0)" )
.call(yAxis);
// end draw axes here
</script>
and the code I added in the second link is here
var vis = svg.append("svg:g")
.attr("transform", "translate(20,0)")
var rules = vis.append("svg:g").classed("rules", true)
rules.append("svg:g").classed("grid x_grid", true)
.attr("transform", "translate(-20,"+h+")")
.call(d3.svg.axis()
.scale(xScale)
.orient("bottom")
.ticks(4)
.tickSize(-h,0,0)
.tickFormat("")
)
rules.append("svg:g").classed("grid y_grid", true)
.call(d3.svg.axis()
.scale(yScale)
.orient("left")
.ticks(5)
.tickSize(-w,0,0)
.tickFormat("")
)
rules.append("svg:g").classed("labels x_labels", true)
.attr("transform", "translate(-20,"+ h +")")
.call(d3.svg.axis()
.scale(xScale)
.orient("bottom")
.ticks(4)
.tickSize(0)
.tickFormat("")
// .tickFormat(d3.time.format("%Y/%m"))
)
rules.append("svg:g").classed("labels y_labels", true)
.call(d3.svg.axis()
.scale(yScale)
.orient("left")
.ticks(5)
.tickSubdivide(1)
.tickSize(0, 0, 0)
.tickFormat("")
)
Again, really appreciate any help
Assuming that you have Mike Bostock's standard margins defined and you have defined a linear scale for the y-axis the following code will create horizontal gridlines without using tickSize().
svg.selectAll("line.horizontalGrid").data(yScale.ticks(4)).enter()
.append("line")
.attr(
{
"class":"horizontalGrid",
"x1" : margin.right,
"x2" : width,
"y1" : function(d){ return yScale(d);},
"y2" : function(d){ return yScale(d);},
"fill" : "none",
"shape-rendering" : "crispEdges",
"stroke" : "black",
"stroke-width" : "1px"
});
I would suggest to use d3.svg.axis().scale() to tie up the grid to your coordinates. I drew a quick example based on your code: http://jsfiddle.net/temirov/Rt65L/1/
The gist is to use the existing scales, x and y, and to use ticks as grid. Since yAxis and xAxis are already defined we can just re-use them. Here is the relevant code:
//Draw a grid
var numberOfTicks = 6;
var yAxisGrid = yAxis.ticks(numberOfTicks)
.tickSize(w, 0)
.tickFormat("")
.orient("right");
var xAxisGrid = xAxis.ticks(numberOfTicks)
.tickSize(-h, 0)
.tickFormat("")
.orient("top");
svg.append("g")
.classed('y', true)
.classed('grid', true)
.call(yAxisGrid);
svg.append("g")
.classed('x', true)
.classed('grid', true)
.call(xAxisGrid);
You could use the ticks() function of your scale to get the tick values and then use them in a data call to draw the lines.
var ticks = xScale.ticks(4);
rules.selectAll("path.xgrid").data(ticks).enter()
.append("path")
.attr("d", function(d) {
return "M" + xScale(d) + " " + padding + "L" + xScale(d) + " " + (h-padding);
});
You may prefer using a line generator for the grid lines instead of creating the path manually. This works similarly for y grid lines, only that the y coordinate is constant and ranges from 0 to width of graph. You may need to adjust the start and end values to make it look "nice".
In the d3fc project we have created a gridlines component that renders in exactly the same way as the D3(v4) axis.
Here's an example of the usage:
var width = 500, height = 250;
var container = d3.select("#gridlines")
.append("svg")
.attr("width", width)
.attr("height", height);
var xScale = d3.scaleLinear()
.range([0, 100]);
var yScale = d3.scaleLinear()
.range([0, 100]);
var gridline = fc.annotationSvgGridline()
.xScale(xScale)
.yScale(yScale);
container.append("g")
.call(gridline);
Which renders as follows:
The spacing of the gridlines is determined by the ticks supplied by the associated axes.
Disclosure: I am a core contributor to the d3fc project!
Following #arete's idea, you can use the following to avoid re-drawing unnecessarily the gridline:
function createsGrid(data) {
var grid = gridLine.selectAll("line.horizontalGrid").data(scaleY.ticks());
grid.enter()
.append("line")
.attr("class","horizontalGrid");
grid.exit().remove();
grid.attr({
"x1":0,
"x2": width,
"y1": function (d) { return scaleY(d); },
"y2": function (d) { return scaleY(d); }
});
}
and define the following in your CSS file
line.horizonalGrid{
fill : none;
shape-rendering : crispEdges;
stroke : black;
stroke-width : 1.5px;
}
You could just use innerTickSize, instead of tickSize:
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom")
.ticks(1)
.innerTickSize(-h);
Use tickSizeInner()
// x axis
var x = d3.scaleLinear().range([0, width]).domain([0, 100000]);
svg
.append("g")
.call(d3.axisBottom(x).ticks(10).tickSizeInner(-height))
So I'm new to D3 and have little exp with JavaScript in general. So I have been following some tutorials am currently using source code that creates a basic scatter plot. Now my question is how do I use the transition() method to moves the circles around when I add more datasets? I want to be able to set up buttons and when a user presses them, it activates the transition() method with the corresponding dataset. The tutorial I read on transitions only showed a transition on a single rectangle and did it manually, without data, and not with multiple items
//Width and height
var w = 900;
var h = 600;
var padding = 30;
//Static dataset
var dataset = [
[50, 30], [300, 75], [123, 98], [70, 40], [247, 556],
[410, 12], [475, 44], [25, 67], [85, 21], [220, 88],
[600, 150]
];
//Create scale functions
var xScale = d3.scale.linear()
.domain([0, d3.max(dataset, function(d) { return d[0]; })])
.range([padding, w - padding * 2]);
var yScale = d3.scale.linear()
.domain([0, d3.max(dataset, function(d) { return d[1]; })])
.range([h - padding, padding]);
var rScale = d3.scale.linear()
.domain([0, d3.max(dataset, function(d) { return d[1]; })])
.range([4, 4]);
//Define X axis
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom")
.ticks(5);
//Define Y axis
var yAxis = d3.svg.axis()
.scale(yScale)
.orient("left")
.ticks(5);
//Create SVG element
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
//Create circles
svg.selectAll("circle")
.data(dataset)
.enter()
.append("circle")
.attr("cx", function(d) {
return xScale(d[0]);
})
.attr("cy", function(d) {
return yScale(d[1]);
})
.attr("r", function(d) {
return rScale(d[1]);
})
.attr("fill", "blue");
//Create X axis
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + (h - padding) + ")")
.call(xAxis);
//Create Y axis
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(" + padding + ",0)")
.call(yAxis);
First, before addressing the transition issue, you need to refactor things a bit. You're going to want to call an update(newData) function every time your data changes, and have this function do all the necessary updates.
This tutorial by mbostock describes exactly the "general update pattern" you'll need.
Parts II and III then go on to explaining how to work transitions into this pattern.
They're very short. And once you understand them, you'll have just about all the info you need to do this.
I guess you just have to specify .transition() function after .data(newData) function
In the following example Y2 is a node in a JSON file, where Y1 was the previous one used
Example:
//Creating the button
var button = d3.select("body")
.append("input")
.attr("type","button")
.attr("value", "A button");
//Transitioning process
button.on("click", function()
{ circles
.data(data.Y2)
.transition()
.attr("cx", function(d)
{
return d[0];
}
)
.attr("cy", 300);
}
)