diff --git a/src/components/calendars/index.js b/src/components/calendars/index.js index f475ed62932..3db095eea00 100644 --- a/src/components/calendars/index.js +++ b/src/components/calendars/index.js @@ -230,11 +230,14 @@ module.exports = { }, transforms: { filter: { - calendar: makeAttrs([ - 'Sets the calendar system to use for `value`, if it is a date.', - 'Note that this is not necessarily the same calendar as is used', - 'for the target data; that is set by its own calendar attribute,', - 'ie `trace.x` uses `trace.xcalendar` etc.' + valuecalendar: makeAttrs([ + 'Sets the calendar system to use for `value`, if it is a date.' + ].join(' ')), + targetcalendar: makeAttrs([ + 'Sets the calendar system to use for `target`, if it is an', + 'array of dates. If `target` is a string (eg *x*) we use the', + 'corresponding trace attribute (eg `xcalendar`) if it exists,', + 'even if `targetcalendar` is provided.' ].join(' ')) } } diff --git a/src/plot_api/helpers.js b/src/plot_api/helpers.js index d813dfc70f4..f816b463839 100644 --- a/src/plot_api/helpers.js +++ b/src/plot_api/helpers.js @@ -353,6 +353,13 @@ exports.cleanData = function(data, existingData) { transform.target = transform.filtersrc; delete transform.filtersrc; } + + if(transform.calendar) { + if(!transform.valuecalendar) { + transform.valuecalendar = transform.calendar; + } + delete transform.calendar; + } } } } diff --git a/src/transforms/filter.js b/src/transforms/filter.js index 1467620d7cf..a0006d4d2d5 100644 --- a/src/transforms/filter.js +++ b/src/transforms/filter.js @@ -119,7 +119,8 @@ exports.supplyDefaults = function(transformIn) { coerce('target'); var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleDefaults'); - handleCalendarDefaults(transformIn, transformOut, 'calendar', null); + handleCalendarDefaults(transformIn, transformOut, 'valuecalendar', null); + handleCalendarDefaults(transformIn, transformOut, 'targetcalendar', null); } return transformOut; @@ -134,8 +135,21 @@ exports.calcTransform = function(gd, trace, opts) { if(!len) return; - var targetCalendar = Lib.nestedProperty(trace, target + 'calendar').get(), - dataToCoord = getDataToCoordFunc(gd, trace, target), + var targetCalendar = opts.targetcalendar; + + // even if you provide targetcalendar, if target is a string and there + // is a calendar attribute matching target it will get used instead. + if(typeof target === 'string') { + var attrTargetCalendar = Lib.nestedProperty(trace, target + 'calendar').get(); + if(attrTargetCalendar) targetCalendar = attrTargetCalendar; + } + + // if target points to an axis, use the type we already have for that + // axis to find the data type. Otherwise use the values to autotype. + var d2cTarget = (target === 'x' || target === 'y' || target === 'z') ? + target : filterArray; + + var dataToCoord = getDataToCoordFunc(gd, trace, d2cTarget), filterFunc = getFilterFunc(opts, dataToCoord, targetCalendar), arrayAttrs = PlotSchema.findArrayAttributes(trace), originalArrays = {}; @@ -226,7 +240,7 @@ function getFilterFunc(opts, d2c, targetCalendar) { return array.indexOf(operation) !== -1; } - var d2cValue = function(v) { return d2c(v, 0, opts.calendar); }, + var d2cValue = function(v) { return d2c(v, 0, opts.valuecalendar); }, d2cTarget = function(v) { return d2c(v, 0, targetCalendar); }; var coercedValue; diff --git a/test/image/mocks/world-cals.json b/test/image/mocks/world-cals.json index 8d06668a3d5..5d7e0a735ed 100644 --- a/test/image/mocks/world-cals.json +++ b/test/image/mocks/world-cals.json @@ -350,7 +350,7 @@ { "type": "filter", "operation": "[]", - "calendar": "jalali", + "valuecalendar": "jalali", "value": [ "0818-08", "0819-06" diff --git a/test/jasmine/tests/plot_api_test.js b/test/jasmine/tests/plot_api_test.js index 80acfbb1b7e..05af47433cd 100644 --- a/test/jasmine/tests/plot_api_test.js +++ b/test/jasmine/tests/plot_api_test.js @@ -1051,6 +1051,36 @@ describe('Test plot api', function() { expect(trace1.transforms[0].target).toEqual('y'); }); + it('should rename *calendar* to *valuecalendar* in filter transforms', function() { + var data = [{ + transforms: [{ + type: 'filter', + target: 'y', + calendar: 'hebrew' + }, { + type: 'filter', + operation: '<' + }] + }, { + transforms: [{ + type: 'filter', + valuecalendar: 'jalali' + }] + }]; + + Plotly.plot(gd, data); + + var trace0 = gd.data[0], + trace1 = gd.data[1]; + + expect(trace0.transforms.length).toEqual(2); + expect(trace0.transforms[0].calendar).toBeUndefined(); + expect(trace0.transforms[0].valuecalendar).toEqual('hebrew'); + + expect(trace1.transforms.length).toEqual(1); + expect(trace1.transforms[0].valuecalendar).toEqual('jalali'); + }); + it('should cleanup annotations / shapes refs', function() { var data = [{}]; diff --git a/test/jasmine/tests/plotschema_test.js b/test/jasmine/tests/plotschema_test.js index 2420632f3bf..30523b88f33 100644 --- a/test/jasmine/tests/plotschema_test.js +++ b/test/jasmine/tests/plotschema_test.js @@ -196,7 +196,8 @@ describe('plot schema', function() { expect(plotSchema.layout.layoutAttributes.xaxis.calendar.valType).toEqual('enumerated'); expect(plotSchema.layout.layoutAttributes.scene.xaxis.calendar.valType).toEqual('enumerated'); - expect(plotSchema.transforms.filter.attributes.calendar.valType).toEqual('enumerated'); + expect(plotSchema.transforms.filter.attributes.valuecalendar.valType).toEqual('enumerated'); + expect(plotSchema.transforms.filter.attributes.targetcalendar.valType).toEqual('enumerated'); }); it('should list correct defs', function() { diff --git a/test/jasmine/tests/transform_filter_test.js b/test/jasmine/tests/transform_filter_test.js index 04c5e0709ad..59507843486 100644 --- a/test/jasmine/tests/transform_filter_test.js +++ b/test/jasmine/tests/transform_filter_test.js @@ -142,6 +142,75 @@ describe('filter transforms calc:', function() { expect(out[0].z).toEqual(['2016-10-21', '2016-12-02']); }); + it('should use the calendar from the target attribute if target is a string', function() { + // this is the same data as in "filters should handle 3D *z* data" + // but with different calendars + var out = _transform([Lib.extendDeep({}, base, { + type: 'scatter3d', + // the same array as above but in nanakshahi dates + z: ['0547-05-05', '0548-05-17', '0548-06-17', '0548-08-07', '0548-09-19'], + zcalendar: 'nanakshahi', + transforms: [{ + type: 'filter', + operation: '>', + value: '5776-06-28', + valuecalendar: 'hebrew', + target: 'z', + // targetcalendar is ignored! + targetcalendar: 'taiwan' + }] + })]); + + expect(out[0].x).toEqual([0, 1]); + expect(out[0].y).toEqual([1, 2]); + expect(out[0].z).toEqual(['0548-08-07', '0548-09-19']); + }); + + it('should use targetcalendar anyway if there is no matching calendar attribute', function() { + // this is the same data as in "filters should handle 3D *z* data" + // but with different calendars + var out = _transform([Lib.extendDeep({}, base, { + type: 'scatter', + // the same array as above but in taiwanese dates + text: ['0104-07-20', '0105-08-01', '0105-09-01', '0105-10-21', '0105-12-02'], + transforms: [{ + type: 'filter', + operation: '>', + value: '5776-06-28', + valuecalendar: 'hebrew', + target: 'text', + targetcalendar: 'taiwan' + }] + })]); + + expect(out[0].x).toEqual([0, 1]); + expect(out[0].y).toEqual([1, 2]); + expect(out[0].text).toEqual(['0105-10-21', '0105-12-02']); + }); + + it('should use targetcalendar if target is an array', function() { + // this is the same data as in "filters should handle 3D *z* data" + // but with different calendars + var out = _transform([Lib.extendDeep({}, base, { + type: 'scatter3d', + // the same array as above but in nanakshahi dates + z: ['0547-05-05', '0548-05-17', '0548-06-17', '0548-08-07', '0548-09-19'], + zcalendar: 'nanakshahi', + transforms: [{ + type: 'filter', + operation: '>', + value: '5776-06-28', + valuecalendar: 'hebrew', + target: ['0104-07-20', '0105-08-01', '0105-09-01', '0105-10-21', '0105-12-02'], + targetcalendar: 'taiwan' + }] + })]); + + expect(out[0].x).toEqual([0, 1]); + expect(out[0].y).toEqual([1, 2]); + expect(out[0].z).toEqual(['0548-08-07', '0548-09-19']); + }); + it('filters should handle geographical *lon* data', function() { var trace0 = { type: 'scattergeo',