amCharts5 - DateAxis: confusing transition from Days to Hours - amcharts

is it just me or is the transition in this chart from days to hours really harsh and a bit confusing as well? I took this of a demo from amcharts. Did i miss something? Do i have any chance to make it smoother and how? In the example the transitions from month to week and week to days and days to day, they all work fine. But zooming in further from days it jumps directly to hours. I tried already a lot of variations with groupIntervals, groupCount and valueYGrouped. I know the transition in amCharts4 is definitely smoother by default (going to groups of hours first and also not jumping all over the place).
https://codepen.io/robjsky/pen/VwrxQMz
// Create X-Axis
var xAxis = chart.xAxes.push(
am5xy.DateAxis.new(root, {
groupData: true,
baseInterval: { timeUnit: "minute", count: 1 },
groupCount: 30,
renderer: am5xy.AxisRendererX.new(root, {})
})
);

Related

Wrong year assigned to a ticklabel in plotly.js

The following image contains a part of a scatter plot generated with plotly.js:
The red arrow shows the wrong value in ticklabel. It should be 53 2020.
If a zoom is made, then the right ticklabels are obtained:
The layout for xaxis.tickformat gives Week / Year.
This problem appears just when the ticklabel is generated when date is December 31st of any year.
The layout for xaxis is set as:
xaxis: {
type: 'date',
tickformat: '%V\n%Y',
ticks: 'outside',
ticklabelmode: 'period',
tickmode: 'auto',
autorange: true,
},
After a little research, the issue seems to be related to:
Plotly uses Date.getUTCFullyear to calculate ticklabels,
For a date given on December 31st, the result may be different according to getUTCFullYear in MDN.
One attention calling point is that the value of data showed in the first image when the mouse is over the point (following the green arrow), is the right one: 53, 2020!
The code for this issue can be found at:
codepen
Please, can anyone help to solve this issue? Thanks.

d3js timeline -- on scrub day generate a clean granular view for date and time

