I'm new to d3 and I'm trying to do some data visualization with it. I found some examples about how to create a time scale in d3, but when I followed the examples and try to create the time scale, it failed. I was frustrated because I couldn't figure out where it went wrong... the example is like this:
how to format time on xAxis use d3.js
So my code is like this:
boxplotdata =[]
boxplotdata.push(
{"datetime":"2013-10-30 01:47",length: 500, start:100,deep1_a:130,deep1:50,deep2_a:200,deep2:60,deep3_a:280,deep3:50,deep4_a:350,deep4:60},
{"datetime":"2013-10-31 01:45",length: 600, start:200,deep1_a:230,deep1:60,deep2_a:300,deep2:60,deep3_a:380,deep3:50,deep4_a:450,deep4:60},
{"datetime":"2013-11-01 02:11",length: 550,start:150,deep1_a:180,deep1:50,deep2_a:250,deep2:60,deep3_a:350,deep3:50,deep4_a:410,deep4:60},
{"datetime":"2013-11-02 01:59",length: 500,start:160,deep1_a:190,deep1:80,deep2_a:300,deep2:60,deep3_a:370,deep3:50,deep4_a:430,deep4:60},
);
//SET MARGIN FOR THE CANVAS
var margin = {top: 30, right: 10, bottom: 10, left: 10},
width = 960 - margin.left - margin.right,
height = 600 - margin.top - margin.bottom;
var parseDate = d3.time.format("%Y-%m-%d %H:%M").parse;
//SET X AND Y
var x = d3.time.scale()
.domain([0,11])
.range([50, width]);
var y = d3.time.scale()
.domain([new Date(boxplotdata[0].datetime),d3.time.day.offset(new Date(boxplotdata[boxplotdata.length-1].datetime),1)])
.rangeRound([20, height-margin.top- margin.bottom]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("top")
.tickFormat(d3.time.format("%H:%M"))
//.ticks(d3.time.minutes,15)
//.tickPadding(8);
var yAxis = d3.svg.axis()
.scale(y)
.orient('right')
.ticks(d3.time.days,1)
.tickFormat(d3.time.format('%m-%d'))
.tickSize(0)
.tickPadding(8);
var line = d3.svg.line()
.x(function(d) { return x(d.datetime); });
var w=960,h=1000;
d3.select("#chart").selectAll("svg").remove(); //Old canvas must be removed before creating a new canvas.
var svg=d3.select("#chart").append("svg")
//.attr("width",w).attr("height",h);
.attr("width",w+margin.right+margin.left).attr("height",h+margin.top+margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
boxplotdata.forEach(function(d) {
d.datetime = parseDate(d.datetime);
});
x.domain(d3.extent(boxplotdata, function(d) { return d.datetime; }));
bars = svg.selectAll("g")
.data(boxplotdata)
.enter()
.append("g");
some drawing codes here..., and at last:
svg.append("g")
.attr("class", "x axis")
//.attr('transform', 'translate(0, ' + (height - margin.top - margin.bottom) + ')')
.call(xAxis);
svg.append("g")
.attr('class', 'y axis')
.call(yAxis);
However, when I tried, I could only get a graph with all time on the xAxis shown as "00:00". What's going wrong here? Hope someone can help me out. Thanks!
Your example data is from different days, so what's happening here is that D3 is picking representative values (i.e. the boundaries between days) and making ticks for that. As your date format only shows hour and minute, 00:00 is all you get.
You have basically two options here. You could either change the date format to show days (which is what D3 intends), or you could set the tick values explicitly to whatever you want. For the first, you could use e.g. d3.time.format("%a"). For the second, see the documentation.
Hi You can use the tickFormat function on the axis
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.tickFormat(d3.time.format("%H"));
Related
How does one make a basic scatter plot like the one below using Plottable.js?
Is there something wrong with my JSON?
How to reveal the minus scales?
Would you have done anything else differently?
Style doesn't matter, the default Plottable.js one is fine.
window.onload = function() {
var coordinates = [
{
x:"-5",
y:"3"
}, {
x:"2",
y:"-1,5"
}, {
x:"5",
y:"2,5"
}
];
var xScale = new Plottable.Scale.Linear();
var yScale = new Plottable.Scale.Linear();
var colorScale = new Plottable.Scale.Color("10");
var xAxis = new Plottable.Axis.Numeric(xScale, "bottom");
var yAxis = new Plottable.Axis.Numeric(yScale, "left");
var plot = new Plottable.Plot.Scatter(xScale, yScale)
.addDataset(coordinates)
.project("x", "", xScale)
.project("y", "", yScale)
.project("fill", "", colorScale);
var chart = new Plottable.Component.Table([
[yAxis, plot],
[null, xAxis]
]);
chart.renderTo("#my_chart");
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Test</title>
<link rel="stylesheet" href="https://rawgit.com/palantir/plottable/develop/plottable.css">
</head>
<body>
<svg width="100%" height="600" id="my_chart"></svg>
<script src="https://rawgit.com/mbostock/d3/master/d3.min.js"></script>
<script src="https://rawgit.com/palantir/plottable/develop/plottable.min.js"></script>
</body>
</html>
Mark has the right idea - the table system doesn't natively support this layout, so you need to take some manual control over how they are laid out. However, using somewhat obscure parts of the Plottable API, there is a cleaner and better-supported way to lay out the chart you want, which doesn't have the problem of the axes being slightly offset.
The first change is we are going to stop using the table layout engine entirely, since it isn't able to do what we want. Instead, we will plop all the components together in a Component.Group. A Group just overlays components in the same space without trying to position them at all.
var chart = new Plottable.Component.Group([yAxis, xAxis, plot]);
Then we are going to use the alignment and offset methods that are defined on the base (abstract) component class. We set the x-alignment of the y axis to "center" and the y-alignment of the x axis to "center" This will put the axes in the center of the chart.
var xAxis = new Plottable.Axis.Numeric(xScale, "bottom").yAlign("center");
var yAxis = new Plottable.Axis.Numeric(yScale, "left").xAlign("center");
We're not quite done at this point, since to really center the axes we need to shift them back by one half of their own width. The width is only calculated when the chart is rendered (strictly speaking, in the computeLayout call, but that is an internal detail), so we need to set an offset after the chart is rendered:
chart.renderTo("#plottable");
xAxis.yOffset(xAxis.height()/2);
yAxis.xOffset(-yAxis.width()/2);
You can see the final result here (it's a fork of Mark's plnkr). Note that now the axes are aligned on the center of the chart, as the center dot is perfectly on 0,0.
Here's a couple examples I just put together. The first is the straight d3 way of doing what you are asking. The second is a hacked up plottable.js. With plottable.js I can't find a way to position the axis outside of their table system, I had to resort to manually moving them. The table system they use is designed to relieve the developer of having to manually position things. This is great and easy, of course, until you want to control where to position things.
Here's the hack, after you render your plottable:
// move the axis...
d3.select(".y-axis")
.attr('transform',"translate(" + width / 2 + "," + 0 + ")");
d3.select(".x-axis")
.attr("transform", "translate(" + 48 + "," + height / 2 + ")");
Note, I didn't remove the left side margin (the 48 above) that plottable puts in. This could be hacked in as well, but at that point, what is plottable providing for you anyway...
It should be noted that the different appearance of each plot is entirely controlled through the CSS.
Complete d3 scatter plot:
// D3 EXAMPLE
var margin = {
top: 20,
right: 20,
bottom: 20,
left: 20
},
width = 500 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.scale.linear()
.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 svg = d3.select("#d3").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 + ")");
x.domain([-100, 100]);
y.domain([-100, 100]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(" + 0 + "," + height / 2 + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + width / 2 + "," + 0 + ")")
.call(yAxis)
.append("text");
svg.selectAll(".dot")
.data(data)
.enter().append("circle")
.attr("class", "dot")
.attr("r", function(d) {
return d.r;
})
.attr("cx", function(d) {
return x(d.x);
})
.attr("cy", function(d) {
return y(d.y);
})
.style("fill", function(d) {
return d.c;
});
Plottable.js:
// PLOTTABLE.JS
var xScale = new Plottable.Scale.Linear();
var yScale = new Plottable.Scale.Linear();
var xAxis = new Plottable.Axis.Numeric(xScale, "bottom");
var yAxis = new Plottable.Axis.Numeric(yScale, "left");
var plot = new Plottable.Plot.Scatter(xScale, yScale);
plot.addDataset(data);
function getXDataValue(d) {
return d.x;
}
plot.project("x", getXDataValue, xScale);
function getYDataValue(d) {
return d.y;
}
plot.project("y", getYDataValue, yScale);
function getRDataValue(d){
return d.r;
}
plot.project("r", getRDataValue);
function getFillValue(d){
return d.c;
}
plot.project("fill", getFillValue);
var chart = new Plottable.Component.Table([
[yAxis, plot],
[null, xAxis]
]);
chart.renderTo("#plottable");
d3.select(".y-axis")
.attr('transform',"translate(" + width / 2 + "," + 0 + ")");
d3.select(".x-axis")
.attr("transform", "translate(" + 48 + "," + height / 2 + ")");
I have currently have a quick test for a graph I'm about to create for website and I have made the most basic functionality. I have a graph, a 4 elements and an x and a y axis and a zoom functionality.
My problem lies in the fact that when I zoom on the graph, the elements are able to reach the axis and overlap it. I've pasted my source code below
//Setting generic width and height values for our SVG.
var margin = {top: 60, right: 0, bottom: 60, left: 40},
width = 1024 - 70 - margin.left - margin.right,
height = 668 - margin.top - margin.bottom;
//Other variable declarations.
//Creating scales used to scale everything to the size of the SVG.
var xScale = d3.scale.linear()
.domain([0, 1024])
.range([0, width]);
var yScale = d3.scale.linear()
.domain([1, 768])
.range([height, 0]);
//Creates an xAxis variable that can be used in our SVG.
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(yScale)
.orient("left");
//Zoom command ...
var zoom = d3.behavior.zoom()
.x(xScale)
.y(yScale)
.scaleExtent([1, 10])
.on("zoom", zoomTargets);
// The mark '#' indicates an ID. IF '#' isn't included argument expected is a tag such as "svg" or "p" etc..
var SVG = d3.select("#mainSVG")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.call(zoom);
//Create background. The mouse must be over an object on the graph for the zoom to work. The rectangle will cover the entire graph.
var rect = SVG.append("rect")
.attr("width", width)
.attr("height", height);
//Showing the axis that we created earlier in the script for both X and Y.
var xAxisGroup = SVG.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
var yAxisGroup = SVG.append("g")
.attr("class", "y axis")
.call(yAxis);
//This selects 4 circles (non-existent, there requires data-binding) and appends them all below enter.
//The amount of numbers in data is the amount of circles to be appended in the enter() section.
var circle = SVG
.selectAll("circle")
.data([40,100,400,1900])
.enter()
.append("circle")
.attr("cx",function(d){return xScale(d)})
.attr("cy",function(d){return yScale(d)})
.attr("r",20);
//Resets zoom when click on circle object. Zoom work now, should be changed to a button instead of click on circle though.
SVG.selectAll("circle").on("click", function() {
zoom.scale(1);
zoom.translate([0,0]);
zoomTargets();
});
function zoomTargets() {
SVG.select(".x.axis").call(xAxis);
SVG.select(".y.axis").call(yAxis);
SVG.selectAll("circle").attr("cx",function(d){return xScale(d)}).attr("cy",function(d){return yScale(d)});
}
function resetZoom() {
zoom.scale(1);
zoom.translate([0,0]);
zoomTargets();
}
I've tried using "append("g2") before creating a circle to I can make g2 smaller than the entire svg, but that doesn't seem to work. As far as I have understood, you can just append a new element inside your existing one. I'm guessing I'm wrong since it hasn't worked for me.
Thank you in advance for your help.
Leave a small gap between the most extreme data point and the axis. In particular, you may want the range of your domain to take the margins into account:
var xScale = d3.scale.linear()
.domain([0, 1024])
.range([0, width-margin.right]);
var yScale = d3.scale.linear()
.domain([1, 768])
.range([height, margin.bottom]);
I am trying to develop a scatterplot using d3 but the domain for y-axis is confusing me. y-axis are gonna display patient names and x-axis display their appointment dates. x-axis are working fine, but y-axis are displaying only two patient names.
function graph() {
var num_patient = Object.keys(patientList).length;
var patient_names = Object.keys(patientList);
console.log(patient_names);
var x = d3.time.scale().range([0, width]);
var y = d3.scale.ordinal().range([height, 0]);
x.domain(d3.extent(data, function(d) {return parseDate(d.dates); }));
//y.domain(patient_names.map(function(d) { return d.name;}));
y.domain(patient_names);
console.log(y.domain());
var xAxis = d3.svg.axis()
.scale(x)
.ticks(d3.time.year, 1)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var svg = d3.select("#punchcard")
.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.selectAll("dot")
.data(data)
.enter()
.append("circle")
.attr("r", 3.5)
.attr("cx", function(d) { return x(d.date); })
.attr("cy", function(d) { return y(d.value); });
svg.append("g") // Add the X Axis
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g") // Add the Y Axis
.attr("class", "y axis")
.call(yAxis);
}
console.log(patient_names) display the names correctly:
`["Andrew","Fred","Steve","John"]`
console.log(y.domain()) displays an extra undefined object:
["Andrew", "Fred","Steve" , "John", undefined]
But the y-axis only display Andrew at 0 and Fred at height h. How can I get to display all four names? I cannot hard code them as they are user input values. BTW: I am a beginner with d3 and js.
Thanks in advance!
With ordinal scales, you need to define the range points for the inputs explicitly (see the documentation). That is, you need to tell the scale explicitly which input value to map to which output. For example:
var y = d3.scale.ordinal()
.domain(["Andrew","Fred","Steve","John"])
.range([height, height * 2/3, height * 1/3, 0]);
You probably want to use the .rangePoints() method instead, which allows you to specify an interval that D3 automatically divides based on the number of values in the domain:
var y = d3.scale.ordinal()
.domain(["Andrew","Fred","Steve","John"])
.rangePoints([height, 0]);
Note that for .rangePoints() to work properly, you need to set the domain before the output range.
I am trying to figure out how to make a zoomable y axis that is in hours. I want the y axis to range from 7AM to 5PM. My x axis in days.
Currently, I have the y axis in hours but it only shows 12AM at the top of the y axis. I am unsure of where to get it to go from 7AM to 5PM. My code does zoom on both of the axis.
Any help would be appreciated :)
// Define the min and max date
var mindate = new Date(2013,0,20), // TODO: clip date
maxdate = new Date(2013,0,25);
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.time.scale()
.domain([mindate, maxdate])
.range([0, width]);
var y = d3.time.scale()
.domain([mindate, maxdate])
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.ticks(d3.time.days, 1)
.tickFormat(d3.time.format("%A : %d")); // d is for testing
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.ticks(d3.time.days, 12)
.tickFormat(d3.time.format("%I %p")); // For 12 hour time
var zoom = d3.behavior.zoom()
// .x(x)
.y(y)
.scaleExtent([1, 10])
.on("zoom", zoomed);
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 + ")")
.call(zoom);
svg.append("rect")
.attr("width", width)
.attr("height", height);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
function zoomed() {
svg.select(".x.axis").call(xAxis);
svg.select(".y.axis").call(yAxis);
I'm not 100% sure if this is what you're after, but I've interpreted your question as how to create a scale from 7am to 5pm on the same day with 1 hour steps. The first thing was to create a set of dates for the y-scale for the required time as in:
var ymindate = new Date(2013,0,20, 7), // TODO: clip date
ymaxdate = new Date(2013,0,20, 17);
The next step required only minor changes to your code. the ticks were set to d3.time.hours not days and the step was set to 1 hour, as shown below.
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.ticks(d3.time.hours, 1)
.tickFormat(d3.time.format("%I %p"));
If you want just 7am and 5pm to appear on multiple days you would have to create a custom set of ticks and use a time intervals
You would need to set the domain of the y scale accordingly. At the moment, your min and max dates are both full days, so you get only one value for the time. If you used for example
var mindate = new Date(2013,0,20, 7, 0, 0, 0),
maxdate = new Date(2013,0,25, 17, 0, 0, 0);
you should get the times you want on the y axis.
i am trying to build a plugin to display a year view of the events on a google calendar account, i am trying to build this view using D3 and fetching the data from the google calendar API (this part sounds easy so far), but i'm stuck trying to actually build the view in d3, here is what i have so far:
My code :
var margin = {top: 40, right: 40, bottom: 40, left: 40},
width = 960,
height = 500;
var x = d3.time.scale()
.domain([new Date(2013, 0, 1), new Date(2014, 0, 1)])
.range([0, width])
var y = d3.time.scale()
.domain([new Date(2013,0,1), new Date(2013, 0,31)])
.range([0, height])
var xAxis = d3.svg.axis()
.scale(x)
.orient("top")
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.ticks(d3.time.days, 1)
.tickFormat(d3.time.format('%e'))
var svg = d3.select(".container").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 + ")");
function makeXaxis() {
return d3.svg.axis()
.scale(x)
.orient("top")
}
function makeYaxis() {
return d3.svg.axis()
.scale(y)
.orient("left")
.ticks(d3.time.days, 1)
.tickFormat(d3.time.format('%e'))
}
svg.append("g")
.attr("class", "x axis")
// .attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(0,0)")
.call(yAxis);
svg.append("g")
.attr("class", "grid")
.call(makeYaxis()
.tickSize(-width, 0, 0)
.tickFormat("")
.tickPadding(8)
)
svg.append("g")
.attr("class", "grid")
.call(makeXaxis()
.tickSize(-height, 0, 0)
.tickFormat("")
)
What it renders:
actual view calendar
What i want to achieve:
goal calendar
There are a few problem with my code,
the first being that my y domain is an arbitrary month of 31 days, i don't know if my data will place itself on the graph well if it is a time scale, should i use a linear scale of 31 ?
How can i just render January...December without the 2013 and 2014
How can i display the months in the middle top of each row instead of displaying them on every tick ?
Well , i am not going to ask for more considering i am already asking for alot.
Thanks everyone, i am totally new to d3, i have read lots of tuts tho.
My approach (not the only way):
Use linear numerical domains for both axes. In other words:
x.domain( [0, 11] ); // months, zero-indexed
y.domain( [1, 31] ); // days of month
To render the months properly, you will then have to do something kooky like:
var monthFormatter = d3.time.format( "%B" ); // %B = Month name
function makeYaxis() {
return d3.svg.axis()
.scale(y)
.orient("left")
.tickFormat( function(d) { return monthFormatter( d.date ) } )
}
As for alignment of these values, this https://groups.google.com/forum/?fromgroups=#!topic/d3-js/1gCrn0taKw8 describes how it's a non-trivial problem. You may be best to tinker with adjusting the x-values manually.
I don't see the code used to render the data on screen, but you can use something like:
svg.selectAll( rect ).data( myData ).append( "rect" )
.attr( "x", function(d) { return x( d.date.getMonth() ); } )
.attr( "y", function(d) { return x( d.date.getDate() ); } )
I imagine that having 'stacked' data (side-by-side) will be problematic, but we'll deal with that later.