From e9c3ef88ad93599e256b59569084b3c237aa14d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Thu, 2 Aug 2018 12:50:57 -0400 Subject: [PATCH 01/18] :hocho: obsolete things in Bar.calc - :hocho: one obsolete comment - use trace.orientation instead using same x/y logic as in supplyDefaults. --- src/traces/bar/calc.js | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/src/traces/bar/calc.js b/src/traces/bar/calc.js index 10fc320af06..b3c5775d5e0 100644 --- a/src/traces/bar/calc.js +++ b/src/traces/bar/calc.js @@ -15,17 +15,11 @@ var arraysToCalcdata = require('./arrays_to_calcdata'); var calcSelection = require('../scatter/calc_selection'); module.exports = function calc(gd, trace) { - // depending on bar direction, set position and size axes - // and data ranges - // note: this logic for choosing orientation is - // duplicated in graph_obj->setstyles + var xa = Axes.getFromId(gd, trace.xaxis || 'x'); + var ya = Axes.getFromId(gd, trace.yaxis || 'y'); + var size, pos; - var xa = Axes.getFromId(gd, trace.xaxis || 'x'), - ya = Axes.getFromId(gd, trace.yaxis || 'y'), - orientation = trace.orientation || ((trace.x && !trace.y) ? 'h' : 'v'), - pos, size, i; - - if(orientation === 'h') { + if(trace.orientation === 'h') { size = xa.makeCalcdata(trace, 'x'); pos = ya.makeCalcdata(trace, 'y'); } else { @@ -38,7 +32,7 @@ module.exports = function calc(gd, trace) { var cd = new Array(serieslen); // set position and size - for(i = 0; i < serieslen; i++) { + for(var i = 0; i < serieslen; i++) { cd[i] = { p: pos[i], s: size[i] }; if(trace.ids) { From 80717d5e4e5d4962e0ca18674c838698e3409ae9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Thu, 9 Aug 2018 13:50:29 -0400 Subject: [PATCH 02/18] require doTicksSingle and setConvertCartesian directly --- src/plots/polar/polar.js | 40 +++++++++++++++++++--------------------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/src/plots/polar/polar.js b/src/plots/polar/polar.js index e300807ed1c..97af7bd7d25 100644 --- a/src/plots/polar/polar.js +++ b/src/plots/polar/polar.js @@ -16,10 +16,11 @@ var Lib = require('../../lib'); var Color = require('../../components/color'); var Drawing = require('../../components/drawing'); var Plots = require('../plots'); -var Axes = require('../cartesian/axes'); +var setConvertCartesian = require('../cartesian/set_convert'); var doAutoRange = require('../cartesian/autorange').doAutoRange; -var dragElement = require('../../components/dragelement'); +var doTicksSingle = require('../cartesian/axes').doTicksSingle; var dragBox = require('../cartesian/dragbox'); +var dragElement = require('../../components/dragelement'); var Fx = require('../../components/fx'); var Titles = require('../../components/titles'); var prepSelect = require('../cartesian/select').prepSelect; @@ -63,7 +64,7 @@ function Polar(gd, id) { .attr('class', id); // unfortunately, we have to keep track of some axis tick settings - // so that we don't have to call Axes.doTicksSingle with its special redraw flag + // so that we don't have to call doTicksSingle with its special redraw flag this.radialTickLayout = null; this.angularTickLayout = null; } @@ -264,7 +265,7 @@ proto.updateLayout = function(fullLayout, polarLayout) { range: [sectorBBox[0] * rSpan, sectorBBox[2] * rSpan], domain: xDomain2 }; - Axes.setConvert(xaxis, fullLayout); + setConvertCartesian(xaxis, fullLayout); xaxis.setScale(); var yaxis = _this.yaxis = { @@ -273,7 +274,7 @@ proto.updateLayout = function(fullLayout, polarLayout) { range: [sectorBBox[1] * rSpan, sectorBBox[3] * rSpan], domain: yDomain2 }; - Axes.setConvert(yaxis, fullLayout); + setConvertCartesian(yaxis, fullLayout); yaxis.setScale(); xaxis.isPtWithinRange = function(d) { return _this.isPtWithinSector(d); }; @@ -298,14 +299,16 @@ proto.updateLayout = function(fullLayout, polarLayout) { }; proto.doAutoRange = function(fullLayout, polarLayout) { + var gd = this.gd; + var radialAxis = this.radialAxis; var radialLayout = polarLayout.radialaxis; - var ax = this.radialAxis; - setScale(ax, radialLayout, fullLayout); - doAutoRange(this.gd, ax); + radialAxis.setScale(); + doAutoRange(gd, radialAxis); - radialLayout.range = ax.range.slice(); - radialLayout._input.range = ax.range.slice(); + var rng = radialAxis.range; + radialLayout.range = rng.slice(); + radialLayout._input.range = rng.slice(); }; proto.updateRadialAxis = function(fullLayout, polarLayout) { @@ -348,7 +351,8 @@ proto.updateRadialAxis = function(fullLayout, polarLayout) { _this.radialTickLayout = newTickLayout; } - Axes.doTicksSingle(gd, ax, true); + ax.setScale(); + doTicksSingle(gd, ax, true); // stash 'actual' radial axis angle for drag handlers (in degrees) var angle = _this.radialAxisAngle = _this.vangles ? @@ -468,8 +472,6 @@ proto.updateAngularAxis = function(fullLayout, polarLayout) { ax._tickFilter = function(d) { return isAngleInSector(c2rad(d), sector); }; } - setScale(ax, angularLayout, fullLayout); - ax._transfn = function(d) { var rad = c2rad(d); var xy = rad2xy(rad); @@ -530,7 +532,8 @@ proto.updateAngularAxis = function(fullLayout, polarLayout) { _this.angularTickLayout = newTickLayout; } - Axes.doTicksSingle(gd, ax, true); + ax.setScale(); + doTicksSingle(gd, ax, true); // angle of polygon vertices in radians (null means circles) // TODO what to do when ax.period > ax._categories ?? @@ -956,7 +959,7 @@ proto.updateRadialDrag = function(fullLayout, polarLayout) { if((drange > 0) !== (rprime > range0[0])) return; rng1 = radialAxis.range[1] = rprime; - Axes.doTicksSingle(gd, _this.radialAxis, true); + doTicksSingle(gd, _this.radialAxis, true); layers['radial-grid'] .attr('transform', strTranslate(cx, cy)) .selectAll('path').attr('transform', null); @@ -1112,7 +1115,7 @@ proto.updateAngularDrag = function(fullLayout, polarLayout) { } setConvertAngular(angularAxis); - Axes.doTicksSingle(gd, angularAxis, true); + doTicksSingle(gd, angularAxis, true); if(_this._hasClipOnAxisFalse && !isFullCircle(sector)) { // mutate sector to trick isPtWithinSector @@ -1205,11 +1208,6 @@ proto.fillViewInitialKey = function(key, val) { } }; -function setScale(ax, axLayout, fullLayout) { - Axes.setConvert(ax, fullLayout); - ax.setScale(); -} - function strTickLayout(axLayout) { var out = axLayout.ticks + String(axLayout.ticklen) + String(axLayout.showticklabels); if('side' in axLayout) out += axLayout.side; From 3d8eef2b7b7e258e094395e65186ddea725c6f0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Thu, 9 Aug 2018 14:09:12 -0400 Subject: [PATCH 03/18] improve polar setConvert - introduce 'g'eomtric coordinate system, that handles angular rotation / directrion and radial range shifts - add 't'icks coord system for angular axes tick vals - add setGeometry (which sets the c2g converters) - make 'c' linear theta coordinates be in radians, eliminating trace.thetaunit deps after 'calc' --- src/lib/angles.js | 5 + src/lib/index.js | 1 + src/plots/polar/helpers.js | 61 ----------- src/plots/polar/layout_defaults.js | 7 +- src/plots/polar/polar.js | 126 +++++++++------------ src/plots/polar/set_convert.js | 169 +++++++++++++++++++++++++++++ src/traces/scatterpolar/calc.js | 5 - src/traces/scatterpolar/hover.js | 22 ++-- src/traces/scatterpolar/plot.js | 58 ++++------ src/traces/scatterpolargl/index.js | 39 +++---- 10 files changed, 276 insertions(+), 217 deletions(-) delete mode 100644 src/plots/polar/helpers.js create mode 100644 src/plots/polar/set_convert.js diff --git a/src/lib/angles.js b/src/lib/angles.js index ccfd1c83ffd..b5dd27f28f9 100644 --- a/src/lib/angles.js +++ b/src/lib/angles.js @@ -27,3 +27,8 @@ exports.wrap180 = function(deg) { if(Math.abs(deg) > 180) deg -= Math.round(deg / 360) * 360; return deg; }; + +exports.isFullCircle = function(sector) { + var arc = Math.abs(sector[1] - sector[0]); + return arc === 360; +}; diff --git a/src/lib/index.js b/src/lib/index.js index 9df929ce3e4..abe8e1a8fa8 100644 --- a/src/lib/index.js +++ b/src/lib/index.js @@ -87,6 +87,7 @@ lib.deg2rad = anglesModule.deg2rad; lib.rad2deg = anglesModule.rad2deg; lib.wrap360 = anglesModule.wrap360; lib.wrap180 = anglesModule.wrap180; +lib.isFullCircle = anglesModule.isFullCircle; var geom2dModule = require('./geometry2d'); lib.segmentsIntersect = geom2dModule.segmentsIntersect; diff --git a/src/plots/polar/helpers.js b/src/plots/polar/helpers.js deleted file mode 100644 index 6a85594d1a5..00000000000 --- a/src/plots/polar/helpers.js +++ /dev/null @@ -1,61 +0,0 @@ -/** -* Copyright 2012-2018, 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 Lib = require('../../lib'); - -exports.setConvertAngular = function setConvertAngular(ax) { - var dir = {clockwise: -1, counterclockwise: 1}[ax.direction]; - var rot = Lib.deg2rad(ax.rotation); - var _c2rad; - var _rad2c; - - function getTotalNumberOfCategories() { - return ax.period ? - Math.max(ax.period, ax._categories.length) : - ax._categories.length; - } - - if(ax.type === 'linear') { - _c2rad = function(v, unit) { - if(unit === 'degrees') return Lib.deg2rad(v); - return v; - }; - _rad2c = function(v, unit) { - if(unit === 'degrees') return Lib.rad2deg(v); - return v; - }; - } - else if(ax.type === 'category') { - _c2rad = function(v) { - var tot = getTotalNumberOfCategories(); - return v * 2 * Math.PI / tot; - }; - _rad2c = function(v) { - var tot = getTotalNumberOfCategories(); - return v * tot / Math.PI / 2; - }; - } - - function transformRad(v) { return dir * v + rot; } - function unTransformRad(v) { return (v - rot) / dir; } - - // use the shift 'sector' to get right tick labels for non-default - // angularaxis 'rotation' and/or 'direction' - ax.unTransformRad = unTransformRad; - - // this version is used on hover - ax._c2rad = _c2rad; - - ax.c2rad = function(v, unit) { return transformRad(_c2rad(v, unit)); }; - ax.rad2c = function(v, unit) { return _rad2c(unTransformRad(v), unit); }; - - ax.c2deg = function(v, unit) { return Lib.rad2deg(ax.c2rad(v, unit)); }; - ax.deg2c = function(v, unit) { return ax.rad2c(Lib.deg2rad(v), unit); }; -}; diff --git a/src/plots/polar/layout_defaults.js b/src/plots/polar/layout_defaults.js index 850301deef2..1b89e221c7f 100644 --- a/src/plots/polar/layout_defaults.js +++ b/src/plots/polar/layout_defaults.js @@ -19,10 +19,9 @@ var handleTickLabelDefaults = require('../cartesian/tick_label_defaults'); var handleCategoryOrderDefaults = require('../cartesian/category_order_defaults'); var handleLineGridDefaults = require('../cartesian/line_grid_defaults'); var autoType = require('../cartesian/axis_autotype'); -var setConvert = require('../cartesian/set_convert'); -var setConvertAngular = require('./helpers').setConvertAngular; var layoutAttributes = require('./layout_attributes'); +var setConvert = require('./set_convert'); var constants = require('./constants'); var axisNames = constants.axisNames; @@ -66,7 +65,7 @@ function handleDefaults(contIn, contOut, coerce, opts) { }); var visible = coerceAxis('visible'); - setConvert(axOut, layoutOut); + setConvert(axOut, contOut, layoutOut); var dfltColor; var dfltFontColor; @@ -140,8 +139,6 @@ function handleDefaults(contIn, contOut, coerce, opts) { var direction = coerceAxis('direction'); coerceAxis('rotation', {counterclockwise: 0, clockwise: 90}[direction]); - - setConvertAngular(axOut); break; } diff --git a/src/plots/polar/polar.js b/src/plots/polar/polar.js index 97af7bd7d25..01f838c978a 100644 --- a/src/plots/polar/polar.js +++ b/src/plots/polar/polar.js @@ -17,6 +17,7 @@ var Color = require('../../components/color'); var Drawing = require('../../components/drawing'); var Plots = require('../plots'); var setConvertCartesian = require('../cartesian/set_convert'); +var setConvertPolar = require('./set_convert'); var doAutoRange = require('../cartesian/autorange').doAutoRange; var doTicksSingle = require('../cartesian/axes').doTicksSingle; var dragBox = require('../cartesian/dragbox'); @@ -29,15 +30,14 @@ var setCursor = require('../../lib/setcursor'); var polygonTester = require('../../lib/polygon').tester; var MID_SHIFT = require('../../constants/alignment').MID_SHIFT; +var constants = require('./constants'); var _ = Lib._; var deg2rad = Lib.deg2rad; var rad2deg = Lib.rad2deg; var wrap360 = Lib.wrap360; var wrap180 = Lib.wrap180; - -var setConvertAngular = require('./helpers').setConvertAngular; -var constants = require('./constants'); +var isFullCircle = Lib.isFullCircle; function Polar(gd, id) { this.id = id; @@ -161,6 +161,9 @@ proto.updateLayout = function(fullLayout, polarLayout) { var layers = _this.layers; var gs = fullLayout._size; + // axis attributes + var radialLayout = polarLayout.radialaxis; + var angularLayout = polarLayout.angularaxis; // layout domains var xDomain = polarLayout.domain.x; var yDomain = polarLayout.domain.y; @@ -211,37 +214,25 @@ proto.updateLayout = function(fullLayout, polarLayout) { var cxx = _this.cxx = cx - xOffset2; var cyy = _this.cyy = cy - yOffset2; - var mockOpts = { - // to get _boundingBox computation right when showticklabels is false - anchor: 'free', - position: 0, - // dummy truthy value to make Axes.doTicksSingle draw the grid - _counteraxis: true, - // don't use automargins routine for labels - automargin: false - }; - - _this.radialAxis = Lib.extendFlat({}, polarLayout.radialaxis, mockOpts, { + _this.radialAxis = _this.mockAxis(fullLayout, polarLayout, radialLayout, { _axislayer: layers['radial-axis'], _gridlayer: layers['radial-grid'], // make this an 'x' axis to make positioning (especially rotation) easier _id: 'x', - _pos: 0, // convert to 'x' axis equivalent side: { counterclockwise: 'top', clockwise: 'bottom' - }[polarLayout.radialaxis.side], + }[radialLayout.side], // spans length 1 radius domain: [0, radius / gs.w] }); - _this.angularAxis = Lib.extendFlat({}, polarLayout.angularaxis, mockOpts, { + _this.angularAxis = _this.mockAxis(fullLayout, polarLayout, angularLayout, { _axislayer: layers['angular-axis'], _gridlayer: layers['angular-grid'], // angular axes need *special* logic _id: 'angular', - _pos: 0, side: 'right', // to get auto nticks right domain: [0, Math.PI], @@ -298,6 +289,23 @@ proto.updateLayout = function(fullLayout, polarLayout) { _this.framework.selectAll('.crisp').classed('crisp', 0); }; +proto.mockAxis = function(fullLayout, polarLayout, axLayout, opts) { + var commonOpts = { + // to get _boundingBox computation right when showticklabels is false + anchor: 'free', + position: 0, + _pos: 0, + // dummy truthy value to make doTicksSingle draw the grid + _counteraxis: true, + // don't use automargins routine for labels + automargin: false + }; + + var ax = Lib.extendFlat(commonOpts, axLayout, opts); + setConvertPolar(ax, polarLayout, fullLayout); + return ax; +}; + proto.doAutoRange = function(fullLayout, polarLayout) { var gd = this.gd; var radialAxis = this.radialAxis; @@ -326,6 +334,8 @@ proto.updateRadialAxis = function(fullLayout, polarLayout) { _this.fillViewInitialKey('radialaxis.angle', radialLayout.angle); _this.fillViewInitialKey('radialaxis.range', ax.range.slice()); + ax.setGeometry(); + // rotate auto tick labels by 180 if in quadrant II and III to make them // readable from left-to-right // @@ -428,52 +438,35 @@ proto.updateAngularAxis = function(fullLayout, polarLayout) { var cy = _this.cy; var angularLayout = polarLayout.angularaxis; var sector = polarLayout.sector; - var sectorInRad = sector.map(deg2rad); var ax = _this.angularAxis; _this.fillViewInitialKey('angularaxis.rotation', angularLayout.rotation); - // wrapper around c2rad from setConvertAngular - // note that linear ranges are always set in degrees for Axes.doTicksSingle - function c2rad(d) { - return ax.c2rad(d.x, 'degrees'); - } + ax.setGeometry(); + + // 't'ick to 'g'eometric radians is used all over the place here + var t2g = function(d) { return ax.t2g(d.x); }; // (x,y) at max radius function rad2xy(rad) { return [radius * Math.cos(rad), radius * Math.sin(rad)]; } - // Set the angular range in degrees to make auto-tick computation cleaner, - // changing rotation/direction should not affect the angular tick labels. - if(ax.type === 'linear') { - if(isFullCircle(sector)) { - ax.range = sector.slice(); - } else { - ax.range = sectorInRad.map(ax.unTransformRad).map(rad2deg); - } - - // run rad2deg on tick0 and ditck for thetaunit: 'radians' axes - if(ax.thetaunit === 'radians') { - ax.tick0 = rad2deg(ax.tick0); - ax.dtick = rad2deg(ax.dtick); - } - + // run rad2deg on tick0 and ditck for thetaunit: 'radians' axes + if(ax.type === 'linear' && ax.thetaunit === 'radians') { + ax.tick0 = rad2deg(ax.tick0); + ax.dtick = rad2deg(ax.dtick); } + // Use tickval filter for category axes instead of tweaking // the range w.r.t sector, so that sectors that cross 360 can // show all their ticks. - else if(ax.type === 'category') { - var period = angularLayout.period ? - Math.max(angularLayout.period, angularLayout._categories.length) : - angularLayout._categories.length; - - ax.range = [0, period]; - ax._tickFilter = function(d) { return isAngleInSector(c2rad(d), sector); }; + if(ax.type === 'category') { + ax._tickFilter = function(d) { return isAngleInSector(t2g(d), sector); }; } ax._transfn = function(d) { - var rad = c2rad(d); + var rad = t2g(d); var xy = rad2xy(rad); var out = strTranslate(cx + xy[0], cy - xy[1]); @@ -487,7 +480,7 @@ proto.updateAngularAxis = function(fullLayout, polarLayout) { }; ax._gridpath = function(d) { - var rad = c2rad(d); + var rad = t2g(d); var xy = rad2xy(rad); return 'M0,0L' + (-xy[0]) + ',' + xy[1]; }; @@ -495,7 +488,7 @@ proto.updateAngularAxis = function(fullLayout, polarLayout) { var offset4fontsize = (angularLayout.ticks !== 'outside' ? 0.7 : 0.5); ax._labelx = function(d) { - var rad = c2rad(d); + var rad = t2g(d); var labelStandoff = ax._labelStandoff; var pad = ax._pad; @@ -508,7 +501,7 @@ proto.updateAngularAxis = function(fullLayout, polarLayout) { }; ax._labely = function(d) { - var rad = c2rad(d); + var rad = t2g(d); var labelStandoff = ax._labelStandoff; var labelShift = ax._labelShift; var pad = ax._pad; @@ -520,7 +513,7 @@ proto.updateAngularAxis = function(fullLayout, polarLayout) { }; ax._labelanchor = function(angle, d) { - var rad = c2rad(d); + var rad = t2g(d); return signSin(rad) === 0 ? (signCos(rad) > 0 ? 'start' : 'end') : 'middle'; @@ -535,11 +528,11 @@ proto.updateAngularAxis = function(fullLayout, polarLayout) { ax.setScale(); doTicksSingle(gd, ax, true); - // angle of polygon vertices in radians (null means circles) + // angle of polygon vertices in geometric radians (null means circles) // TODO what to do when ax.period > ax._categories ?? var vangles; if(polarLayout.gridshape === 'linear') { - vangles = ax._vals.map(c2rad); + vangles = ax._vals.map(t2g); // ax._vals should be always ordered, make them // always turn counterclockwise for convenience here @@ -1014,6 +1007,7 @@ proto.updateAngularDrag = function(fullLayout, polarLayout) { var gd = _this.gd; var layers = _this.layers; var radius = _this.radius; + var angularAxis = _this.angularAxis; var cx = _this.cx; var cy = _this.cy; var cxx = _this.cxx; @@ -1054,8 +1048,6 @@ proto.updateAngularDrag = function(fullLayout, polarLayout) { var rot0, rot1; // induced radial axis rotation (only used on polygon grids) var rrot1; - // copy of polar sector value at drag start - var sector0; // angle about circle center at drag start var a0; @@ -1104,22 +1096,14 @@ proto.updateAngularDrag = function(fullLayout, polarLayout) { sel.attr('transform', strRotate([da, tx.attr('x'), tx.attr('y')]) + strTranslate(xy.x, xy.y)); }); - var angularAxis = _this.angularAxis; + // update rotation -> range -> _m,_b angularAxis.rotation = wrap180(rot1); + angularAxis.setGeometry(); + angularAxis.setScale(); - if(angularAxis.type === 'linear' && !isFullCircle(sector)) { - angularAxis.range = sector0 - .map(deg2rad) - .map(angularAxis.unTransformRad) - .map(rad2deg); - } - - setConvertAngular(angularAxis); doTicksSingle(gd, angularAxis, true); if(_this._hasClipOnAxisFalse && !isFullCircle(sector)) { - // mutate sector to trick isPtWithinSector - _this.sector = [sector0[0] - da, sector0[1] - da]; scatterTraces.call(Drawing.hideOutsideRangePoints, _this); } @@ -1148,7 +1132,6 @@ proto.updateAngularDrag = function(fullLayout, polarLayout) { dragOpts.prepFn = function(evt, startX, startY) { var polarLayoutNow = fullLayout[_this.id]; - sector0 = polarLayoutNow.sector.slice(); rot0 = polarLayoutNow.angularaxis.rotation; var bbox = angularDrag.getBoundingClientRect(); @@ -1173,8 +1156,9 @@ proto.updateAngularDrag = function(fullLayout, polarLayout) { proto.isPtWithinSector = function(d) { var sector = this.sector; + var thetag = this.angularAxis.c2g(d.theta); - if(!isAngleInSector(d.rad, sector)) { + if(!isAngleInSector(thetag, sector)) { return false; } @@ -1195,7 +1179,7 @@ proto.isPtWithinSector = function(d) { if(vangles) { var polygonIn = polygonTester(makePolygon(r0, sector, vangles)); var polygonOut = polygonTester(makePolygon(r1, sector, vangles)); - var xy = [r * Math.cos(d.rad), r * Math.sin(d.rad)]; + var xy = [r * Math.cos(thetag), r * Math.sin(thetag)]; return polygonOut.contains(xy) && !polygonIn.contains(xy); } @@ -1539,10 +1523,6 @@ function pathAnnulus(r0, r1, sector) { } } -function isFullCircle(sector) { - var arc = Math.abs(sector[1] - sector[0]); - return arc === 360; -} function updateElement(sel, showAttr, attrs) { if(showAttr) { diff --git a/src/plots/polar/set_convert.js b/src/plots/polar/set_convert.js new file mode 100644 index 00000000000..ac930aa9de2 --- /dev/null +++ b/src/plots/polar/set_convert.js @@ -0,0 +1,169 @@ +/** +* Copyright 2012-2018, 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 Lib = require('../../lib'); +var setConvertCartesian = require('../cartesian/set_convert'); + +var deg2rad = Lib.deg2rad; +var rad2deg = Lib.rad2deg; +var isFullCircle = Lib.isFullCircle; + +/** + * setConvert for polar axes! + * + * @param {object} ax + * axis in question (works for both radial and angular axes) + * @param {object} polarLayout + * full polar layout of the subplot associated with 'ax' + * @param {object} fullLayout + * full layout + * + * Here, reuse some of the Cartesian setConvert logic, + * but we must extend some of it, as both radial and angular axes + * don't have domains and angular axes don't have _true_ ranges. + * + * Moreover, we introduce two new coordinate systems: + * - 'g' for geometric coordinates and + * - 't' for angular ticks + * + * Radial axis coordinate systems flow: + * - d2c (in calc) just like for cartesian axes + * - c2g (in plot) translates calcdata about `radialaxis.range[0]` + * + * Angular axis coordinate systems flow: + * + for linear axes: + * - d2c (in calc) angles -> 'data' radians + * - c2g (in plot) 'data' -> 'geometric' radians (fn of ax rotation & direction) + * - t2g (in updateAngularAxis) 'tick' value (in degrees) -> 'geometric' radians + * + for category axes: + * - d2c (in calc) just like for cartesian axes + * - c2g (in plot) category indices -> 'geometric' radians + * - t2g (in updateAngularAxis) 'tick' value (as category indices) -> 'geometric' radians + * + * Then, 'g'eometric data is ready to be converted to (x,y). + */ +module.exports = function setConvert(ax, polarLayout, fullLayout) { + setConvertCartesian(ax, fullLayout); + + switch(ax._id) { + case 'x': + case 'radialaxis': + setConvertRadial(ax); + break; + case 'angular': + case 'angularaxis': + setConvertAngular(ax, polarLayout); + break; + } +}; + +function setConvertRadial(ax) { + ax.setGeometry = function() { + var rng = ax.range; + + var rFilter = rng[0] > rng[1] ? + function(v) { return v <= 0; } : + function(v) { return v >= 0; }; + + ax.c2g = function(v) { + var r = ax.c2r(v) - rng[0]; + return rFilter(r) ? r : 0; + }; + + ax.g2c = function(v) { + return ax.r2c(v + rng[0]); + }; + }; +} + +function toRadians(v, unit) { + return unit === 'degrees' ? deg2rad(v) : v; +} + +function fromRadians(v, unit) { + return unit === 'degrees' ? rad2deg(v) : v; +} + +function setConvertAngular(ax, polarLayout) { + var axType = ax.type; + + if(axType === 'linear') { + var _d2c = ax.d2c; + var _c2d = ax.c2d; + + ax.d2c = function(v, unit) { return toRadians(_d2c(v), unit); }; + ax.c2d = function(v, unit) { return _c2d(fromRadians(v, unit)); }; + } + + // override makeCalcdata to handle thetaunit and special theta0/dtheta logic + ax.makeCalcdata = function(trace, coord) { + var arrayIn = trace[coord]; + var len = trace._length; + var arrayOut, i; + + var _d2c = function(v) { return ax.d2c(v, trace.thetaunit); }; + + if(arrayIn) { + if(Lib.isTypedArray(arrayIn) && axType === 'linear') { + if(len === arrayIn.length) { + return arrayIn; + } else if(arrayIn.subarray) { + return arrayIn.subarray(0, len); + } + } + + arrayOut = new Array(len); + for(i = 0; i < len; i++) { + arrayOut[i] = _d2c(arrayIn[i]); + } + } + return arrayOut; + }; + + // N.B. we mock the axis 'range' here + ax.setGeometry = function() { + var sector = polarLayout.sector; + var dir = {clockwise: -1, counterclockwise: 1}[ax.direction]; + var rot = deg2rad(ax.rotation); + + ax.rad2g = function(v) { return dir * v + rot; }; + ax.g2rad = function(v) { return (v - rot) / dir; }; + + switch(axType) { + case 'linear': + ax.c2rad = ax.rad2c = Lib.identity; + ax.t2rad = deg2rad; + ax.rad2r = rad2deg; + + // Set the angular range in degrees to make auto-tick computation cleaner, + // changing rotation/direction should not affect the angular tick value. + ax.range = isFullCircle(sector) ? + sector.slice() : + sector.map(deg2rad).map(ax.g2rad).map(rad2deg); + break; + + case 'category': + var catLen = ax._categories.length; + var _period = ax._period = ax.period ? Math.max(ax.period, catLen) : catLen; + + ax.c2rad = ax.t2rad = function(v) { return v * 2 * Math.PI / _period; }; + ax.rad2c = ax.rad2t = function(v) { return v * _period / Math.PI / 2; }; + + ax.range = [0, _period]; + break; + } + + ax.c2g = function(v) { return ax.rad2g(ax.c2rad(v)); }; + ax.g2c = function(v) { return ax.rad2c(ax.g2rad(v)); }; + + ax.t2g = function(v) { return ax.rad2g(ax.t2rad(v)); }; + ax.g2t = function(v) { return ax.rad2t(ax.g2rad(v)); }; + }; +} diff --git a/src/traces/scatterpolar/calc.js b/src/traces/scatterpolar/calc.js index f2f10dd7f2d..69d1cd55600 100644 --- a/src/traces/scatterpolar/calc.js +++ b/src/traces/scatterpolar/calc.js @@ -29,10 +29,6 @@ module.exports = function calc(gd, trace) { var len = trace._length; var cd = new Array(len); - function c2rad(v) { - return angularAxis.c2rad(v, trace.thetaunit); - } - for(var i = 0; i < len; i++) { var r = rArray[i]; var theta = thetaArray[i]; @@ -41,7 +37,6 @@ module.exports = function calc(gd, trace) { if(isNumeric(r) && isNumeric(theta)) { cdi.r = r; cdi.theta = theta; - cdi.rad = c2rad(theta); } else { cdi.r = BADNUM; } diff --git a/src/traces/scatterpolar/hover.js b/src/traces/scatterpolar/hover.js index 8202724a332..6eaad2950c0 100644 --- a/src/traces/scatterpolar/hover.js +++ b/src/traces/scatterpolar/hover.js @@ -46,23 +46,21 @@ function makeHoverPointText(cdi, trace, subplot) { radialAxis._hovertitle = 'r'; angularAxis._hovertitle = 'θ'; - var rad = angularAxis._c2rad(cdi.theta, trace.thetaunit); - - // show theta value in unit of angular axis - var theta; - if(angularAxis.type === 'linear' && trace.thetaunit !== angularAxis.thetaunit) { - theta = angularAxis.thetaunit === 'degrees' ? Lib.rad2deg(rad) : rad; - } else { - theta = cdi.theta; - } - function textPart(ax, val) { text.push(ax._hovertitle + ': ' + Axes.tickText(ax, val, 'hover').text); } if(parts.indexOf('all') !== -1) parts = ['r', 'theta']; - if(parts.indexOf('r') !== -1) textPart(radialAxis, radialAxis.c2r(cdi.r)); - if(parts.indexOf('theta') !== -1) textPart(angularAxis, theta); + if(parts.indexOf('r') !== -1) { + textPart(radialAxis, radialAxis.c2r(cdi.r)); + } + if(parts.indexOf('theta') !== -1) { + var theta = cdi.theta; + textPart( + angularAxis, + angularAxis.thetaunit === 'degrees' ? Lib.rad2deg(theta) : theta + ); + } return text.join('
'); } diff --git a/src/traces/scatterpolar/plot.js b/src/traces/scatterpolar/plot.js index e0018ddfc85..cde1dd613be 100644 --- a/src/traces/scatterpolar/plot.js +++ b/src/traces/scatterpolar/plot.js @@ -12,7 +12,7 @@ var scatterPlot = require('../scatter/plot'); var BADNUM = require('../../constants/numerical').BADNUM; module.exports = function plot(gd, subplot, moduleCalcData) { - var i, j; + var mlayer = subplot.layers.frontplot.select('g.scatterlayer'); var plotinfo = { xaxis: subplot.xaxis, @@ -22,45 +22,27 @@ module.exports = function plot(gd, subplot, moduleCalcData) { }; var radialAxis = subplot.radialAxis; - var radialRange = radialAxis.range; - var rFilter; - - if(radialRange[0] > radialRange[1]) { - rFilter = function(v) { return v <= 0; }; - } else { - rFilter = function(v) { return v >= 0; }; - } - - // map (r, theta) first to a 'geometric' r and then to (x,y) - // on-par with what scatterPlot expects. - - for(i = 0; i < moduleCalcData.length; i++) { - for(j = 0; j < moduleCalcData[i].length; j++) { - var cdi = moduleCalcData[i][j]; - var r = cdi.r; - - if(r !== BADNUM) { - // convert to 'r' data to fit with mocked polar x/y axis - // which are always `type: 'linear'` - var rr = radialAxis.c2r(r) - radialRange[0]; - if(rFilter(rr)) { - var rad = cdi.rad; - cdi.x = rr * Math.cos(rad); - cdi.y = rr * Math.sin(rad); - continue; - } else { - // flag for scatter/line_points.js - // to extend line (and fills) into center - cdi.intoCenter = [subplot.cxx, subplot.cyy]; - } + var angularAxis = subplot.angularAxis; + + // convert: + // 'c' (r,theta) -> 'geometric' (r,theta) -> (x,y) + for(var i = 0; i < moduleCalcData.length; i++) { + var cdi = moduleCalcData[i]; + + for(var j = 0; j < cdi.length; j++) { + var cd = cdi[j]; + var r = cd.r; + + if(r === BADNUM) { + cd.x = cd.y = BADNUM; + } else { + var rg = radialAxis.c2g(r); + var thetag = angularAxis.c2g(cd.theta); + cd.x = rg * Math.cos(thetag); + cd.y = rg * Math.sin(thetag); } - - cdi.x = BADNUM; - cdi.y = BADNUM; } } - var scatterLayer = subplot.layers.frontplot.select('g.scatterlayer'); - - scatterPlot(gd, plotinfo, moduleCalcData, scatterLayer); + scatterPlot(gd, plotinfo, moduleCalcData, mlayer); }; diff --git a/src/traces/scatterpolargl/index.js b/src/traces/scatterpolargl/index.js index b5a16ed6937..d6a24114b55 100644 --- a/src/traces/scatterpolargl/index.js +++ b/src/traces/scatterpolargl/index.js @@ -44,7 +44,6 @@ function calc(container, trace) { function plot(container, subplot, cdata) { var radialAxis = subplot.radialAxis; var angularAxis = subplot.angularAxis; - var rRange = radialAxis.range; var scene = ScatterGl.sceneUpdate(container, subplot); scene.clear(); @@ -56,42 +55,38 @@ function plot(container, subplot, cdata) { var stash = cd.t; var rArray = stash.r; var thetaArray = stash.theta; - var i, r, rr, theta, rad; + var i; var subRArray = rArray.slice(); var subThetaArray = thetaArray.slice(); // filter out by range for(i = 0; i < rArray.length; i++) { - r = rArray[i], theta = thetaArray[i]; - rad = angularAxis.c2rad(theta, trace.thetaunit); - - if(!subplot.isPtWithinSector({r: r, rad: rad})) { + if(!subplot.isPtWithinSector({r: rArray[i], theta: thetaArray[i]})) { subRArray[i] = NaN; subThetaArray[i] = NaN; } } var count = rArray.length; - var positions = new Array(count * 2), x = Array(count), y = Array(count); - - function c2rad(v) { - return angularAxis.c2rad(v, trace.thetaunit); - } + var positions = new Array(count * 2); + var x = Array(count); + var y = Array(count); for(i = 0; i < count; i++) { - r = subRArray[i]; - theta = subThetaArray[i]; - - if(isNumeric(r) && isNumeric(theta) && r >= 0) { - rr = radialAxis.c2r(r) - rRange[0]; - rad = c2rad(theta); - - x[i] = positions[i * 2] = rr * Math.cos(rad); - y[i] = positions[i * 2 + 1] = rr * Math.sin(rad); + var r = subRArray[i]; + var xx, yy; + + if(isNumeric(r)) { + var rg = radialAxis.c2g(r); + var thetag = angularAxis.c2g(subThetaArray[i], trace.thetaunit); + xx = rg * Math.cos(thetag); + yy = rg * Math.sin(thetag); } else { - x[i] = y[i] = positions[i * 2] = positions[i * 2 + 1] = NaN; + xx = yy = NaN; } + x[i] = positions[i * 2] = xx; + y[i] = positions[i * 2 + 1] = yy; } var options = ScatterGl.sceneOptions(container, subplot, trace, positions); @@ -156,14 +151,12 @@ function hoverPoints(pointData, xval, yval, hovermode) { } var subplot = pointData.subplot; - var angularAxis = subplot.angularAxis; var cdi = newPointData.cd[newPointData.index]; var trace = newPointData.trace; // augment pointData with r/theta param cdi.r = rArray[newPointData.index]; cdi.theta = thetaArray[newPointData.index]; - cdi.rad = angularAxis.c2rad(cdi.theta, trace.thetaunit); if(!subplot.isPtWithinSector(cdi)) return; From 87a392065c613a6be6225331050888028adaac28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Thu, 9 Aug 2018 14:29:02 -0400 Subject: [PATCH 04/18] :hocho: now useless intoCenter line calcdata hack --- src/traces/scatter/line_points.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/traces/scatter/line_points.js b/src/traces/scatter/line_points.js index ef84cb0cdaa..8e498e46466 100644 --- a/src/traces/scatter/line_points.js +++ b/src/traces/scatter/line_points.js @@ -59,7 +59,7 @@ module.exports = function linePoints(d, opts) { if(!di) return false; var x = xa.c2p(di.x); var y = ya.c2p(di.y); - if(x === BADNUM || y === BADNUM) return di.intoCenter || false; + if(x === BADNUM || y === BADNUM) return false; return [x, y]; } From 91064cac5c3f7ed14741c96641f9a7436a19c33b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Thu, 9 Aug 2018 14:30:03 -0400 Subject: [PATCH 05/18] add :books: about axes in polar subplots --- src/plots/polar/polar.js | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/plots/polar/polar.js b/src/plots/polar/polar.js index 01f838c978a..8cc57c66343 100644 --- a/src/plots/polar/polar.js +++ b/src/plots/polar/polar.js @@ -156,6 +156,32 @@ proto.updateLayers = function(fullLayout, polarLayout) { join.order(); }; +/* Polar subplots juggle with 6 'axis objects' (!), these are: + * + * - polarLayout.radialaxis (aka radialLayout in this file): + * - polarLayout.angularaxis (aka angularLayout in this file): + * used for data -> calcdata conversions (aka d2c) during the calc step + * + * - this.radialAxis + * extends polarLayout.radialaxis, adds mocked 'domain' and + * few other keys in order to reuse Cartesian doAutoRange and doTicksSingle, + * used for calcdata -> geometric conversions (aka c2g) during the plot step + * + setGeometry setups ax.c2g for given ax.range + * + setScale setups ax._m,ax._b for given ax.range + * + * - this.angularAxis + * extends polarLayout.angularaxis, adds mocked 'range' and 'domain' and + * a few other keys in order to reuse Cartesian doTicksSingle, + * used for calcdata -> geometric conversions (aka c2g) during the plot step + * + setGeometry setups ax.c2g given ax.rotation, ax.direction & ax._categories, + * and mocks ax.range + * + setScale setups ax._m,ax._b with that mocked ax.range + * + * - this.xaxis + * - this.yaxis + * setup so that polar traces can reuse plot methods of Cartesian traces + * which mostly rely on 2pixel methods (e.g ax.c2p) + */ proto.updateLayout = function(fullLayout, polarLayout) { var _this = this; var layers = _this.layers; From 3787bfac2f5f705ad12ca3db843031e0d4832e5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Thu, 9 Aug 2018 15:15:52 -0400 Subject: [PATCH 06/18] sub angularAxis._id 'angular' -> 'angularaxis' ... which is on-par with fullLayout.polar.angularaxis --- src/plots/cartesian/axes.js | 14 +++++++------- src/plots/polar/polar.js | 6 ++---- src/plots/polar/set_convert.js | 1 - test/jasmine/tests/polar_test.js | 14 +++++++------- 4 files changed, 16 insertions(+), 19 deletions(-) diff --git a/src/plots/cartesian/axes.js b/src/plots/cartesian/axes.js index 7fcaf882633..a32da7bcfc2 100644 --- a/src/plots/cartesian/axes.js +++ b/src/plots/cartesian/axes.js @@ -554,7 +554,7 @@ axes.calcTicks = function calcTicks(ax) { // If same angle over a full circle, the last tick vals is a duplicate. // TODO must do something similar for angular date axes. - if(ax._id === 'angular' && Math.abs(rng[1] - rng[0]) === 360) { + if(ax._id === 'angularaxis' && Math.abs(rng[1] - rng[0]) === 360) { vals.pop(); } @@ -722,7 +722,7 @@ axes.autoTicks = function(ax, roughDTick) { ax.tick0 = 0; ax.dtick = Math.ceil(Math.max(roughDTick, 1)); } - else if(ax._id === 'angular') { + else if(ax._id === 'angularaxis') { ax.tick0 = 0; base = 1; ax.dtick = roundDTick(roughDTick, base, roundAngles); @@ -958,7 +958,7 @@ axes.tickText = function(ax, x, hover) { if(ax.type === 'date') formatDate(ax, out, hover, extraPrecision); else if(ax.type === 'log') formatLog(ax, out, hover, extraPrecision, hideexp); else if(ax.type === 'category') formatCategory(ax, out); - else if(ax._id === 'angular') formatAngle(ax, out, hover, extraPrecision, hideexp); + else if(ax._id === 'angularaxis') formatAngle(ax, out, hover, extraPrecision, hideexp); else formatLinear(ax, out, hover, extraPrecision, hideexp); // add prefix and suffix @@ -1646,7 +1646,7 @@ axes.doTicksSingle = function(gd, arg, skipTitle) { else return 'M' + shift + ',0h' + len; }; } - else if(axid === 'angular') { + else if(axid === 'angularaxis') { sides = ['left', 'right']; transfn = ax._transfn; tickpathfn = function(shift, len) { @@ -1682,7 +1682,7 @@ axes.doTicksSingle = function(gd, arg, skipTitle) { var valsClipped = vals.filter(clipEnds); // don't clip angular values - if(ax._id === 'angular') { + if(ax._id === 'angularaxis') { valsClipped = vals; } @@ -1751,7 +1751,7 @@ axes.doTicksSingle = function(gd, arg, skipTitle) { return axside === 'right' ? 'start' : 'end'; }; } - else if(axid === 'angular') { + else if(axid === 'angularaxis') { ax._labelShift = labelShift; ax._labelStandoff = labelStandoff; ax._pad = pad; @@ -1798,7 +1798,7 @@ axes.doTicksSingle = function(gd, arg, skipTitle) { maxFontSize = Math.max(maxFontSize, d.fontSize); }); - if(axid === 'angular') { + if(axid === 'angularaxis') { tickLabels.each(function(d) { d3.select(this).select('text') .call(svgTextUtils.positionText, labelx(d), labely(d)); diff --git a/src/plots/polar/polar.js b/src/plots/polar/polar.js index 8cc57c66343..396687e1a81 100644 --- a/src/plots/polar/polar.js +++ b/src/plots/polar/polar.js @@ -142,7 +142,7 @@ proto.updateLayers = function(fullLayout, polarLayout) { break; case 'angular-grid': sel.style('fill', 'none'); - sel.append('g').classed('angular', 1); + sel.append('g').classed('angularaxis', 1); break; case 'radial-line': sel.append('line').style('fill', 'none'); @@ -257,8 +257,6 @@ proto.updateLayout = function(fullLayout, polarLayout) { _this.angularAxis = _this.mockAxis(fullLayout, polarLayout, angularLayout, { _axislayer: layers['angular-axis'], _gridlayer: layers['angular-grid'], - // angular axes need *special* logic - _id: 'angular', side: 'right', // to get auto nticks right domain: [0, Math.PI], @@ -547,7 +545,7 @@ proto.updateAngularAxis = function(fullLayout, polarLayout) { var newTickLayout = strTickLayout(angularLayout); if(_this.angularTickLayout !== newTickLayout) { - layers['angular-axis'].selectAll('.angulartick').remove(); + layers['angular-axis'].selectAll('.' + ax._id + 'tick').remove(); _this.angularTickLayout = newTickLayout; } diff --git a/src/plots/polar/set_convert.js b/src/plots/polar/set_convert.js index ac930aa9de2..2b1236a45c2 100644 --- a/src/plots/polar/set_convert.js +++ b/src/plots/polar/set_convert.js @@ -57,7 +57,6 @@ module.exports = function setConvert(ax, polarLayout, fullLayout) { case 'radialaxis': setConvertRadial(ax); break; - case 'angular': case 'angularaxis': setConvertAngular(ax, polarLayout); break; diff --git a/test/jasmine/tests/polar_test.js b/test/jasmine/tests/polar_test.js index f7fb263a07c..60bc10c920b 100644 --- a/test/jasmine/tests/polar_test.js +++ b/test/jasmine/tests/polar_test.js @@ -306,14 +306,14 @@ describe('Test relayout on polar subplots:', function() { var pos1 = []; Plotly.plot(gd, fig).then(function() { - d3.selectAll('.angulartick> text').each(function() { + d3.selectAll('.angularaxistick > text').each(function() { var tx = d3.select(this); pos0.push([tx.attr('x'), tx.attr('y')]); }); return Plotly.relayout(gd, 'polar.angularaxis.rotation', 90); }) .then(function() { - d3.selectAll('.angulartick> text').each(function() { + d3.selectAll('.angularaxistick > text').each(function() { var tx = d3.select(this); pos1.push([tx.attr('x'), tx.attr('y')]); }); @@ -330,7 +330,7 @@ describe('Test relayout on polar subplots:', function() { var fig = Lib.extendDeep({}, require('@mocks/polar_scatter.json')); function check(cnt, expected) { - var ticks = d3.selectAll('path.angulartick'); + var ticks = d3.selectAll('path.angularaxistick'); expect(ticks.size()).toBe(cnt, '# of ticks'); ticks.each(function() { @@ -433,21 +433,21 @@ describe('Test relayout on polar subplots:', function() { return toggle( 'polar.angularaxis.showgrid', [true, false], [8, 0], - '.angular-grid > .angular > path', assertCnt + '.angular-grid > .angularaxis > path', assertCnt ); }) .then(function() { return toggle( 'polar.angularaxis.showticklabels', [true, false], [8, 0], - '.angular-axis > .angulartick > text', assertCnt + '.angular-axis > .angularaxistick > text', assertCnt ); }) .then(function() { return toggle( 'polar.angularaxis.ticks', ['outside', ''], [8, 0], - '.angular-axis > path.angulartick', assertCnt + '.angular-axis > path.angularaxistick', assertCnt ); }) .catch(failTest) @@ -558,7 +558,7 @@ describe('Test relayout on polar subplots:', function() { expect(gd._fullLayout.polar._subplot.angularAxis.range) .toBeCloseToArray([0, exp.period], 2, 'range in mocked angular axis - ' + msg); - expect(d3.selectAll('path.angulartick').size()) + expect(d3.selectAll('path.angularaxistick').size()) .toBe(exp.nTicks, '# of visible angular ticks - ' + msg); expect([gd.calcdata[0][5].x, gd.calcdata[0][5].y]) From 4f881faec1be586d4808564a08c4c6692d3f6b55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Thu, 9 Aug 2018 17:27:59 -0400 Subject: [PATCH 07/18] add r0/dr theta0/dtheta - where the dtheta default is the ax.period divided by the trace length (given by the length of the 'other' coordinate). --- src/plots/polar/layout_defaults.js | 2 +- src/plots/polar/set_convert.js | 11 +++ src/traces/scatterpolar/attributes.js | 43 ++++++++++ src/traces/scatterpolar/defaults.js | 38 +++++++-- src/traces/scatterpolar/index.js | 2 +- src/traces/scatterpolargl/attributes.js | 4 + src/traces/scatterpolargl/defaults.js | 8 +- .../baselines/polar_r0dr-theta0dtheta.png | Bin 0 -> 71519 bytes test/image/mocks/polar_r0dr-theta0dtheta.json | 77 ++++++++++++++++++ test/jasmine/tests/scatterpolar_test.js | 37 +++++++++ 10 files changed, 207 insertions(+), 15 deletions(-) create mode 100644 test/image/baselines/polar_r0dr-theta0dtheta.png create mode 100644 test/image/mocks/polar_r0dr-theta0dtheta.json diff --git a/src/plots/polar/layout_defaults.js b/src/plots/polar/layout_defaults.js index 1b89e221c7f..42fcbad3a4d 100644 --- a/src/plots/polar/layout_defaults.js +++ b/src/plots/polar/layout_defaults.js @@ -198,7 +198,7 @@ function handleAxisTypeDefaults(axIn, axOut, coerce, subplotData, dataAttr) { } } - if(trace) { + if(trace && trace[dataAttr]) { axOut.type = autoType(trace[dataAttr], 'gregorian'); } diff --git a/src/plots/polar/set_convert.js b/src/plots/polar/set_convert.js index 2b1236a45c2..314ed07ea1f 100644 --- a/src/plots/polar/set_convert.js +++ b/src/plots/polar/set_convert.js @@ -122,7 +122,18 @@ function setConvertAngular(ax, polarLayout) { for(i = 0; i < len; i++) { arrayOut[i] = _d2c(arrayIn[i]); } + } else { + var coord0 = coord + '0'; + var dcoord = 'd' + coord; + var v0 = (coord0 in trace) ? _d2c(trace[coord0]) : 0; + var dv = (trace[dcoord]) ? _d2c(trace[dcoord]) : (ax.period || 2 * Math.PI) / len; + + arrayOut = new Array(len); + for(i = 0; i < len; i++) { + arrayOut[i] = v0 + i * dv; + } } + return arrayOut; }; diff --git a/src/traces/scatterpolar/attributes.js b/src/traces/scatterpolar/attributes.js index 576780bd047..e05c8f31da3 100644 --- a/src/traces/scatterpolar/attributes.js +++ b/src/traces/scatterpolar/attributes.js @@ -28,6 +28,49 @@ module.exports = { description: 'Sets the angular coordinates' }, + r0: { + valType: 'any', + dflt: 0, + role: 'info', + editType: 'calc+clearAxisTypes', + description: [ + 'Alternate to `r`.', + 'Builds a linear space of r coordinates.', + 'Use with `dr`', + 'where `r0` is the starting coordinate and `dr` the step.' + ].join(' ') + }, + dr: { + valType: 'number', + dflt: 1, + role: 'info', + editType: 'calc', + description: 'Sets the r coordinate step.' + }, + + theta0: { + valType: 'any', + dflt: 0, + role: 'info', + editType: 'calc+clearAxisTypes', + description: [ + 'Alternate to `theta`.', + 'Builds a linear space of theta coordinates.', + 'Use with `dtheta`', + 'where `theta0` is the starting coordinate and `dtheta` the step.' + ].join(' ') + }, + dtheta: { + valType: 'number', + role: 'info', + editType: 'calc', + description: [ + 'Sets the theta coordinate step.', + 'By default, the `dtheta` step equals the subplot\'s period divided', + 'by the length of the `r` coordinates.' + ].join(' ') + }, + thetaunit: { valType: 'enumerated', values: ['radians', 'degrees', 'gradians'], diff --git a/src/traces/scatterpolar/defaults.js b/src/traces/scatterpolar/defaults.js index 5fe19016b48..1e3a47f02ce 100644 --- a/src/traces/scatterpolar/defaults.js +++ b/src/traces/scatterpolar/defaults.js @@ -20,22 +20,17 @@ var PTS_LINESONLY = require('../scatter/constants').PTS_LINESONLY; var attributes = require('./attributes'); -module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) { +function supplyDefaults(traceIn, traceOut, defaultColor, layout) { function coerce(attr, dflt) { return Lib.coerce(traceIn, traceOut, attributes, attr, dflt); } - var r = coerce('r'); - var theta = coerce('theta'); - var len = (r && theta) ? Math.min(r.length, theta.length) : 0; - + var len = handleRThetaDefaults(traceIn, traceOut, layout, coerce); if(!len) { traceOut.visible = false; return; } - traceOut._length = len; - coerce('thetaunit'); coerce('mode', len < PTS_LINESONLY ? 'lines+markers' : 'lines'); coerce('text'); @@ -76,4 +71,33 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout coerce('hoveron', dfltHoverOn.join('+') || 'points'); Lib.coerceSelectionMarkerOpacity(traceOut, coerce); +} + +function handleRThetaDefaults(traceIn, traceOut, layout, coerce) { + var r = coerce('r'); + var theta = coerce('theta'); + var len; + + if(r) { + if(theta) { + len = Math.min(r.length, theta.length); + } else { + len = r.length; + coerce('theta0'); + coerce('dtheta'); + } + } else { + if(!theta) return 0; + len = traceOut.theta.length; + coerce('r0'); + coerce('dr'); + } + + traceOut._length = len; + return len; +} + +module.exports = { + handleRThetaDefaults: handleRThetaDefaults, + supplyDefaults: supplyDefaults }; diff --git a/src/traces/scatterpolar/index.js b/src/traces/scatterpolar/index.js index ece30328990..8a3f55e609a 100644 --- a/src/traces/scatterpolar/index.js +++ b/src/traces/scatterpolar/index.js @@ -15,7 +15,7 @@ module.exports = { categories: ['polar', 'symbols', 'showLegend', 'scatter-like'], attributes: require('./attributes'), - supplyDefaults: require('./defaults'), + supplyDefaults: require('./defaults').supplyDefaults, colorbar: require('../scatter/marker_colorbar'), calc: require('./calc'), plot: require('./plot'), diff --git a/src/traces/scatterpolargl/attributes.js b/src/traces/scatterpolargl/attributes.js index 6f1ce8c6dc4..0e778aff071 100644 --- a/src/traces/scatterpolargl/attributes.js +++ b/src/traces/scatterpolargl/attributes.js @@ -15,6 +15,10 @@ module.exports = { mode: scatterPolarAttrs.mode, r: scatterPolarAttrs.r, theta: scatterPolarAttrs.theta, + r0: scatterPolarAttrs.r0, + dr: scatterPolarAttrs.dr, + theta0: scatterPolarAttrs.theta0, + dtheta: scatterPolarAttrs.dtheta, thetaunit: scatterPolarAttrs.thetaunit, text: scatterPolarAttrs.text, diff --git a/src/traces/scatterpolargl/defaults.js b/src/traces/scatterpolargl/defaults.js index 1c6a5cae2c3..101a29e0452 100644 --- a/src/traces/scatterpolargl/defaults.js +++ b/src/traces/scatterpolargl/defaults.js @@ -11,6 +11,7 @@ var Lib = require('../../lib'); var subTypes = require('../scatter/subtypes'); +var handleRThetaDefaults = require('../scatterpolar/defaults').handleRThetaDefaults; var handleMarkerDefaults = require('../scatter/marker_defaults'); var handleLineDefaults = require('../scatter/line_defaults'); var handleFillColorDefaults = require('../scatter/fillcolor_defaults'); @@ -23,17 +24,12 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout return Lib.coerce(traceIn, traceOut, attributes, attr, dflt); } - var r = coerce('r'); - var theta = coerce('theta'); - var len = (r && theta) ? Math.min(r.length, theta.length) : 0; - + var len = handleRThetaDefaults(traceIn, traceOut, layout, coerce); if(!len) { traceOut.visible = false; return; } - traceOut._length = len; - coerce('thetaunit'); coerce('mode', len < PTS_LINESONLY ? 'lines+markers' : 'lines'); coerce('text'); diff --git a/test/image/baselines/polar_r0dr-theta0dtheta.png b/test/image/baselines/polar_r0dr-theta0dtheta.png new file mode 100644 index 0000000000000000000000000000000000000000..4345d2092ee69ad4b4db04f73ddb76e7d58e0a14 GIT binary patch literal 71519 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/dθ", + "r": [1, 2, 3], + "theta0": 1, + "dtheta": 2, + "thetaunit": "radians" + }, + + { + "type": "scatterpolargl", + "name": "[gl] missing θ", + "subplot": "polar2", + "marker": {"color": "#1f77b4"}, + "r": [1, 2, 3] + }, { + "type": "scatterpolargl", + "name": "[gl] missing r", + "subplot": "polar2", + "marker": {"color": "#ff7f0e"}, + "theta": [0, -90, -220] + }, { + "type": "scatterpolargl", + "name": "[gl] set r0/dr", + "subplot": "polar2", + "marker": {"color": "#2ca02c"}, + "r0": 5, + "dr": -1, + "theta": [0, -90, -190] + }, { + "type": "scatterpolargl", + "name": "[gl] set θ0/dθ", + "subplot": "polar2", + "marker": {"color": "#d62728"}, + "r": [1, 2, 3], + "theta0": 1, + "dtheta": 2, + "thetaunit": "radians" + }], + "layout": { + "width": 500, + "height": 800, + "margin": {"l": 200, "t": 20, "b": 20}, + "legend": { + "x": -0.2, + "xanchor": "right", + "y": 0.5 + }, + "polar": { + "domain": { + "x": [0, 1], + "y": [0.5, 1] + } + }, + "polar2": { + "domain": { + "x": [0, 1], + "y": [0, 0.5] + } + } + } +} diff --git a/test/jasmine/tests/scatterpolar_test.js b/test/jasmine/tests/scatterpolar_test.js index 58477040103..a8ee1c0945e 100644 --- a/test/jasmine/tests/scatterpolar_test.js +++ b/test/jasmine/tests/scatterpolar_test.js @@ -28,6 +28,10 @@ describe('Test scatterpolar trace defaults:', function() { expect(traceOut.r).toEqual([1, 2, 3, 4, 5]); expect(traceOut.theta).toEqual([1, 2, 3]); expect(traceOut._length).toBe(3); + expect(traceOut.r0).toBeUndefined(); + expect(traceOut.dr).toBeUndefined(); + expect(traceOut.theta0).toBeUndefined(); + expect(traceOut.dtheta).toBeUndefined(); }); it('should not truncate *theta* when longer than *r*', function() { @@ -40,6 +44,39 @@ describe('Test scatterpolar trace defaults:', function() { expect(traceOut.r).toEqual([1, 2, 3]); expect(traceOut.theta).toEqual([1, 2, 3, 4, 5]); expect(traceOut._length).toBe(3); + expect(traceOut.r0).toBeUndefined(); + expect(traceOut.dr).toBeUndefined(); + expect(traceOut.theta0).toBeUndefined(); + expect(traceOut.dtheta).toBeUndefined(); + }); + + it('should coerce *theta0* and *dtheta* when *theta* is not set', function() { + _supply({ + r: [1, 2, 3] + }); + + expect(traceOut.r).toEqual([1, 2, 3]); + expect(traceOut.theta).toBeUndefined(); + expect(traceOut._length).toBe(3); + expect(traceOut.r0).toBeUndefined(); + expect(traceOut.dr).toBeUndefined(); + expect(traceOut.theta0).toBe(0); + // its default value is computed later + expect(traceOut.dtheta).toBeUndefined(); + }); + + it('should coerce *r0* and *dr* when *r* is not set', function() { + _supply({ + theta: [1, 2, 3, 4, 5] + }); + + expect(traceOut.r).toBeUndefined(); + expect(traceOut.theta).toEqual([1, 2, 3, 4, 5]); + expect(traceOut._length).toBe(5); + expect(traceOut.r0).toBe(0); + expect(traceOut.dr).toBe(1); + expect(traceOut.theta0).toBeUndefined(); + expect(traceOut.dtheta).toBeUndefined(); }); }); From 449dc4307fec03db0b0073cc8e8dafb0b079e3af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Fri, 10 Aug 2018 17:09:23 -0400 Subject: [PATCH 08/18] fix #2888 - clear scene via gl ref in line2d object --- src/plots/polar/polar.js | 2 + src/traces/scattergl/index.js | 2 + src/traces/scatterpolargl/index.js | 1 - test/jasmine/tests/polar_test.js | 86 ++++++++++++++++++++++++++++++ 4 files changed, 90 insertions(+), 1 deletion(-) diff --git a/src/plots/polar/polar.js b/src/plots/polar/polar.js index 396687e1a81..a6d3669cfc9 100644 --- a/src/plots/polar/polar.js +++ b/src/plots/polar/polar.js @@ -994,6 +994,7 @@ proto.updateRadialDrag = function(fullLayout, polarLayout) { var _module = moduleCalcData[0][0].trace._module; var polarLayoutNow = gd._fullLayout[_this.id]; + if(_this._scene) _this._scene.clear(); _module.plot(gd, _this, moduleCalcDataVisible, polarLayoutNow); if(!Registry.traceIs(k, 'gl')) { @@ -1136,6 +1137,7 @@ proto.updateAngularDrag = function(fullLayout, polarLayout) { var moduleCalcData = _this.traceHash[k]; var moduleCalcDataVisible = Lib.filterVisible(moduleCalcData); var _module = moduleCalcData[0][0].trace._module; + if(_this._scene) _this._scene.clear(); _module.plot(gd, _this, moduleCalcDataVisible, polarLayoutNow); } } diff --git a/src/traces/scattergl/index.js b/src/traces/scattergl/index.js index e4cf251a984..f3a6e2eae10 100644 --- a/src/traces/scattergl/index.js +++ b/src/traces/scattergl/index.js @@ -302,6 +302,8 @@ function sceneUpdate(gd, subplot) { } if(scene.scatter2d) { clearViewport(scene.scatter2d, vp); + } else if(scene.line2d) { + clearViewport(scene.line2d, vp); } else if(scene.glText) { clearViewport(scene.glText[0], vp); } diff --git a/src/traces/scatterpolargl/index.js b/src/traces/scatterpolargl/index.js index d6a24114b55..a575270545a 100644 --- a/src/traces/scatterpolargl/index.js +++ b/src/traces/scatterpolargl/index.js @@ -46,7 +46,6 @@ function plot(container, subplot, cdata) { var angularAxis = subplot.angularAxis; var scene = ScatterGl.sceneUpdate(container, subplot); - scene.clear(); cdata.forEach(function(cdscatter, traceIndex) { if(!cdscatter || !cdscatter[0] || !cdscatter[0].trace) return; diff --git a/test/jasmine/tests/polar_test.js b/test/jasmine/tests/polar_test.js index 60bc10c920b..2930aacb85b 100644 --- a/test/jasmine/tests/polar_test.js +++ b/test/jasmine/tests/polar_test.js @@ -1076,6 +1076,92 @@ describe('Test polar interactions:', function() { .catch(failTest) .then(done); }); + + describe('@gl should update scene during drag interactions on radial and angular drag area', function() { + var objs = ['scatter2d', 'line2d']; + var scene, gl, nTraces; + + function _dragRadial() { + var node = d3.select('.polar > .draglayer > .radialdrag').node(); + var p0 = [375, 200]; + var dp = [-50, 0]; + return drag(node, dp[0], dp[1], null, p0[0], p0[1], 2); + } + + function _dragAngular() { + var node = d3.select('.polar > .draglayer > .angulardrag').node(); + var p0 = [350, 150]; + var dp = [-20, 20]; + return drag(node, dp[0], dp[1], null, p0[0], p0[1]); + } + + // once on drag, once on mouseup relayout + function _assert() { + expect(gl.clear).toHaveBeenCalledTimes(2); + gl.clear.calls.reset(); + + objs.forEach(function(o) { + if(scene[o]) { + expect(scene[o].draw).toHaveBeenCalledTimes(2 * nTraces); + scene[o].draw.calls.reset(); + } + }); + } + + var specs = [{ + desc: 'scatter marker case', + // mode: 'markers' by default + }, { + desc: 'line case', + // start with lines to lock down fix for #2888 + patch: function(fig) { + fig.data.forEach(function(trace) { trace.mode = 'lines'; }); + } + }, { + desc: 'line & markers case', + patch: function(fig) { + fig.data.forEach(function(trace) { trace.mode = 'markers+lines'; }); + } + }]; + + specs.forEach(function(s) { + it('- ' + s.desc, function(done) { + var fig = Lib.extendDeep({}, require('@mocks/glpolar_scatter.json')); + scene = null; + gl = null; + + fig.layout.hovermode = false; + fig.layout.width = 400; + fig.layout.height = 400; + fig.layout.margin = {l: 50, t: 50, b: 50, r: 50}; + + if(s.patch) s.patch(fig); + nTraces = fig.data.length; + + Plotly.newPlot(gd, fig).then(function() { + scene = gd._fullLayout.polar._subplot._scene; + + objs.forEach(function(o) { + if(scene[o]) { + spyOn(scene[o], 'draw').and.callThrough(); + if(!gl) { + // all objects have the same _gl ref, + // spy on it just once + gl = scene[o].regl._gl; + spyOn(gl, 'clear').and.callThrough(); + } + } + }); + }) + .then(function() { return _dragRadial(); }) + .then(_assert) + .then(function() { return _dragAngular(); }) + .then(_assert) + .catch(failTest) + .then(done); + }); + }); + }); }); describe('Test polar *gridshape linear* interactions', function() { From dae3ccd53549a971c2e5b6fa83e4b033f601a89a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Fri, 10 Aug 2018 17:18:17 -0400 Subject: [PATCH 09/18] :hocho: hoveron declaration in scattergl and scatterpolargl ... until we actually implement it in https://github.com/plotly/plotly.js/issues/2887 --- src/traces/scattergl/attributes.js | 2 +- src/traces/scattergl/defaults.js | 9 --------- src/traces/scatterpolargl/attributes.js | 2 +- src/traces/scatterpolargl/defaults.js | 8 -------- 4 files changed, 2 insertions(+), 19 deletions(-) diff --git a/src/traces/scattergl/attributes.js b/src/traces/scattergl/attributes.js index a4d709d10c8..3d8798115bd 100644 --- a/src/traces/scattergl/attributes.js +++ b/src/traces/scattergl/attributes.js @@ -78,7 +78,7 @@ var attrs = module.exports = overrideAll({ fill: scatterAttrs.fill, fillcolor: scatterAttrs.fillcolor, - hoveron: scatterAttrs.hoveron, + // no hoveron selected: { marker: scatterAttrs.selected.marker, diff --git a/src/traces/scattergl/defaults.js b/src/traces/scattergl/defaults.js index 328c50e8709..2703cb31b20 100644 --- a/src/traces/scattergl/defaults.js +++ b/src/traces/scattergl/defaults.js @@ -44,12 +44,9 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout handleLineDefaults(traceIn, traceOut, defaultColor, layout, coerce); } - var dfltHoverOn = []; - if(subTypes.hasMarkers(traceOut)) { handleMarkerDefaults(traceIn, traceOut, defaultColor, layout, coerce); coerce('marker.line.width', isOpen || isBubble ? 1 : 0); - dfltHoverOn.push('points'); } if(subTypes.hasText(traceOut)) { @@ -61,12 +58,6 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout handleFillColorDefaults(traceIn, traceOut, defaultColor, coerce); } - if(traceOut.fill === 'tonext' || traceOut.fill === 'toself') { - dfltHoverOn.push('fills'); - } - - coerce('hoveron', dfltHoverOn.join('+') || 'points'); - var errorBarsSupplyDefaults = Registry.getComponentMethod('errorbars', 'supplyDefaults'); errorBarsSupplyDefaults(traceIn, traceOut, defaultColor, {axis: 'y'}); errorBarsSupplyDefaults(traceIn, traceOut, defaultColor, {axis: 'x', inherit: 'y'}); diff --git a/src/traces/scatterpolargl/attributes.js b/src/traces/scatterpolargl/attributes.js index 0e778aff071..00434b3781e 100644 --- a/src/traces/scatterpolargl/attributes.js +++ b/src/traces/scatterpolargl/attributes.js @@ -34,7 +34,7 @@ module.exports = { fillcolor: scatterGlAttrs.fillcolor, hoverinfo: scatterPolarAttrs.hoverinfo, - hoveron: scatterPolarAttrs.hoveron, + // no hoveron selected: scatterPolarAttrs.selected, unselected: scatterPolarAttrs.unselected diff --git a/src/traces/scatterpolargl/defaults.js b/src/traces/scatterpolargl/defaults.js index 101a29e0452..9e1f948d018 100644 --- a/src/traces/scatterpolargl/defaults.js +++ b/src/traces/scatterpolargl/defaults.js @@ -39,11 +39,8 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout coerce('connectgaps'); } - var dfltHoverOn = []; - if(subTypes.hasMarkers(traceOut)) { handleMarkerDefaults(traceIn, traceOut, defaultColor, layout, coerce); - dfltHoverOn.push('points'); } coerce('fill'); @@ -51,10 +48,5 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout handleFillColorDefaults(traceIn, traceOut, defaultColor, coerce); } - if(traceOut.fill === 'tonext' || traceOut.fill === 'toself') { - dfltHoverOn.push('fills'); - } - coerce('hoveron', dfltHoverOn.join('+') || 'points'); - Lib.coerceSelectionMarkerOpacity(traceOut, coerce); }; From d3b36151d02219bf122fc7cd813b544ccdd6ac2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Fri, 10 Aug 2018 17:36:37 -0400 Subject: [PATCH 10/18] add on-graph 'text' to scatterpolargl - follow-up from https://github.com/plotly/plotly.js/pull/2737 --- src/traces/scatterpolargl/attributes.js | 5 +- src/traces/scatterpolargl/defaults.js | 6 ++ src/traces/scatterpolargl/index.js | 4 ++ test/image/baselines/glpolar_subplots.png | Bin 0 -> 47514 bytes test/image/mocks/glpolar_subplots.json | 68 ++++++++++++++++++++++ 5 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 test/image/baselines/glpolar_subplots.png create mode 100644 test/image/mocks/glpolar_subplots.json diff --git a/src/traces/scatterpolargl/attributes.js b/src/traces/scatterpolargl/attributes.js index 00434b3781e..fbcbaa1a415 100644 --- a/src/traces/scatterpolargl/attributes.js +++ b/src/traces/scatterpolargl/attributes.js @@ -22,7 +22,7 @@ module.exports = { thetaunit: scatterPolarAttrs.thetaunit, text: scatterPolarAttrs.text, - // no hovertext + hovertext: scatterPolarAttrs.hovertext, line: scatterGlAttrs.line, connectgaps: scatterGlAttrs.connectgaps, @@ -33,6 +33,9 @@ module.exports = { fill: scatterGlAttrs.fill, fillcolor: scatterGlAttrs.fillcolor, + textposition: scatterGlAttrs.textposition, + textfont: scatterGlAttrs.textfont, + hoverinfo: scatterPolarAttrs.hoverinfo, // no hoveron diff --git a/src/traces/scatterpolargl/defaults.js b/src/traces/scatterpolargl/defaults.js index 9e1f948d018..70efac6da99 100644 --- a/src/traces/scatterpolargl/defaults.js +++ b/src/traces/scatterpolargl/defaults.js @@ -14,6 +14,7 @@ var subTypes = require('../scatter/subtypes'); var handleRThetaDefaults = require('../scatterpolar/defaults').handleRThetaDefaults; var handleMarkerDefaults = require('../scatter/marker_defaults'); var handleLineDefaults = require('../scatter/line_defaults'); +var handleTextDefaults = require('../scatter/text_defaults'); var handleFillColorDefaults = require('../scatter/fillcolor_defaults'); var PTS_LINESONLY = require('../scatter/constants').PTS_LINESONLY; @@ -33,6 +34,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout coerce('thetaunit'); coerce('mode', len < PTS_LINESONLY ? 'lines+markers' : 'lines'); coerce('text'); + coerce('hovertext'); if(subTypes.hasLines(traceOut)) { handleLineDefaults(traceIn, traceOut, defaultColor, layout, coerce); @@ -43,6 +45,10 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout handleMarkerDefaults(traceIn, traceOut, defaultColor, layout, coerce); } + if(subTypes.hasText(traceOut)) { + handleTextDefaults(traceIn, traceOut, layout, coerce); + } + coerce('fill'); if(traceOut.fill !== 'none') { handleFillColorDefaults(traceIn, traceOut, defaultColor, coerce); diff --git a/src/traces/scatterpolargl/index.js b/src/traces/scatterpolargl/index.js index a575270545a..b2e6be07249 100644 --- a/src/traces/scatterpolargl/index.js +++ b/src/traces/scatterpolargl/index.js @@ -95,6 +95,7 @@ function plot(container, subplot, cdata) { if(options.marker && !scene.scatter2d) scene.scatter2d = true; if(options.line && !scene.line2d) scene.line2d = true; if((options.errorX || options.errorY) && !scene.error2d) scene.error2d = true; + if(options.text && !scene.glText) scene.glText = true; stash.tree = cluster(positions); @@ -116,6 +117,9 @@ function plot(container, subplot, cdata) { scene.markerOptions.push(options.marker); scene.markerSelectedOptions.push(options.markerSel); scene.markerUnselectedOptions.push(options.markerUnsel); + scene.textOptions.push(options.text); + scene.textSelectedOptions.push(options.textSel); + scene.textUnselectedOptions.push(options.textUnsel); scene.count = cdata.length; // stash scene ref diff --git a/test/image/baselines/glpolar_subplots.png b/test/image/baselines/glpolar_subplots.png new file mode 100644 index 0000000000000000000000000000000000000000..ed807c1ab1559cb4c6307fa5046989c3df14e564 GIT binary patch literal 47514 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�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 literal 0 HcmV?d00001 diff --git a/test/image/mocks/glpolar_subplots.json b/test/image/mocks/glpolar_subplots.json new file mode 100644 index 00000000000..7b689f4786b --- /dev/null +++ b/test/image/mocks/glpolar_subplots.json @@ -0,0 +1,68 @@ +{ + "data": [{ + "type": "scatterpolargl", + "mode": "markers+lines+text", + "r": [1, 2, 3], + "theta": [0, 45, 180], + "text": ["A0", "B0", "C0"], + "hovertext": ["hover A0", "hover B0", "hover C0"], + "marker": {"symbol": "square", "size": 15}, + "textfont": { + "color": ["red", "green", "blue"], + "size": 20 + }, + "textposition": ["top left", "bottom right", "bottom right"] + }, { + "type": "scatterpolargl", + "mode": "markers+lines+text", + "r": [1, 2, 3], + "theta": [-0, -45, -180], + "text": ["A1", "B1", "C1"], + "hovertext": ["hover A1", "hover B1", "hover C1"], + "marker": { + "symbol": "square", + "size": 15, + "color": [-1, 1, 2], + "showscale": true + }, + "textfont": { + "color": "green", + "size": [20, 15, 10] + }, + "textposition": "top left", + "subplot": "polar2" + }], + + "layout": { + "showlegend": false, + "polar": { + "domain": { + "x": [0, 0.44] + }, + "radialaxis": { + "showgrid": false, + "title": "1st subplot", + "titlefont": { + "size": 20, + "color": "blue" + }, + "angle": -45 + } + }, + "polar2": { + "domain": { + "x": [0.56, 1] + }, + "radialaxis": { + "range": [0, 4.7], + "showgrid": false, + "title": "2nd subplot", + "titlefont": { + "size": 10, + "color": "orange" + }, + "angle": 45 + } + } + } +} From ec57a4283b70925636d9c28dbe8cced9fc7e9723 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Mon, 13 Aug 2018 11:31:27 -0400 Subject: [PATCH 11/18] fix radial drag when scatterpolar & scatterpolargl are on same subplot --- src/plots/polar/polar.js | 6 ++++-- test/jasmine/tests/polar_test.js | 11 ++++++++++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/plots/polar/polar.js b/src/plots/polar/polar.js index a6d3669cfc9..1ee329ff626 100644 --- a/src/plots/polar/polar.js +++ b/src/plots/polar/polar.js @@ -993,11 +993,13 @@ proto.updateRadialDrag = function(fullLayout, polarLayout) { var moduleCalcDataVisible = Lib.filterVisible(moduleCalcData); var _module = moduleCalcData[0][0].trace._module; var polarLayoutNow = gd._fullLayout[_this.id]; + var isGL = Registry.traceIs(k, 'gl'); + + if(isGL && _this._scene) _this._scene.clear(); - if(_this._scene) _this._scene.clear(); _module.plot(gd, _this, moduleCalcDataVisible, polarLayoutNow); - if(!Registry.traceIs(k, 'gl')) { + if(!isGL) { for(var i = 0; i < moduleCalcDataVisible.length; i++) { _module.style(gd, moduleCalcDataVisible[i]); } diff --git a/test/jasmine/tests/polar_test.js b/test/jasmine/tests/polar_test.js index 2930aacb85b..b4d89123482 100644 --- a/test/jasmine/tests/polar_test.js +++ b/test/jasmine/tests/polar_test.js @@ -1122,6 +1122,13 @@ describe('Test polar interactions:', function() { patch: function(fig) { fig.data.forEach(function(trace) { trace.mode = 'markers+lines'; }); } + }, { + desc: 'gl and non-gl on same subplot case', + patch: function(fig) { + fig.data.forEach(function(trace, i) { + trace.type = (i % 2) ? 'scatterpolar' : 'scatterpolargl'; + }); + } }]; specs.forEach(function(s) { @@ -1136,7 +1143,9 @@ describe('Test polar interactions:', function() { fig.layout.margin = {l: 50, t: 50, b: 50, r: 50}; if(s.patch) s.patch(fig); - nTraces = fig.data.length; + nTraces = fig.data.reduce(function(acc, trace) { + return (trace.type === 'scatterpolargl') ? ++acc : acc; + }, 0); Plotly.newPlot(gd, fig).then(function() { scene = gd._fullLayout.polar._subplot._scene; From f5bceda77844160879033d283f2a4cf914924a7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Mon, 13 Aug 2018 12:08:48 -0400 Subject: [PATCH 12/18] :hocho: unused keys from angular axis objects - ?2rad and rad2? methods are now gone - ax._period is gone --- src/plots/polar/set_convert.js | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/src/plots/polar/set_convert.js b/src/plots/polar/set_convert.js index 314ed07ea1f..98e025006a7 100644 --- a/src/plots/polar/set_convert.js +++ b/src/plots/polar/set_convert.js @@ -143,37 +143,40 @@ function setConvertAngular(ax, polarLayout) { var dir = {clockwise: -1, counterclockwise: 1}[ax.direction]; var rot = deg2rad(ax.rotation); - ax.rad2g = function(v) { return dir * v + rot; }; - ax.g2rad = function(v) { return (v - rot) / dir; }; + var rad2g = function(v) { return dir * v + rot; }; + var g2rad = function(v) { return (v - rot) / dir; }; + + var rad2c, c2rad; + var rad2t, t2rad; switch(axType) { case 'linear': - ax.c2rad = ax.rad2c = Lib.identity; - ax.t2rad = deg2rad; - ax.rad2r = rad2deg; + c2rad = rad2c = Lib.identity; + t2rad = deg2rad; + rad2t = rad2deg; // Set the angular range in degrees to make auto-tick computation cleaner, // changing rotation/direction should not affect the angular tick value. ax.range = isFullCircle(sector) ? sector.slice() : - sector.map(deg2rad).map(ax.g2rad).map(rad2deg); + sector.map(deg2rad).map(g2rad).map(rad2deg); break; case 'category': var catLen = ax._categories.length; - var _period = ax._period = ax.period ? Math.max(ax.period, catLen) : catLen; + var _period = ax.period ? Math.max(ax.period, catLen) : catLen; - ax.c2rad = ax.t2rad = function(v) { return v * 2 * Math.PI / _period; }; - ax.rad2c = ax.rad2t = function(v) { return v * _period / Math.PI / 2; }; + c2rad = t2rad = function(v) { return v * 2 * Math.PI / _period; }; + rad2c = rad2t = function(v) { return v * _period / Math.PI / 2; }; ax.range = [0, _period]; break; } - ax.c2g = function(v) { return ax.rad2g(ax.c2rad(v)); }; - ax.g2c = function(v) { return ax.rad2c(ax.g2rad(v)); }; + ax.c2g = function(v) { return rad2g(c2rad(v)); }; + ax.g2c = function(v) { return rad2c(g2rad(v)); }; - ax.t2g = function(v) { return ax.rad2g(ax.t2rad(v)); }; - ax.g2t = function(v) { return ax.rad2t(ax.g2rad(v)); }; + ax.t2g = function(v) { return rad2g(t2rad(v)); }; + ax.g2t = function(v) { return rad2t(g2rad(v)); }; }; } From 0c517d197dce032bcd5835adf80952672c2341fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Mon, 13 Aug 2018 16:37:49 -0400 Subject: [PATCH 13/18] use isAngular helper in axes.js --- src/plots/cartesian/axes.js | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/plots/cartesian/axes.js b/src/plots/cartesian/axes.js index a32da7bcfc2..39104b1dbad 100644 --- a/src/plots/cartesian/axes.js +++ b/src/plots/cartesian/axes.js @@ -554,7 +554,7 @@ axes.calcTicks = function calcTicks(ax) { // If same angle over a full circle, the last tick vals is a duplicate. // TODO must do something similar for angular date axes. - if(ax._id === 'angularaxis' && Math.abs(rng[1] - rng[0]) === 360) { + if(isAngular(ax) && Math.abs(rng[1] - rng[0]) === 360) { vals.pop(); } @@ -722,7 +722,7 @@ axes.autoTicks = function(ax, roughDTick) { ax.tick0 = 0; ax.dtick = Math.ceil(Math.max(roughDTick, 1)); } - else if(ax._id === 'angularaxis') { + else if(isAngular(ax)) { ax.tick0 = 0; base = 1; ax.dtick = roundDTick(roughDTick, base, roundAngles); @@ -958,7 +958,7 @@ axes.tickText = function(ax, x, hover) { if(ax.type === 'date') formatDate(ax, out, hover, extraPrecision); else if(ax.type === 'log') formatLog(ax, out, hover, extraPrecision, hideexp); else if(ax.type === 'category') formatCategory(ax, out); - else if(ax._id === 'angularaxis') formatAngle(ax, out, hover, extraPrecision, hideexp); + else if(isAngular(ax)) formatAngle(ax, out, hover, extraPrecision, hideexp); else formatLinear(ax, out, hover, extraPrecision, hideexp); // add prefix and suffix @@ -1646,7 +1646,7 @@ axes.doTicksSingle = function(gd, arg, skipTitle) { else return 'M' + shift + ',0h' + len; }; } - else if(axid === 'angularaxis') { + else if(isAngular(ax)) { sides = ['left', 'right']; transfn = ax._transfn; tickpathfn = function(shift, len) { @@ -1682,7 +1682,7 @@ axes.doTicksSingle = function(gd, arg, skipTitle) { var valsClipped = vals.filter(clipEnds); // don't clip angular values - if(ax._id === 'angularaxis') { + if(isAngular(ax)) { valsClipped = vals; } @@ -1751,7 +1751,7 @@ axes.doTicksSingle = function(gd, arg, skipTitle) { return axside === 'right' ? 'start' : 'end'; }; } - else if(axid === 'angularaxis') { + else if(isAngular(ax)) { ax._labelShift = labelShift; ax._labelStandoff = labelStandoff; ax._pad = pad; @@ -1798,7 +1798,7 @@ axes.doTicksSingle = function(gd, arg, skipTitle) { maxFontSize = Math.max(maxFontSize, d.fontSize); }); - if(axid === 'angularaxis') { + if(isAngular(ax)) { tickLabels.each(function(d) { d3.select(this).select('text') .call(svgTextUtils.positionText, labelx(d), labely(d)); @@ -2425,3 +2425,7 @@ function swapAxisAttrs(layout, key, xFullAxes, yFullAxes, dfltTitle) { np(layout, yFullAxes[i]._name + '.' + key).set(xVal); } } + +function isAngular(ax) { + return ax._id === 'angularaxis'; +} From 4c11c84251cfe8aa532f42a26022803e867f4ed4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Mon, 13 Aug 2018 16:57:57 -0400 Subject: [PATCH 14/18] improve polar setConvert docstring --- src/plots/polar/set_convert.js | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/src/plots/polar/set_convert.js b/src/plots/polar/set_convert.js index 98e025006a7..d7b12f5d679 100644 --- a/src/plots/polar/set_convert.js +++ b/src/plots/polar/set_convert.js @@ -33,19 +33,17 @@ var isFullCircle = Lib.isFullCircle; * - 'g' for geometric coordinates and * - 't' for angular ticks * - * Radial axis coordinate systems flow: - * - d2c (in calc) just like for cartesian axes - * - c2g (in plot) translates calcdata about `radialaxis.range[0]` + * Radial axis coordinate systems: + * - d, c and l: same as for cartesian axes + * - g: like calcdata but translated about `radialaxis.range[0]` * - * Angular axis coordinate systems flow: - * + for linear axes: - * - d2c (in calc) angles -> 'data' radians - * - c2g (in plot) 'data' -> 'geometric' radians (fn of ax rotation & direction) - * - t2g (in updateAngularAxis) 'tick' value (in degrees) -> 'geometric' radians - * + for category axes: - * - d2c (in calc) just like for cartesian axes - * - c2g (in plot) category indices -> 'geometric' radians - * - t2g (in updateAngularAxis) 'tick' value (as category indices) -> 'geometric' radians + * Angular axis coordinate systems: + * - d: data, in whatever form it's provided + * - c: calcdata, turned into radians (for linear axes) + * or category indices (category axes) + * - t: tick calcdata, just like 'c' but in degrees for linear axes + * - g: geometric calcdata, radians coordinates that take into account + * axis rotation and direction * * Then, 'g'eometric data is ready to be converted to (x,y). */ From b4700cabda7752b3d21f1277a37dd912664a2805 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Mon, 13 Aug 2018 16:58:24 -0400 Subject: [PATCH 15/18] use .filter instead of (confusing) .reduce --- test/jasmine/tests/polar_test.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/jasmine/tests/polar_test.js b/test/jasmine/tests/polar_test.js index b4d89123482..ee88b681172 100644 --- a/test/jasmine/tests/polar_test.js +++ b/test/jasmine/tests/polar_test.js @@ -1143,9 +1143,9 @@ describe('Test polar interactions:', function() { fig.layout.margin = {l: 50, t: 50, b: 50, r: 50}; if(s.patch) s.patch(fig); - nTraces = fig.data.reduce(function(acc, trace) { - return (trace.type === 'scatterpolargl') ? ++acc : acc; - }, 0); + nTraces = fig.data + .filter(function(trace) { return trace.type === 'scatterpolargl'; }) + .length; Plotly.newPlot(gd, fig).then(function() { scene = gd._fullLayout.polar._subplot._scene; From c3044e094eacfc25fbd2f85648c66c4bf8693994 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Mon, 13 Aug 2018 17:02:52 -0400 Subject: [PATCH 16/18] clean up cleanViewport logic --- src/traces/scattergl/index.js | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/traces/scattergl/index.js b/src/traces/scattergl/index.js index f3a6e2eae10..8bf2661b6a5 100644 --- a/src/traces/scattergl/index.js +++ b/src/traces/scattergl/index.js @@ -300,13 +300,9 @@ function sceneUpdate(gd, subplot) { if(scene.select2d) { clearViewport(scene.select2d, vp); } - if(scene.scatter2d) { - clearViewport(scene.scatter2d, vp); - } else if(scene.line2d) { - clearViewport(scene.line2d, vp); - } else if(scene.glText) { - clearViewport(scene.glText[0], vp); - } + + var anyComponent = scene.scatter2d || scene.line2d || (scene.glText || [])[0]; + if(anyComponent) clearViewport(anyComponent, vp); }; // remove scene resources From b81885d7315b1d16e609e7d1ca7af3311ea3dbaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Mon, 13 Aug 2018 17:09:55 -0400 Subject: [PATCH 17/18] avoid trying to call scene.clear more than once per mousemove ... that is move scene.clear() out of trace-type loop --- src/plots/polar/polar.js | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/plots/polar/polar.js b/src/plots/polar/polar.js index 1ee329ff626..500659f55db 100644 --- a/src/plots/polar/polar.js +++ b/src/plots/polar/polar.js @@ -988,18 +988,17 @@ proto.updateRadialDrag = function(fullLayout, polarLayout) { _this.xaxis.setScale(); _this.yaxis.setScale(); - for(var k in _this.traceHash) { - var moduleCalcData = _this.traceHash[k]; + if(_this._scene) _this._scene.clear(); + + for(var traceType in _this.traceHash) { + var moduleCalcData = _this.traceHash[traceType]; var moduleCalcDataVisible = Lib.filterVisible(moduleCalcData); var _module = moduleCalcData[0][0].trace._module; var polarLayoutNow = gd._fullLayout[_this.id]; - var isGL = Registry.traceIs(k, 'gl'); - - if(isGL && _this._scene) _this._scene.clear(); _module.plot(gd, _this, moduleCalcDataVisible, polarLayoutNow); - if(!isGL) { + if(!Registry.traceIs(traceType, 'gl')) { for(var i = 0; i < moduleCalcDataVisible.length; i++) { _module.style(gd, moduleCalcDataVisible[i]); } @@ -1134,12 +1133,13 @@ proto.updateAngularDrag = function(fullLayout, polarLayout) { scatterTraces.call(Drawing.hideOutsideRangePoints, _this); } - for(var k in _this.traceHash) { - if(Registry.traceIs(k, 'gl')) { - var moduleCalcData = _this.traceHash[k]; + if(_this._scene) _this._scene.clear(); + + for(var traceType in _this.traceHash) { + if(Registry.traceIs(traceType, 'gl')) { + var moduleCalcData = _this.traceHash[traceType]; var moduleCalcDataVisible = Lib.filterVisible(moduleCalcData); var _module = moduleCalcData[0][0].trace._module; - if(_this._scene) _this._scene.clear(); _module.plot(gd, _this, moduleCalcDataVisible, polarLayoutNow); } } From 75786e40279e1cfbbc04333be7fb0d15d2f3be39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Tue, 14 Aug 2018 12:49:11 -0400 Subject: [PATCH 18/18] add fill2d and error2d in clearViewport fallback list --- src/traces/scattergl/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/traces/scattergl/index.js b/src/traces/scattergl/index.js index 8bf2661b6a5..f96b1458fd5 100644 --- a/src/traces/scattergl/index.js +++ b/src/traces/scattergl/index.js @@ -301,7 +301,8 @@ function sceneUpdate(gd, subplot) { clearViewport(scene.select2d, vp); } - var anyComponent = scene.scatter2d || scene.line2d || (scene.glText || [])[0]; + var anyComponent = scene.scatter2d || scene.line2d || + (scene.glText || [])[0] || scene.fill2d || scene.error2d; if(anyComponent) clearViewport(anyComponent, vp); };