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.
Related
I have created a linechart with D3.js, but can't seem to figure out how to control the X-axis completely.
You can see my example her: http://codepen.io/DesignMonkey/pen/KdOZNQ
I have sat the ticks count for the x-axis to 3 days, and I have an Array with a range of 31 days, that should show a day in each end of the x-axis and skip to every third day. But for some reason when the a-axis pass the 1st in the month, it shows both 2015-08-31 and 2015-09-01 and doesn't skip to 2015-09-03 as it is supposed to.
My code for the linechart is here:
// Set the dimensions of the canvas / graph
let margin = {top: 30, right: 20, bottom: 30, left: 40},
width = 330 - margin.left - margin.right,
height = 180 - margin.top - margin.bottom;
// Set the ranges
var x = d3.time.scale().range([0, width]).nice(10);
var y = d3.scale.linear().rangeRound([height, 0]);
// Define the axes
var xAxis = d3.svg.axis().scale(x)
.orient("bottom")
.ticks(d3.time.days, 3)
.tickFormat(d3.time.format('%e'))
.innerTickSize(-height)
.outerTickSize(0)
.tickPadding(10)
var yAxis = d3.svg.axis().scale(y)
.orient("left")
.ticks(5)
.innerTickSize(-width)
.outerTickSize(0)
.tickPadding(10)
// Define the line
var valueline = d3.svg.line()
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.value); });
// Adds the svg canvas
let svg = d3.select(template.find(".chart"))
.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 + ")");
// For each line
data.forEach((item) => {
// Scale the range of the data
x.domain(d3.extent(item.data, function(d) {
if(d.value != undefined)
return d.date;
}));
// Create a perfect looking domainrange to the nearest 10th
y.domain([
Math.floor(d3.min(item.data, function(d) {
if(d.value != undefined)
return d.value;
})/10)*10
,
Math.ceil(d3.max(item.data, function(d) {
if(d.value != undefined)
return d.value;
})/10)*10
]);
// Add the X Axis
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
// Add the Y Axis
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
// Add only points that have value
svg.append("path")
.attr("class", "line color-"+ item.colorClass)
.attr("d", valueline(item.data.filter((pnt) => pnt.value != undefined)));
});
Can somebody tell me what I'm doing wrong here? :)
/Peter
UPDATE:
I found out it's like, that it want to show the new month. See this pen: http://codepen.io/DesignMonkey/pen/jbgYZx
It doesn't say "Septemper 1" but only "Septemper" like it wants to label the change of month. How do I disable this? :)
Solution:
var count = 0;
var tickRange = data[0].data.map((item) => {
count = (count == 3) ? 0 : count+1;
if(count == 1) {
return item.date;
}
})
.filter((d) => d != undefined);
and then:
var xAxis = d3.svg.axis().scale(x)
.orient("bottom")
.ticks(d3.time.days, 3)
.tickValues(tickRange)
.tickFormat(d3.time.format('%e'))
.innerTickSize(-height)
.outerTickSize(0)
.tickPadding(10)
Thanks :)
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'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"));
Working with one bar chart with d3.js I am unable to align ticks in x axis with bars.
In left and right verges the ticks are ok, but not in the middle.
Here is the code:
var formatDate = d3.time.format("%e %b");
var height = 325;
var xTimeScale = d3.time.scale()
.domain([new Date(data[0].date), d3.time.day.offset(new Date(data[data.length - 2].date), 1)])
.range([30, width]);
var xAxis = d3.svg.axis()
.scale(xTimeScale)
.orient("bottom")
.ticks(d3.time.days, .1)
.tickFormat(formatDate);
chart.append("g")
.attr("class", "xaxis axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
chart.selectAll(".xaxis text")
.attr("transform", function(d) {return "translate(" + this.getBBox().height * -2 + "," + this.getBBox().height + ")rotate(-45)";});
What am I missing?
Thanks in advance.
Update: Here is the jsFiddle updated with chrtan suggestions.
My problem now is to align text with the center of bar and not in left.
From the looks of your graph, you lost a bar's worth of space. If all the bars are supposed to be left-aligned against a tick, that final bar you have should be to the right of the January 31st tick.
You might need to add the February 1st tick by perhaps changing the [data.length - 2] to [data.length - 1] in the domain() for your xTimeScale.
Then for display purposes, you could probably remove the last tick axis text with:
d3.select(chart.selectAll(".xaxis text")[0].pop()).remove();
The inner selectAll should get the array containing your xAxis tick texts and then pop the very last tick. This last tick should then be removed by the outer select.
An example with an auto time ticks with d3.js
// set domain for axis
var x_domain = d3.extent(data, function(d) { return new Date(d.date); });
//format date
var date_format = d3.time.format('%Y %B');
var vis = d3.select("#graph")
.append("svg:svg")
.attr('class', 'chart')
.attr("width", width)
.attr("height", height);
var yScale = d3.scale.linear()
.domain([0, d3.max(data, function(d) { return d.data; })]).nice()
.range([height - padding, padding]);
var xScale = d3.time.scale()
.domain(x_domain)
.range([padding, width - padding]);
// define the y axis
var yAxis = d3.svg.axis()
.orient("left")
.scale(yScale);
// define the x axis
var xAxis = d3.svg.axis()
.orient("bottom")
.scale(xScale)
.tickFormat(date_format);
// draw y axis with labels and move in from the size by the amount of padding
vis.append("g")
.attr("class", "axis")
.attr("transform", "translate("+padding+",0)")
.call(yAxis);
// draw x axis with labels and move to the bottom of the chart area
vis.append("g")
.attr("class", "xaxis axis")
.attr("transform", "translate(0," + (height - padding) + ")")
.call(xAxis);
// and set data in graph...
a great example : http://bl.ocks.org/phoebebright/3061203
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.