custom ticks on an ordinal scale d3 - javascript

I've been creating some charts to plot intra-day data from financial results. Often this is a value generated every ten minutes. This varies but its a good example. Therefore there are large periods of time I won't get information, eg when markets are close over weekend and between 5:00pm in the evening and 9:00pm in the morning. I've tried created a custom time scale for the x-axis but in the end the best result is to just use an ordinal scale. it works well and gives the result that I and the people reading the chart want ie, no gaps in the line plot and evenly space data points. (Its the convention)
My question is how do I know plot custom ticks on this xAxis in the correct place, given that I generate them in an array called ticks.major. The example below shows how I generate the axis and there are the correct number of days. But they all are being plotted at the beginning of the graph. Any help appreciated thanks.
var xScale = d3.scale.ordinal()
//var xScale = scaleWeekday()...custom timescale no longer used
.domain(xDomain)
.rangeBands([0,(plotWidth-yLabelOffset)])
var xAxis = d3.svg.axis()
.scale(xScale)
//.tickValues(ticks.major)//used to create tick d3 time scale axis
.tickFormat(function (d,i) {
return ticks.major[i]
})
.tickSize(yOffset/2)
.orient("bottom");
var xLabel=plot.append("g")
.attr("class",media+"xAxis")
.attr("transform",function(){
if(yAlign=="right") {
return "translate("+(margin.left)+","+(plotHeight+margin.top)+")"
}
else {return "translate("+(margin.left+yLabelOffset)+","+(plotHeight+margin.top)+")"}
})
.call(xAxis);
it looks like this:

I think the mistake I'm making is that by using the tick.major array its applying the tick value to the first 12 dates that are passed bacause thats all thats in the tick.major array. Then because there are no more dates in the tick.majore array it has no labels for any further date in the series. Its better to apply a test in the tickFormetter to see if the current day at a particular datapoint is different from the day at the previous datapoint. Then return a tick. like this
.tickFormat(function (d,i) {
if(i>0) {
var day=d.getDay()
var yesterday=data[i-1].date.getDay()
console.log(day,yesterday)
if(day!=yesterday) {
return d
}
}
})
The returned d needs a bit of date formatting to make it readable

Related

Plotting heatmap with available attributes instead of range D3 React

I'm working on a heatmap which basically plots the graph between taxIDs and KeywordNames from an external JSON. While I'm able to plot the values I see many blank spaces on the graph and clueless how I can plot them with the available data.
Here's the link to codesandbox: https://codesandbox.io/s/40mnzk9xv4
On the X-Axis I'm plotting the TaxIDs which are being calculated within the given range. I did try using the function rangeBands() but I get an error everytime.
Its the similar case with Y-Axis where I'am plotting the keywordIDs which are also being calculated within a range. I'm trying to print all the KeywordNames on Y axis and all taxIDs on the X-Axis and plot their corresponding spectracount on graph.
Please help me where have I gone wrong.
The output I'm looking for is something similar to this: https://bl.ocks.org/Bl3f/cdb5ad854b376765fa99
Thank you.
Some things to help you get you one your way:
First, your scales should use scaleBand(), not scaleLinear(), as they have discrete domains (i.e. categories of something, rather than continuous)
Second, your scale domains is taking every value of taxId and keywordName in your data as a possible value. But some values are repeated more than once. You need to be filtering them so you only have unique values. So your scale code should be:
const xValues = d3.set(data.map(d => d.taxId)).values();
const yValues = d3.set(data.map(d => d.keywordName)).values();
const xScale = d3.scaleBand()
.range([0, width])
.domain(xValues); //X-Axis
const yScale = d3.scaleBand()
.range([0, height])
.domain(yValues); //Y-Axis
Finally, your code that places the heatmap tiles needs to be calling the scale functions so it works out the position of each rect correctly:
chart.selectAll('g')
.data(data).enter().append('g')
.append('rect')
.attr('x', d => { return xScale(d.taxId) })
.attr('y', d => { return yScale(d.keywordName) })
That should get you most of the way there. You'll want to also reduce cellSize and remove the tickFormat calls on the axes as they are trying to convert your text labels to numbers.

Add time parser to axis in interactive scatterplot using D3js

I am making a scatterplot using D3.js and I am unable to apply a proper time scale to the X axis. All my data is within the same year (2016), in a 3 months span.
I usually do this in a different way, and I want to learn what I am doing wrong in this one, which has a different code structure as I usually use in D3.
Here is the code in JsBin
This is X axis code:
var xValue = function(d) {
return d.datadois;
},
xScale = d3.time.scale(),
xMap = function(d) {
return xScale(xValue(d));
},
xAxis = d3.svg.axis().scale(xScale).orient("bottom").ticks(10, ".f");
Wonder how can I apply the time scale properly, to render the months in my data.

D3 Non-Continuous Dates Domain Gives Gaps on X-Axis

