-
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Non-fancy scattergl to work with dates #1021
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
fac70b2
a23756d
ab4ffbf
f5d6113
3c79068
5b71b47
46a6b6c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
/** | ||
* Copyright 2012-2016, Plotly, Inc. | ||
* All rights reserved. | ||
* | ||
* This source code is licensed under the MIT license found in the | ||
* LICENSE file in the root directory of this source tree. | ||
*/ | ||
|
||
|
||
'use strict'; | ||
|
||
var isNumeric = require('fast-isnumeric'); | ||
|
||
var Lib = require('../../lib'); | ||
var cleanDatum = require('./clean_datum'); | ||
|
||
module.exports = function autoType(array) { | ||
if(moreDates(array)) return 'date'; | ||
if(category(array)) return 'category'; | ||
if(linearOK(array)) return 'linear'; | ||
else return '-'; | ||
}; | ||
|
||
// is there at least one number in array? If not, we should leave | ||
// ax.type empty so it can be autoset later | ||
function linearOK(array) { | ||
if(!array) return false; | ||
|
||
for(var i = 0; i < array.length; i++) { | ||
if(isNumeric(array[i])) return true; | ||
} | ||
|
||
return false; | ||
} | ||
|
||
// does the array a have mostly dates rather than numbers? | ||
// note: some values can be neither (such as blanks, text) | ||
// 2- or 4-digit integers can be both, so require twice as many | ||
// dates as non-dates, to exclude cases with mostly 2 & 4 digit | ||
// numbers and a few dates | ||
function moreDates(a) { | ||
var dcnt = 0, | ||
ncnt = 0, | ||
// test at most 1000 points, evenly spaced | ||
inc = Math.max(1, (a.length - 1) / 1000), | ||
ai; | ||
|
||
for(var i = 0; i < a.length; i += inc) { | ||
ai = a[Math.round(i)]; | ||
if(Lib.isDateTime(ai)) dcnt += 1; | ||
if(isNumeric(ai)) ncnt += 1; | ||
} | ||
|
||
return (dcnt > ncnt * 2); | ||
} | ||
|
||
// are the (x,y)-values in td.data mostly text? | ||
// require twice as many categories as numbers | ||
function category(a) { | ||
// test at most 1000 points | ||
var inc = Math.max(1, (a.length - 1) / 1000), | ||
curvenums = 0, | ||
curvecats = 0, | ||
ai; | ||
|
||
for(var i = 0; i < a.length; i += inc) { | ||
ai = cleanDatum(a[Math.round(i)]); | ||
if(isNumeric(ai)) curvenums++; | ||
else if(typeof ai === 'string' && ai !== '' && ai !== 'None') curvecats++; | ||
} | ||
|
||
return curvecats > curvenums * 2; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,6 +17,7 @@ var isNumeric = require('fast-isnumeric'); | |
|
||
var Lib = require('../../lib'); | ||
var Axes = require('../../plots/cartesian/axes'); | ||
var autoType = require('../../plots/cartesian/axis_autotype'); | ||
var ErrorBars = require('../../components/errorbars'); | ||
var str2RGBArray = require('../../lib/str2rgbarray'); | ||
var truncate = require('../../lib/float32_truncate'); | ||
|
@@ -114,11 +115,13 @@ proto.handlePick = function(pickResult) { | |
index = this.idToIndex[pickResult.pointId]; | ||
} | ||
|
||
var x = this.pickXData[index]; | ||
|
||
return { | ||
trace: this, | ||
dataCoord: pickResult.dataCoord, | ||
traceCoord: [ | ||
this.pickXData[index], | ||
isNumeric(x) || !Lib.isDateTime(x) ? x : Lib.dateTime2ms(x), | ||
this.pickYData[index] | ||
], | ||
textLabel: Array.isArray(this.textLabels) ? | ||
|
@@ -135,7 +138,7 @@ proto.handlePick = function(pickResult) { | |
|
||
// check if trace is fancy | ||
proto.isFancy = function(options) { | ||
if(this.scene.xaxis.type !== 'linear') return true; | ||
if(this.scene.xaxis.type !== 'linear' && this.scene.xaxis.type !== 'date') return true; | ||
if(this.scene.yaxis.type !== 'linear') return true; | ||
|
||
if(!options.x || !options.y) return true; | ||
|
@@ -259,6 +262,29 @@ proto.update = function(options) { | |
this.color = getTraceColor(options, {}); | ||
}; | ||
|
||
// We'd ideally know that all values are of fast types; sampling gives no certainty but faster | ||
// (for the future, typed arrays can guarantee it, and Date values can be done with | ||
// representing the epoch milliseconds in a typed array; | ||
// also, perhaps the Python / R interfaces take care of String->Date conversions | ||
// such that there's no need to check for string dates in plotly.js) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for the comment.
That won't happen any time soon unfortunately ... |
||
// Patterned from axis_defaults.js:moreDates | ||
// Code DRYing is not done to preserve the most direct compilation possible for speed; | ||
// also, there are quite a few differences | ||
function allFastTypesLikely(a) { | ||
var len = a.length, | ||
inc = Math.max(0, (len - 1) / Math.min(Math.max(len, 1), 1000)), | ||
ai; | ||
|
||
for(var i = 0; i < len; i += inc) { | ||
ai = a[Math.floor(i)]; | ||
if(!isNumeric(ai) && !(ai instanceof Date)) { | ||
return false; | ||
} | ||
} | ||
|
||
return true; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. very nicely done. |
||
} | ||
|
||
proto.updateFast = function(options) { | ||
var x = this.xData = this.pickXData = options.x; | ||
var y = this.yData = this.pickYData = options.y; | ||
|
@@ -272,24 +298,34 @@ proto.updateFast = function(options) { | |
|
||
var xx, yy; | ||
|
||
var fastType = allFastTypesLikely(x); | ||
var isDateTime = !fastType && autoType(x) === 'date'; | ||
|
||
// TODO add 'very fast' mode that bypasses this loop | ||
// TODO bypass this on modebar +/- zoom | ||
for(var i = 0; i < len; ++i) { | ||
xx = x[i]; | ||
yy = y[i]; | ||
if(fastType || isDateTime) { | ||
|
||
// check for isNaN is faster but doesn't skip over nulls | ||
if(!isNumeric(xx) || !isNumeric(yy)) continue; | ||
for(var i = 0; i < len; ++i) { | ||
xx = x[i]; | ||
yy = y[i]; | ||
|
||
idToIndex[pId++] = i; | ||
if(isNumeric(yy)) { | ||
|
||
positions[ptr++] = xx; | ||
positions[ptr++] = yy; | ||
if(!fastType) { | ||
xx = Lib.dateTime2ms(xx); | ||
} | ||
|
||
idToIndex[pId++] = i; | ||
|
||
bounds[0] = Math.min(bounds[0], xx); | ||
bounds[1] = Math.min(bounds[1], yy); | ||
bounds[2] = Math.max(bounds[2], xx); | ||
bounds[3] = Math.max(bounds[3], yy); | ||
positions[ptr++] = xx; | ||
positions[ptr++] = yy; | ||
|
||
bounds[0] = Math.min(bounds[0], xx); | ||
bounds[1] = Math.min(bounds[1], yy); | ||
bounds[2] = Math.max(bounds[2], xx); | ||
bounds[3] = Math.max(bounds[3], yy); | ||
} | ||
} | ||
} | ||
|
||
positions = truncate(positions, ptr); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
var PlotlyInternal = require('@src/plotly'); | ||
|
||
var hasWebGLSupport = require('../assets/has_webgl_support'); | ||
|
||
var createGraphDiv = require('../assets/create_graph_div'); | ||
var destroyGraphDiv = require('../assets/destroy_graph_div'); | ||
|
||
describe('date axis', function() { | ||
|
||
if(!hasWebGLSupport('axes_test date axis')) return; | ||
|
||
var gd; | ||
|
||
beforeEach(function() { | ||
gd = createGraphDiv(); | ||
}); | ||
|
||
afterEach(destroyGraphDiv); | ||
|
||
it('should use the fancy gl-vis/gl-scatter2d', function() { | ||
PlotlyInternal.plot(gd, [{ | ||
type: 'scattergl', | ||
'marker': { | ||
'color': 'rgb(31, 119, 180)', | ||
'size': 18, | ||
'symbol': [ | ||
'diamond', | ||
'cross' | ||
] | ||
}, | ||
x: [new Date('2016-10-10'), new Date('2016-10-12')], | ||
y: [15, 16] | ||
}]); | ||
|
||
expect(gd._fullLayout.xaxis.type).toBe('date'); | ||
expect(gd._fullLayout.yaxis.type).toBe('linear'); | ||
expect(gd._fullData[0].type).toBe('scattergl'); | ||
expect(gd._fullData[0]._module.basePlotModule.name).toBe('gl2d'); | ||
|
||
// one way of check which renderer - fancy vs not - we're using | ||
expect(gd._fullLayout._plots.xy._scene2d.glplot.objects[3].pointCount).toBe(0); | ||
}); | ||
|
||
it('should use the fancy gl-vis/gl-scatter2d once again', function() { | ||
PlotlyInternal.plot(gd, [{ | ||
type: 'scattergl', | ||
'marker': { | ||
'color': 'rgb(31, 119, 180)', | ||
'size': 36, | ||
'symbol': [ | ||
'circle', | ||
'cross' | ||
] | ||
}, | ||
x: [new Date('2016-10-10'), new Date('2016-10-11')], | ||
y: [15, 16] | ||
}]); | ||
|
||
expect(gd._fullLayout.xaxis.type).toBe('date'); | ||
expect(gd._fullLayout.yaxis.type).toBe('linear'); | ||
expect(gd._fullData[0].type).toBe('scattergl'); | ||
expect(gd._fullData[0]._module.basePlotModule.name).toBe('gl2d'); | ||
|
||
// one way of check which renderer - fancy vs not - we're using | ||
expect(gd._fullLayout._plots.xy._scene2d.glplot.objects[3].pointCount).toBe(0); | ||
}); | ||
|
||
it('should now use the non-fancy gl-vis/gl-scatter2d', function() { | ||
PlotlyInternal.plot(gd, [{ | ||
type: 'scattergl', | ||
mode: 'markers', // important, as otherwise lines are assumed (which needs fancy) | ||
x: [new Date('2016-10-10'), new Date('2016-10-11')], | ||
y: [15, 16] | ||
}]); | ||
|
||
expect(gd._fullLayout.xaxis.type).toBe('date'); | ||
expect(gd._fullLayout.yaxis.type).toBe('linear'); | ||
expect(gd._fullData[0].type).toBe('scattergl'); | ||
expect(gd._fullData[0]._module.basePlotModule.name).toBe('gl2d'); | ||
|
||
expect(gd._fullLayout._plots.xy._scene2d.glplot.objects[3].pointCount).toBe(2); | ||
}); | ||
|
||
it('should use the non-fancy gl-vis/gl-scatter2d with string dates', function() { | ||
PlotlyInternal.plot(gd, [{ | ||
type: 'scattergl', | ||
mode: 'markers', // important, as otherwise lines are assumed (which needs fancy) | ||
x: ['2016-10-10', '2016-10-11'], | ||
y: [15, 16] | ||
}]); | ||
|
||
expect(gd._fullLayout.xaxis.type).toBe('date'); | ||
expect(gd._fullLayout.yaxis.type).toBe('linear'); | ||
expect(gd._fullData[0].type).toBe('scattergl'); | ||
expect(gd._fullData[0]._module.basePlotModule.name).toBe('gl2d'); | ||
|
||
expect(gd._fullLayout._plots.xy._scene2d.glplot.objects[3].pointCount).toBe(2); | ||
}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