I am building a timeline chart - that will change its date scale at the top when the brush becomes small to the scope of 1 day -- but when it hits this mode -- the labels overlap and it looks messy until you get to a 12 hour spread.
What is the best way of cleaning this functionality up so it doesn't overlap. I thought about having 1 line that shows date -- and another line under it that shows the hours at that level.
https://jsfiddle.net/aLh9d51t/
var tFormat = '%Y-%m';
var tTick = 'timeMonth';
if (days < 40) {
tFormat = '%Y-%m-%d';
tTick = 'timeWeek';
}
if (days <= 7) {
tFormat = '%Y-%m-%d';
tTick = 'timeDay';
}
if (days <= 1) {
tFormat = '%Y-%m-%d %H%p';
tTick = 'timeHour';
}
First, you can hide redundant parts of date when possible: show years, months, days only if there are more than one visible. So you definitely do not need years and months when you show hours and minutes.
Just look how default d3 axis handles this (e.g. https://bl.ocks.org/mbostock/1166403).
Second, considering your chart has fixed width, you can fine-tune different formats for different zoom levels (you already do this in your code snippet).
Take a look at this example: http://bl.ocks.org/oluckyman/6199145
It has similar logic as in your code snippet:
https://gist.github.com/oluckyman/6199145#file-axisdaysview-js-L33-L58
But the decision which format to choose depends on chart width:
https://gist.github.com/oluckyman/6199145#file-axisdaysview-js-L72-L75
And third, if you restricted to long labels for some reason, you can rotate them to 30°-45°
Also this could be useful: https://bl.ocks.org/mbostock/4149176

crossfilter "double grouping" where key is the value of another reduction

Here is my data about mac address. It is recorded per minute. For each minute, I have many unique Mac addresses.
mac_add,created_time
18:59:36:12:23:33,2016-12-07 00:00:00.000
1c:e1:92:34:d7:46,2016-12-07 00:00:00.000
2c:f0:ee:86:bd:51,2016-12-07 00:00:00.000
5c:cf:7f:d3:2e:ce,2016-12-07 00:00:00.000
...
18:59:36:12:23:33,2016-12-07 00:01:00.000
1c:cd:e5:1e:99:78,2016-12-07 00:01:00.000
1c:e1:92:34:d7:46,2016-12-07 00:01:00.000
5c:cf:7f:22:01:df,2016-12-07 00:01:00.000
5c:cf:7f:d3:2e:ce,2016-12-07 00:01:00.000
...
I would like to create 2 bar charts using dc.js and crossfilter. Please refer to the image for the charts.
The first bar chart is easy enough to create. It is brushable. I created the "created_time" dimension, and created a group and reduceCount by "mac_add", such as below:
var moveTime = ndx.dimension(function (d) {
return d.dd; //# this is the created_time
});
var timeGroup = moveTime.group().reduceCount(function (d) {
return d.mac_add;
});
var visitorChart = dc.barChart('#visitor-no-bar');
visitorChart.width(990)
.height(350)
.margins({ top: 0, right: 50, bottom: 20, left: 40 })
.dimension(moveTime)
.group(timeGroup)
.centerBar(true)
.gap(1)
.elasticY(true)
.x(d3.time.scale().domain([new Date(2016, 11, 7), new Date(2016, 11, 13)]))
.round(d3.time.minute.round)
.xUnits(d3.time.minute);
visitorChart.render();
The problem is on the second bar chart. The idea is that, one row of the data equals 1 minute, so I can aggregate and sum all minutes of each mac address to get the time length of each mac addresses, by creating another dimension by "mac_add" and do reduceCount on "mac_add" to get the time length. Then the goal is to group the time length by 30 minutes. So we can get how many mac address that have time length of 30 min and less, how many mac_add that have time length between 30 min and 1 hour, how many mac_add that have time length between 1 hour and 1.5 hour, etc...
Please correct me if I am wrong. Logically, I was thinking the dimension of the second bar chart should be the group of time length (such as <30, <1hr, < 1.5hr, etc). But the time length group themselves are not fix. It depends on the brush selection of the first chart. Maybe it only contains 30 min, maybe it only contains 1.5 hours, maybe it contains 1.5 hours and 2 hours, etc...
So I am really confused what parameters to put into the second bar chart. And method to get the required parameters (how to group a grouped data). Please help me to explain the solution.
Regards,
Marvin
I think we've called this a "double grouping" in the past, but I can't find the previous questions.
Setting up the groups
I'd start with a regular crossfilter group for the mac addresses, and then produce a fake group to aggregate by count of minutes.
var minutesPerMacDim = ndx.dimension(function(d) { return d.mac_add; }),
minutesPerMapGroup = minutesPerMacDim.group();
function bin_keys_by_value(group, bin_value) {
var _bins;
return {
all: function() {
var bins = {};
group.all().forEach(function(kv) {
var valk = bin_value(kv.value);
bins[valk] = bins[valk] || [];
bins[valk].push(kv.key);
});
_bins = bins;
// note: Object.keys returning numerical order here might not
// work everywhere, but I couldn't find a browser where it didn't
return Object.keys(bins).map(function(bin) {
return {key: bin, value: bins[bin].length};
})
},
bins: function() {
return _bins;
}
};
}
function bin_30_mins = function(v) {
return 30 * Math.ceil(v/30);
}
var macsPerMinuteCount = bin_keys_by_value(minutesPerMacGroup);
This will retain the mac addresses for each time bin, which we'll need for filtering later. It's uncommon to add a non-standard method bins to a fake group, but I can't think of an efficient way to retain that information, given that the filtering interface will only give us access to the keys.
Since the function takes a binning function, we could even use a threshold scale if we wanted more complicated bins than just rounding up to the nearest 30 minutes. A quantize scale is a more general way to do the rounding shown above.
Setting up the chart
Using this data to drive a chart is simple: we can use the dimension and fake group as usual.
chart
.dimension(minutesPerMacDim)
.group(macsPerMinuteCount)
Setting up the chart so that it can filter is a bit more complicated:
chart.filterHandler(function(dimension, filters) {
if(filters.length === 0)
dimension.filter(null);
else {
var bins = chart.group().bins(); // retrieve cached bins
var macs = filters.map(function(key) { return bins[key]; })
macs = Array.prototype.concat.apply([], macs);
var macset = d3.set(macs);
dimension.filterFunction(function(key) {
return macset.has(key);
})
}
})
Recall that we're using a dimension which is keyed on mac addresses; this is good because we want to filter on mac addresses. But the chart is receiving minute-counts for its keys, and the filters will contain those keys, like 30, 60, 90, etc. So we need to supply a filterHandler which takes minute-count keys and filters the dimension based on those.
Note 1: This is all untested, so if it doesn't work, please post an example as a fiddle or bl.ock - there are fiddles and blocks you can fork to get started on the main page.
Note 2: Strictly speaking, this is not measuring the length of connections: it's counting the total number of minutes connected. Not sure if this matters to you. If a user disconnects and then reconnects within the timeframe, the two sessions will be counted as one. I think you'd have to preprocess to get duration.
EDIT: Based on your fiddle (thank you!) the code above does seem to work. It's just a matter of setting up the x scale and xUnits properly.
chart2
.x(d3.scale.linear().domain([60,1440]))
.xUnits(function(start, end) {
return (end-start)/30;
})
A linear scale will do just fine here - I wouldn't try to quantize that scale, since the 30-minute divisions are already set up. We do need to set the xUnits so that dc.js knows how wide to make the bars.
I'm not sure why elasticX didn't work here, but the <30 bin completely dwarfed everything else, so I thought it was best to leave that out.
Fork of your fiddle: https://jsfiddle.net/gordonwoodhull/2a8ow1ay/2/

Trouble displaying hourly data from a multi-dimensional array in d3js

I am trying to display a multi-line chart of temperatures for 5 days on an hourly basis. I was able to create the both axes but I'm having trouble displaying the lines.
I have a JSON like so where x is a date object for every 3 hours and y is the temperature.
var dataset = [
//day 1
[
{x: Date 2015-09-07T21:00:00.000Z, y: 30.75},
{x: Date 2015-09-08T00:00:00.000Z, y: 29.32},
{x: Date 2015-09-08T03:00:00.000Z, y: 25.67},
{x: Date 2015-09-08T06:00:00.000Z, y: 22.7}
],
//day 2
[
{x: Date 2015-09-08T09:00:00.000Z, y: 23.69},
{x: Date 2015-09-08T12:00:00.000Z, y: 24.18},
{x: Date 2015-09-08T15:00:00.000Z, y: 26.69},
{x: Date 2015-09-08T18:00:00.000Z, y: 22.36},
{x: Date 2015-09-08T21:00:00.000Z, y: 23.91},
{x: Date 2015-09-09T00:00:00.000Z, y: 22.98}
],
//day 3
Array[8],
//day 4
Array[8],
//day 5
Array[8]
]
When initialize the graph like below, instead of a multi-line graph, I get one line containing all 4 days.
var chart = lineChart("graph")
.x(d3.time.scale().domain([
dataset[0][0].x, dataset[3][7].x
]))
.y(d3.scale.linear().domain([min, max]));
dataset.forEach(function (series) {
chart.addSeries(series);
});
chart.render();
If I change the domain to,
dataset[4][0].x, dataset[4][7].x
it only draws the line for that day.
The strange thing is that when I "Inspect Elemet" via the browser, I can see that all 5 paths have been drawn out but they just dont show up on the UI. I think this has something to do with the way I'm setting the domain but I'm not sure what.
How do I set the domain so that d3js plots each days array on a 24-hour x-axis?
If I understand correctly, you want to have the x-axis from midnight to midnight, as if everything is happening "on one day", like so:
NOTE: something is a bit weird with the timestamps in the source. I have no idea how dt and dt_text are related. Please adjust my examples accordingly...
How can you get that?
The problem in your code is indeed related to the domain. How?
If you set the domain to be from the very first timestamp to the last, like:
.domain([
dataset[0][0].x, dataset[3][7].x
])
then the chart will span over 4 days (by the way: these hard-coded indices are not very robust coding...)
So the chart will then obviously plot over all days, it has no way of knowing that you only want hourly time-stamps.
If you, on the other hand, just use the most recent day as domain:
.domain([
dataset[3][0].x, dataset[3][7].x
]))
It will plot just that, i.e. the last day. The other lines will be plotted too (what you see in the inspector), but the will be hidden away on the left (as you clip the stuff).
So: the problem is, that the x-coordinates are different, as they occur on different dates. There is plenty of ways to work around that (I personally always use moment.js for dates), but to show the effect, here's a quick hack to achieve the graph above:
-> JSFiddle
What did I do? I added a new helper function to calculate the x time-stamp:
function gDate2(date) {
var now = new Date();
var hours = (new Date(date * 1000)).getHours();
var date = now.getDate();
var month = now.getMonth();
var d = new Date(2015, month, date, hours, 0, 0, 0);
return d;
}
Yes, it's not pretty. Personally, I like moment.js to calculate dates and stuff. The important part is that I return all dates as if it's all today (or any other arbitrary day). Then I extract the hour of the timestamp of the according data point and add that (as in the note: maybe you need minutes, seconds too?)
If you are going to use it, please make sure you have timezones, day-light saving etc. under control! I hate dates...)
And again: I am not sure about dt and dt_text. Make sure you got that right!
I hope this helps.

Show only time labels on xAxis. Highcharts

I need to display only time labels on xAxis. I'm using Highcharts and don't fully understand how to do it. On xAxis there should be time labels in format like 21:00. I do not need dates, only time is needed. In addition, the difference between two labels should be 00:30 (half an hour) or 01:30 and it should be zoomable. My PHP script writes some value in database every half an hour and I need to display it on a graph. Sorry for my poor English, I'm Russian. Any help would be appreciated :)
In xAxis of your chart you need to provide tickinterval and dateTimeLabelFormats. Following code gives the tick interval of 1.5 hours. You can change that to any number of hours you want by replacing 1.5 in tickInterval: 1.5 * 3600 * 1000,
xAxis: {
type: 'datetime',
//Sets tickInterval to 24 * 3600 * 1000 if display is by day
tickInterval: 1.5 * 3600 * 1000,
dateTimeLabelFormats : {
day: '%H:%M'
}
}

Resources