I want to plot some time series data that is not continuous (gaps in the dates for weekends, holidays, etc..). It's daily data.
The data looks something like:
date,value
1/2/15,109.33
1/5/15,106.25
1/6/15,106.26
1/7/15,107.75
1/8/15,111.89
1/9/15,112.01
1/12/15,109.25
1/13/15,110.22
...
So I define my x and y scales:
var x = d3.time.scale().range([0, width]);
var y = d3.scale.linear().range([height, 0]);
And set the domain from my source data:
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain(d3.extent(data, function(d) { return d.value; }));
The problem however, is that my dates have gaps in them. And my x-axis now includes those missing dates (d3.time.scale() does this automatically I guess because it maps to a continuous range?).
.extent() finds the max and min values in date then .domain() returns those max and min values as the range for the x-axis. But im not sure how to handle gaps and return the non gap dates to the range.
So my question is: How can I have my x-axis range only include the dates that are in my data set? And not "fill in the blanks". Should I play with the d3.time.scale().range() ? or do I need to use a different scale?
What is the correct way to do this? Can someone please point me in the right direction or give some example? Thank you!
Also please, I want to solve with with just plain d3 and javascript. I am not interested in using any 3rd party abstraction libraries.
As Lars Kotthof points out, you can create an ordinal x axis, which "looks" like a time scale.
Assuming you want to plot your time series as a line, here is an example how to do it: http://jsfiddle.net/ee2todev/3Lu46oqg/
If you want to customize your format for the dates, e.g. respresent the date as day (of the week), day and month you have to convert your string to a date first.
I added one example which formats the dates in a common German format. But you can easily adjust the code to your needs.
http://jsfiddle.net/ee2todev/phLbk0hf/
Last, but not least, you can use
axis.tickValues([values])
to choose the dates you want to display. See also:
https://github.com/mbostock/d3/wiki/SVG-Axes#tickFormat
The d3fc-discontinuous-scale component adapts any other scale (for example a d3 time scale) and adding the concept of discontinuities.
These discontinuities are determined via a 'discontinuity provider' the built in discontinuitySkipWeekends allows you to skip weekends.
Here's an example:
const skipWeekendScale = fc.scaleDiscontinuous(d3.scaleTime())
.discontinuityProvider(fc.discontinuitySkipWeekends());
And here's a complete demo: https://bl.ocks.org/ColinEberhardt/0a5cc7ca6c256dcd6ef1e2e7ffa468d4

D3, variable x axis ticks based on time

I'm using D3.js to generate a graph with response times over time. Previously I was using Google Charts but it was too heavy. D3 is nice and lightweight, but there's something I can't manage to do that I could do with Google Charts.
The problem is that those graphs sometimes span over one week, sometimes over one day, sometimes over one hour. For each case I have to manually modify how the ticks in the X axis appear. I'm doing it by comparing the first and the last values for the X axis, and checking how much time there's between them, like this:
if (dateDif < 25) { // daily
xAxis = d3.svg.axis()
.scale(x)
.tickFormat(d3.time.format('%H:%M'))
.ticks(d3.time.hours, 3)
.orient("bottom");
}
else if (dateDif <= 170) { // ~ weekly
xAxis = d3.svg.axis()
.scale(x)
.tickFormat(d3.time.format('%d/%m %H:%M'))
.ticks(d3.time.hours, 24)
.orient("bottom");
} else { // more than weekly
xAxis = d3.svg.axis()
.scale(x)
.tickFormat(d3.time.format('%d/%m %H:%M'))
.ticks(d3.time.hours, 96)
.orient("bottom");
}
But that it's not good, at all, specially when there are just a few values in the chart (they're generated every minute), then no ticks appear (because it falls in the first case, and there are no enough values to span 3 hours).
Do you know of any plugin or method that automagically adapts the X axis for this kind of situations?
d3.svg.axis() is fairly good at handling this type of behavior by default - try removing .tickFormat(...) from your axis, and setting .ticks(n) where n is the desired number of ticks you want on the axis at any scale zoom level - this might be sufficient for what you desire.
Here are a couple of related examples:
http://bl.ocks.org/mbostock/2983699
http://bl.ocks.org/mbostock/1166403
You could define a function to get the tickFormat based on the dateDif, so you wouldn't need to have such a large if-else block.
function getFormat() {
if (dateDif < 25) {
return d3.time.format('%H:%M');
} else {
return d3.time.format('%d/%m %H:%M');
}
}
Then you can set .ticks() to a number. In the output, roughly that many ticks will be shown. d3 chooses a number of ticks that is close to your requested value, but also makes a few decisions to try to give optimal output.
// approximately 10 ticks will be displayed
xAxis = d3.svg.axis()
.scale(x)
.tickFormat(getFormat())
.ticks(10)
.orient("bottom");
If you use this method, you lose a little control over the exact number of ticks that will be shown, but you are guaranteed to have ticks displayed, and if you choose a sensible number for your ticks value, the output will probably be satisfactory.
Here's a fiddle using this technique with some contrived data.

How do I set custom ticks along the Xaxis in D3? or name the bars in a bar chart?

I'm trying to create a bar chart with custom values for each bar along the xAxis in D3, and thought I'd try to use the tickValues. Just to try it out I gave it some dummy data but it doesn't work like this:
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom")
.tickValues(['hi','b','c','d','e']);
Here's the xScale:
gon.votes.length is an array of total votes counts for EACH answer and functionally is there only to return how many bars there will be for a specific survey question(this is a survey app fyi)
var xScale = d3.scale.linear()
.domain([0,gon.votes.length])
.range([0,w]);
Finally, when I call
function(d){ return d.title}
This will extract the appropriate title for each bar.
Also here is the html of the ticks of the xAxis that gets rendered:
NaN
Any tips on how to put this along the xAxis? Is trying to modify the ticks the wrong approach?
Ah, I see you were using a linear scale. In this case you need an ordinal scale, since the domain is a set of discrete values (the list of titles).
Working example:
http://tributary.io/inlet/5775047
There are comments in the code for the axis part.
The issue is that the range array needs to have as many values as there are labels in the domain array. Otherwise it recycles. In your case, the first label was on 0, then w, then 0, then w, and so on.
To get the titles dynamically from the array of data, you need to use the map method on the array. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
Working example with dynamic titles:
http://tributary.io/inlet/5775215

Categories