Dygraphs allows easy display of time series...
However, if my data contains only two data points, it automatically fills the gaps in X axis with hours. Is it possible to disable this functionality?
I searched and tried many options but not found anything useful.
Example might be the 'Time Series Drawing Demo' from the gallery - if executed on only few datapoints, it fills the 'gaps' with hours.
This is a good example:
g = new Dygraph(document.getElementById('plot'),"a,b\n2008-12-01,0.9\n2008-12-02,0.3\n2008-12-03,0.7\n")
UPDATE- this seems to be working:
ticker: function(a, b, pixels, opts, dygraph, vals) {
var chosen = Dygraph.pickDateTickGranularity(a, b, pixels, opts);
if(chosen==12) chosen=13;
if (chosen >= 0) {
return Dygraph.getDateAxis(a, b, chosen, opts, dygraph);
} else {
// this can happen if self.width_ is zero.
return [];
}
};
Your issue is not that you have two points, but that your points cover a certain amount of time. Dygraphs tries to calculate the best granularity for the x axis tick marks in a given set of data.
One way to modify the default calculation is by using the pixelsPerLabel option.
Example: http://jsfiddle.net/kaliatech/P8ehg/
var data = "a,b\n2008-12-01,0.9\n2008-12-02,0.3\n2008-12-03,0.7\n";
g = new Dygraph(document.getElementById("plot"), data, {
axes: {
x: {
pixelsPerLabel: 100
}
}
});
This requires hard coding a pixel width though, and it is still ultimately dependent on the data set that you are graphing. A more flexible approach might be to use the ticker option, allowing you to supply your own function for calculating label granularity. See the documentation and built-in functions of dygraph-tickers.js.
See also:
How to set specific y-axis label points in dygraphs?
EDIT: Example using ticker. This requires that you are familiar with the data and the data range is somewhat constant, otherwise you could end up with unreadable x-axis labels.
var g = new Dygraph(document.getElementById("demodiv3"), data(), {
title: 'Example for changing x-axis label granularity 3',
axes: {
x: {
ticker: function(a, b, pixels, opts, dygraph, vals) {
var chosen = Dygraph.pickDateTickGranularity(a, b, pixels, opts);
//Force to DAILY if built-in calculation returned SIX_HOURLY
//if(chosen==Dygraph.SIX_HOURLY)
// chosen=Dygraph.DAILY;
//or
//Force to DAILY always
chosen = Dygraph.DAILY;
if (chosen >= 0) {
return Dygraph.getDateAxis(a, b, chosen, opts, dygraph);
} else {
return [];
}
}
}
}
});
Related
I'm plotting sentiment value of tweet over last 10 years.
The csv file has the three columns like below.
I plotted each value by date successfully.
However, when I tried to generate an area graph,
I encountered a problem which is,
each date has multiple values.
That's because each data point is from one single tweets that
one x point ended up with having multiple y values.
So I tried to pick quartile value of each date or pick largest or least y value.
For clarity, please see the example below.
January 8 has multiple y values (textblob)
I want to draw area graph by picking the largest value or 2nd quartile value of each point.
How do I only pick the point?
I would like to feed the point in the following code as a
x/y coordinate for line or area greaph.
function* vlinedrawing(data){
for(let i;i<data.length;i++){
if( i%500==0) yield svg.node();
let px = margin+xscale(data[i].date)
let py = height-margin-yscale(data[i].vader)
paths.append('path')
.attr('x',px)
.attr('y',py)
}
yield svg.node()
}
The entire code is in the following link.
https://jsfiddle.net/soonk/uh5djax4/2/
Thank you in advance.
( The reason why it is a generator is that I'm going to visualize the graph in animated way)
For getting the 2nd quartile you can use d3.quantile like this:
d3.quantile(dataArray, 0.5);
Of course, since the 2nd quartile is the median, you can also just use:
d3.median(dataArray);
But d3.quantile is a bit more versatile, you can just change the p value for any quartile you want.
Using your data, without parsing the dates (so we can use a Set for unique values`), here is a possible solution:
const aggregatedData = [...new Set(data.map(function(d) {
return d.date
}))].map(function(d) {
return {
date: parser(d),
textblob: d3.quantile(data.filter(function(e) {
return e.date === d
}).map(function(e) {
return e.textblob
}), 0.5)
}
});
This is just a quick answer for showing you the way: that's not a optimised code, because there are several loops within loops. You can try to optimise it.
Here is the demo:
var parser = d3.timeParse("%m/%d/%y");
d3.csv('https://raw.githubusercontent.com/jotnajoa/Javascript/master/tweetdata.csv', row).then(function(data) {
const aggregatedData = [...new Set(data.map(function(d) {
return d.date
}))].map(function(d) {
return {
date: parser(d),
textblob: d3.quantile(data.filter(function(e) {
return e.date === d
}).map(function(e) {
return e.textblob
}), 0.5)
}
});
console.log(aggregatedData)
});
function row(d) {
d.vader = +d.vader;
d.textblob = +d.textblob;
return d;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
I am using the Highcharts synchronized charts to display three different variables. However, in order to render cleaner graphs, I'd like to display the x-axis (which is the same for all three graphs) only for the bottom graph.
For that, I presume, I need to cycle at the end of the generation process through the charts and suppress the first two x-axis, kind like
for (i = 0; i < (Highcharts.charts.length - 1); i = i + 1)
{
chart = Highcharts.charts[i];
chart.xAxis.labels.enabled = false;
}
Here is the default fiddle.
I don't succeed in getting this to work. Can anyone help me out on this?
You can set the xAxis.visible property depending on the chart index:
success: function (activity) {
activity = JSON.parse(activity);
activity.datasets.forEach(function (dataset, i) {
...
Highcharts.chart(chartDiv, {
xAxis: {
visible: i === 2,
...
},
...
});
});
}
Live demo: https://jsfiddle.net/BlackLabel/cmdb5at0/
API Reference: https://api.highcharts.com/highcharts/xAxis.visible
I have created an highcharts stacked bar chart, but when the data is skewed, the bars are not visible or the numbers overlap, as shown in below image.
I have seen many posts, but there is no out of the box solution for this, so i am making my custom solution.
I am setting a default height of 150 if the y value is less than 150.
This solution works, but the total of the bars now is shown to be 300 instead of the actual original value. How can i change the total stacklabel value on my own? I am unable to find a way to do that.
Here is the code to change the height to default values. I am storing the actual value in realValue variable in the point object.
chartOptions = {
type: CHARTING.CHART_OPTIONS.TYPE.COLUMN,
// On chart load, apply custom logic
events : {
load: function () {
var chart = this,
minColHeightVal = 150;
chart.series.forEach(function (s) {
s.points.forEach(function (p) {
if (p.y < minColHeightVal) {
p.update({
y: minColHeightVal,
realValue: p.y
}, false);
}
});
});
// How to iterate over the bars here and sum the actual value? i.e. point.realValue and set the stacklabel?
chart.redraw();
}
}
}
Did you try to use minPointLength option? It may be a simpler solution in your case: https://api.highcharts.com/highcharts/series.column.minPointLength
However, using your code to get the wanted result, use stackLabels.formatter function:
formatter: function() {
var series = this.axis.series,
x = this.x,
sum = 0;
series.forEach(function(s) {
if (s.points && s.points[x]) {
sum += s.points[x].realValue ? s.points[x].realValue : s.points[x].y
}
});
return sum;
}
Live demo: https://jsfiddle.net/BlackLabel/uocdykbL/
API Reference: https://api.highcharts.com/highcharts/yAxis.stackLabels.formatter
The highstock column range with dataGrouping enabled seems not to be computing dataAggregation correctly.
The aggregated values seem to change when changing the range.
March 2014 will show different values if scrolling more towards the right.
Code and jsfiddle:
dataGrouping: {
enabled: true,
approximation: function() {
const indices = _.range(this.dataGroupInfo.start, this.dataGroupInfo.start + this.dataGroupInfo.length);
const low = _.min(indices.map(i => this.options.data[i][1]));
const high = _.max(indices.map(i => this.options.data[i][2]));
return [low, high];
},
groupPixelWidth: 50
}
See jsfiddle
The columns are changed only when the navigator does not start from the beggining - and that happens because the way you defined approximation callback.
dataGroupInfo contains information according to the visible points (which fall into x axis range, cropped points) in the chart, not all the points - so to have proper indices for the initial data, you need to add this.cropStart - it is the index from which points are visible.
approximation: function() {
const start = this.cropStart + this.dataGroupInfo.start;
const stop = start + this.dataGroupInfo.length;
const indices = _.range(start, stop);
const low = _.min(indices.map(i => this.options.data[i][1]));
const high = _.max(indices.map(i => this.options.data[i][2]));
return [ low, high ];
},
example: https://jsfiddle.net/12o4e84v/7/
The same functionality can be implemented easier
approximation: function(low, high) {
return [ _.min(low), _.max(high) ];
}
example: https://jsfiddle.net/12o4e84v/8/
Or even simpler:
approximation: 'range',
However, by default approximation for columns is set to range, so you do not have to do it manually.
example: https://jsfiddle.net/12o4e84v/9/
I have a group of graphs visualizing a bunch of data for me (here), based off a csv with approximately 25,000 lines of data, each having 12 parameters. However, doing any interaction (such as selecting a range with the brush on any of the graphs) is slow and unwieldy, completely unlike the dc.js demo found here, which deals with thousands of records as well but maintains smooth animations, or crossfilter's demo here which has 10 times as many records (flights) as I do.
I know the main resource hogs are the two line charts, since they have data points every 15 minutes for about 8 solid months. Removing either of them makes the charts responsive again, but they're the main feature of the visualizations, so is there any way I can make them show less fine-grained data?
The code for the two line graphs specifically is below:
var lineZoomGraph = dc.lineChart("#chart-line-zoom")
.width(1100)
.height(60)
.margins({top: 0, right: 50, bottom: 20, left: 40})
.dimension(dateDim)
.group(tempGroup)
.x(d3.time.scale().domain([minDate,maxDate]));
var tempLineGraph = dc.lineChart("#chart-line-tempPer15Min")
.width(1100).height(240)
.dimension(dateDim)
.group(tempGroup)
.mouseZoomable(true)
.rangeChart(lineZoomGraph)
.brushOn(false)
.x(d3.time.scale().domain([minDate,maxDate]));
Separate but relevant question; how do I modify the y-axis on the line charts? By default they don't encompass the highest and lowest values found in the dataset, which seems odd.
Edit: some code I wrote to try to solve the problem:
var graphWidth = 1100;
var dataPerPixel = data.length / graphWidth;
var tempGroup = dateDim.group().reduceSum(function(d) {
if (d.pointNumber % Math.ceil(dataPerPixel) === 0) {
return d.warmth;
}
});
d.pointNumber is a unique point ID for each data point, cumulative from 0 to 22 thousand ish. Now however the line graph shows up blank. I checked the group's data using tempGroup.all() and now every 21st data point has a temperature value, but all the others have NaN. I haven't succeeded in reducing the group size at all; it's still at 22 thousand or so. I wonder if this is the right approach...
Edit 2: found a different approach. I create the tempGroup normally but then create another group which filters the existing tempGroup even more.
var tempGroup = dateDim.group().reduceSum(function(d) { return d.warmth; });
var filteredTempGroup = {
all: function () {
return tempGroup.top(Infinity).filter( function (d) {
if (d.pointNumber % Math.ceil(dataPerPixel) === 0) return d.value;
} );
}
};
The problem I have here is that d.pointNumber isn't accessible so I can't tell if it's the Nth data point (or a multiple of that). If I assign it to a var it'll just be a fixed value anyway, so I'm not sure how to get around that...
When dealing with performance problems with d3-based charts, the usual culprit is the number of DOM elements, not the size of the data. Notice the crossfilter demo has lots of rows of data, but only a couple hundred bars.
It looks like you might be attempting to plot all the points instead of aggregating them. I guess since you are doing a time series it may be unintuitive to aggregate the points, but consider that your plot can only display 1100 points (the width), so it is pointless to overwork the SVG engine plotting 25,000.
I'd suggest bringing it down to somewhere between 100-1000 bins, e.g. by averaging each day:
var daysDim = data.dimension(function(d) { return d3.time.day(d.time); });
function reduceAddAvg(attr) {
return function(p,v) {
if (_.isLegitNumber(v[attr])) {
++p.count
p.sums += v[attr];
p.averages = (p.count === 0) ? 0 : p.sums/p.count; // gaurd against dividing by zero
}
return p;
};
}
function reduceRemoveAvg(attr) {
return function(p,v) {
if (_.isLegitNumber(v[attr])) {
--p.count
p.sums -= v[attr];
p.averages = (p.count === 0) ? 0 : p.sums/p.count;
}
return p;
};
}
function reduceInitAvg() {
return {count:0, sums:0, averages:0};
}
...
// average a parameter (column) named "param"
var daysGroup = dim.group().reduce(reduceAddAvg('param'), reduceRemoveAvg('param'), reduceInitAvg);
(reusable average reduce functions from the FAQ)
Then specify your xUnits to match, and use elasticY to auto-calculate the y axis:
chart.xUnits(d3.time.days)
.elasticY(true)