From 811c874b53c0a773b1689813b5d06b7866aa2964 Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Sat, 12 Mar 2016 00:32:03 +0100 Subject: [PATCH 01/76] condense axisid, sceneid, geoid to subplotid --- src/lib/coerce.js | 52 ++++++----------------------- src/plots/cartesian/attributes.js | 4 +-- src/plots/geo/layout/attributes.js | 2 +- src/plots/gl3d/layout/attributes.js | 2 +- test/jasmine/tests/lib_test.js | 40 ++++++++++++++++++++++ 5 files changed, 54 insertions(+), 46 deletions(-) diff --git a/src/lib/coerce.js b/src/lib/coerce.js index 44d2e8c49e9..d5489f8c219 100644 --- a/src/lib/coerce.js +++ b/src/lib/coerce.js @@ -16,6 +16,7 @@ var nestedProperty = require('./nested_property'); var getColorscale = require('../components/colorscale/get_scale'); var colorscaleNames = Object.keys(require('../components/colorscale/scales')); +var idRegex = /^([2-9]|[1-9][0-9]+)$/ exports.valObjects = { data_array: { @@ -158,53 +159,20 @@ exports.valObjects = { } } }, - axisid: { + subplotid: { description: [ - 'An axis id string (e.g. \'x\', \'x2\', \'x3\', ...).' + 'An id string of a subplot type (given by dflt), optionally', + 'followed by an integer >1. e.g. if dflt=\'geo\', we can have', + '\'geo\', \'geo2\', \'geo3\', ...' ].join(' '), requiredOpts: [], otherOpts: ['dflt'], coerceFunction: function(v, propOut, dflt) { - if(typeof v === 'string' && v.charAt(0)===dflt) { - var axnum = Number(v.substr(1)); - if(axnum%1 === 0 && axnum>1) { - propOut.set(v); - return; - } - } - propOut.set(dflt); - } - }, - sceneid: { - description: [ - 'A scene id string (e.g. \'scene\', \'scene2\', \'scene3\', ...).' - ].join(' '), - requiredOpts: [], - otherOpts: ['dflt'], - coerceFunction: function(v, propOut, dflt) { - if(typeof v === 'string' && v.substr(0,5)===dflt) { - var scenenum = Number(v.substr(5)); - if(scenenum%1 === 0 && scenenum>1) { - propOut.set(v); - return; - } - } - propOut.set(dflt); - } - }, - geoid: { - description: [ - 'A geo id string (e.g. \'geo\', \'geo2\', \'geo3\', ...).' - ].join(' '), - requiredOpts: [], - otherOpts: ['dflt'], - coerceFunction: function(v, propOut, dflt) { - if(typeof v === 'string' && v.substr(0,3)===dflt) { - var geonum = Number(v.substr(3)); - if(geonum%1 === 0 && geonum>1) { - propOut.set(v); - return; - } + var dlen = dflt.length; + if(typeof v === 'string' && v.substr(0, dlen) === dflt && + idRegex.test(v.substr(dlen))) { + propOut.set(v); + return; } propOut.set(dflt); } diff --git a/src/plots/cartesian/attributes.js b/src/plots/cartesian/attributes.js index 56d6cca9450..7e44198831d 100644 --- a/src/plots/cartesian/attributes.js +++ b/src/plots/cartesian/attributes.js @@ -11,7 +11,7 @@ module.exports = { xaxis: { - valType: 'axisid', + valType: 'subplotid', role: 'info', dflt: 'x', description: [ @@ -23,7 +23,7 @@ module.exports = { ].join(' ') }, yaxis: { - valType: 'axisid', + valType: 'subplotid', role: 'info', dflt: 'y', description: [ diff --git a/src/plots/geo/layout/attributes.js b/src/plots/geo/layout/attributes.js index e6f6823d5e2..a81b27bac1b 100644 --- a/src/plots/geo/layout/attributes.js +++ b/src/plots/geo/layout/attributes.js @@ -11,7 +11,7 @@ module.exports = { geo: { - valType: 'geoid', + valType: 'subplotid', role: 'info', dflt: 'geo', description: [ diff --git a/src/plots/gl3d/layout/attributes.js b/src/plots/gl3d/layout/attributes.js index 98e1719b930..0b1937947ca 100644 --- a/src/plots/gl3d/layout/attributes.js +++ b/src/plots/gl3d/layout/attributes.js @@ -11,7 +11,7 @@ module.exports = { scene: { - valType: 'sceneid', + valType: 'subplotid', role: 'info', dflt: 'scene', description: [ diff --git a/test/jasmine/tests/lib_test.js b/test/jasmine/tests/lib_test.js index fd1a2f5c28b..96c4cadc91f 100644 --- a/test/jasmine/tests/lib_test.js +++ b/test/jasmine/tests/lib_test.js @@ -588,6 +588,46 @@ describe('Test lib.js:', function() { }); + + describe('subplotid valtype', function() { + var dflt = 'slice'; + var idAttrs = { + pizza: { + valType: 'subplotid', + dflt: dflt + } + }; + + var goodVals = ['slice', 'slice2', 'slice1492']; + + goodVals.forEach(function(goodVal) { + it('should allow "' + goodVal + '"', function() { + expect(coerce({pizza: goodVal}, {}, idAttrs, 'pizza')) + .toEqual(goodVal); + }); + }); + + var badVals = [ + 'slice0', + 'slice1', + 'Slice2', + '2slice', + '2', + 2, + 'slice2 ', + 'slice2.0', + ' slice2', + 'slice 2', + 'slice01' + ]; + + badVals.forEach(function(badVal) { + it('should not allow "' + badVal + '"', function() { + expect(coerce({pizza: badVal}, {}, idAttrs, 'pizza')) + .toEqual(dflt); + }); + }); + }); }); describe('coerceFont', function() { From 2595731c4811c69ec906e5d248f55447cb3c28b5 Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Sun, 13 Mar 2016 01:04:22 +0100 Subject: [PATCH 02/76] first cut ternary layout attributes --- src/plots/ternary/layout/attributes.js | 24 +++++++ src/plots/ternary/layout/axis_attributes.js | 58 +++++++++++++++++ src/plots/ternary/layout/layout_attributes.js | 63 +++++++++++++++++++ 3 files changed, 145 insertions(+) create mode 100644 src/plots/ternary/layout/attributes.js create mode 100644 src/plots/ternary/layout/axis_attributes.js create mode 100644 src/plots/ternary/layout/layout_attributes.js diff --git a/src/plots/ternary/layout/attributes.js b/src/plots/ternary/layout/attributes.js new file mode 100644 index 00000000000..891f39df7ae --- /dev/null +++ b/src/plots/ternary/layout/attributes.js @@ -0,0 +1,24 @@ +/** +* 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'; + + +module.exports = { + subplot: { + valType: 'subplotid', + role: 'info', + dflt: 'ternary', + description: [ + 'Sets a reference between this trace\'s data coordinates and', + 'a ternary subplot.', + 'If *ternary* (the default value), the data refer to `layout.ternary`.', + 'If *ternary2*, the data refer to `layout.ternary2`, and so on.' + ].join(' ') + } +}; diff --git a/src/plots/ternary/layout/axis_attributes.js b/src/plots/ternary/layout/axis_attributes.js new file mode 100644 index 00000000000..eb049affd74 --- /dev/null +++ b/src/plots/ternary/layout/axis_attributes.js @@ -0,0 +1,58 @@ +/** +* 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 axesAttrs = require('../../cartesian/layout_attributes'); +var colorAttrs = require('../../components/color/attributes'); +var extendFlat = require('../../../lib/extend').extendFlat; + + +module.exports = { + title: axesAttrs.title, + titlefont: axesAttrs.titlefont, + color: { // TODO: support this for all other kinds of axes as well + valType: 'color', + dflt: colorAttrs.defaultLine, + role: 'style', + description: [ + 'Sets line, font, tick, and grid color for this axis', + 'all at once. Individual pieces can override this.' + ].join(' ') + }, + // ticks + nticks: axesAttrs.nticks, + ticks: axesAttrs.ticks, + ticklen: axesAttrs.ticklen, + tickwidth: axesAttrs.tickwidth, + tickcolor: axesAttrs.tickcolor, + showticklabels: axesAttrs.showticklabels, + tickfont: axesAttrs.tickfont, + tickangle: axesAttrs.tickangle, + // lines and grids + showline: extendFlat({}, axesAttrs.showline, {dflt: true}), + linecolor: axesAttrs.linecolor, + linewidth: axesAttrs.linewidth, + showgrid: axesAttrs.showgrid, + gridcolor: axesAttrs.gridcolor, + gridwidth: axesAttrs.gridwidth, + // range + min: { + valType: 'number', + dflt: 0, + role: 'info', + min: 0, + description: [ + 'The minimum value visible on this axis.', + 'The maximum is determined by the sum minus the minimum', + 'values of the other two axes. The full view corresponds to', + 'all the minima set to zero.' + ].join(' ') + } +}; diff --git a/src/plots/ternary/layout/layout_attributes.js b/src/plots/ternary/layout/layout_attributes.js new file mode 100644 index 00000000000..bc304055430 --- /dev/null +++ b/src/plots/ternary/layout/layout_attributes.js @@ -0,0 +1,63 @@ +/** +* 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 colorAttrs = require('../../../components/color/attributes'); +var ternaryAxesAttrs = require('./axis_attributes'); + + +module.exports = { + domain: { + x: { + valType: 'info_array', + role: 'info', + items: [ + {valType: 'number', min: 0, max: 1}, + {valType: 'number', min: 0, max: 1} + ], + dflt: [0, 1], + description: [ + 'Sets the horizontal domain of this subplot', + '(in plot fraction).' + ].join(' ') + }, + y: { + valType: 'info_array', + role: 'info', + items: [ + {valType: 'number', min: 0, max: 1}, + {valType: 'number', min: 0, max: 1} + ], + dflt: [0, 1], + description: [ + 'Sets the vertical domain of this subplot', + '(in plot fraction).' + ].join(' ') + } + }, + bgcolor: { + valType: 'color', + role: 'style', + dflt: colorAttrs.background, + description: 'Set the background color of the subplot' + }, + sum: { + valType: 'number', + role: 'info', + dflt: 1, + min: 0, + description: [ + 'The number each triplet should sum to,', + 'and the maximum range of each axis' + ].join(' ') + }, + aaxis: ternaryAxesAttrs, + baxis: ternaryAxesAttrs, + caxis: ternaryAxesAttrs +}; From 7e7e217d1ac7fd9d6d2a943a781f133adbac5f58 Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Sun, 13 Mar 2016 22:18:57 +0100 Subject: [PATCH 03/76] scatterternary attributes first cut --- src/traces/scattergeo/attributes.js | 2 +- src/traces/scatterternary/attributes.js | 121 ++++++++++++++++++++++++ 2 files changed, 122 insertions(+), 1 deletion(-) create mode 100644 src/traces/scatterternary/attributes.js diff --git a/src/traces/scattergeo/attributes.js b/src/traces/scattergeo/attributes.js index 724cd12bc8d..969c7a16970 100644 --- a/src/traces/scattergeo/attributes.js +++ b/src/traces/scattergeo/attributes.js @@ -47,7 +47,7 @@ module.exports = { mode: extendFlat({}, scatterAttrs.mode, {dflt: 'markers'}), text: extendFlat({}, scatterAttrs.text, { description: [ - 'Sets text elements associated with each (lon,lat) pair.', + 'Sets text elements associated with each (lon,lat) pair', 'or item in `locations`.', 'If a single string, the same string appears over', 'all the data points.', diff --git a/src/traces/scatterternary/attributes.js b/src/traces/scatterternary/attributes.js new file mode 100644 index 00000000000..490f5d49daa --- /dev/null +++ b/src/traces/scatterternary/attributes.js @@ -0,0 +1,121 @@ +/** +* 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 scatterAttrs = require('../scatter/attributes'); +var plotAttrs = require('../../plots/attributes'); +var extendFlat = require('../../lib/extend').extendFlat; + +var scatterMarkerAttrs = scatterAttrs.marker, + scatterLineAttrs = scatterAttrs.line, + scatterMarkerLineAttrs = scatterMarkerAttrs.line; + + +module.exports = { + a: { + valType: 'data_array', + description: [ + 'Sets the quantity of component `a` in each data point.', + 'If `a`, `b`, and `c` are all provided, they need not be', + 'normalized, only the relative values matter. If only two', + 'arrays are provided they must be normalized to match', + '`ternary.sum`.' + ].join(' ') + }, + b: { + valType: 'data_array', + description: [ + 'Sets the quantity of component `a` in each data point.', + 'If `a`, `b`, and `c` are all provided, they need not be', + 'normalized, only the relative values matter. If only two', + 'arrays are provided they must be normalized to match', + '`ternary(i).sum`.' + ].join(' ') + }, + c: { + valType: 'data_array', + description: [ + 'Sets the quantity of component `a` in each data point.', + 'If `a`, `b`, and `c` are all provided, they need not be', + 'normalized, only the relative values matter. If only two', + 'arrays are provided they must be normalized to match', + '`ternary.sum`.' + ].join(' ') + }, + sum: { + valType: 'number', + role: 'info', + dflt: 1, + min: 0, + description: [ + 'The number each triplet should sum to,', + 'if only two of `a`, `b`, and `c` are provided.', + 'This overrides `ternary.sum` to normalize this specific', + 'trace, but does not affect the values displayed on the axes.' + ].join(' ') + }, + mode: extendFlat({}, scatterAttrs.mode, {dflt: 'markers'}), + text: extendFlat({}, scatterAttrs.text, { + description: [ + 'Sets text elements associated with each (a,b,c) point.', + 'If a single string, the same string appears over', + 'all the data points.', + 'If an array of string, the items are mapped in order to the', + 'this trace\'s (a,b,c) coordinates.' + ].join(' ') + }), + line: { + color: scatterLineAttrs.color, + width: scatterLineAttrs.width, + dash: scatterLineAttrs.dash, + shape: extendFlat({}, scatterLineAttrs.shape, + {values: ['linear', 'spline']}), + smoothing: scatterLineAttrs.smoothing + }, + connectgaps: scatterAttrs.connectgaps, + fill: extendFlat({}, scatterAttrs.fill, { + values: ['none', 'tozeroa', 'tozerob', 'tozeroc', + 'tonexta', 'tonextb', 'tonextc'] + }), + fillcolor: scatterAttrs.fillcolor, + marker: { + symbol: scatterMarkerAttrs.symbol, + opacity: scatterMarkerAttrs.opacity, + size: scatterMarkerAttrs.size, + sizeref: scatterMarkerAttrs.sizeref, + sizemin: scatterMarkerAttrs.sizemin, + sizemode: scatterMarkerAttrs.sizemode, + color: scatterMarkerAttrs.color, + colorscale: scatterMarkerAttrs.colorscale, + cauto: scatterMarkerAttrs.cauto, + cmax: scatterMarkerAttrs.cmax, + cmin: scatterMarkerAttrs.cmin, + autocolorscale: scatterMarkerAttrs.autocolorscale, + reversescale: scatterMarkerAttrs.reversescale, + showscale: scatterMarkerAttrs.showscale, + line: { + color: scatterMarkerLineAttrs.color, + width: scatterMarkerLineAttrs.width, + colorscale: scatterMarkerLineAttrs.colorscale, + cauto: scatterMarkerLineAttrs.cauto, + cmax: scatterMarkerLineAttrs.cmax, + cmin: scatterMarkerLineAttrs.cmin, + autocolorscale: scatterMarkerLineAttrs.autocolorscale, + reversescale: scatterMarkerLineAttrs.reversescale + } + }, + textfont: scatterAttrs.textfont, + textposition: scatterAttrs.textposition, + hoverinfo: extendFlat({}, plotAttrs.hoverinfo, { + flags: ['a', 'b', 'c', 'text', 'name'] + }), + _nestedModules: { + 'marker.colorbar': 'Colorbar' + } +}; From 6b4af04e66fe5d6eb5b0657b75f1541275e0d243 Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Sun, 20 Mar 2016 00:26:54 +0100 Subject: [PATCH 04/76] handleSubplotDefaults (for gl3d, geo, and ternary... for now!) --- src/plots/geo/layout/defaults.js | 34 ++---- src/plots/gl3d/layout/defaults.js | 166 +++++++++++++----------------- src/plots/subplot_defaults.js | 73 +++++++++++++ 3 files changed, 154 insertions(+), 119 deletions(-) create mode 100644 src/plots/subplot_defaults.js diff --git a/src/plots/geo/layout/defaults.js b/src/plots/geo/layout/defaults.js index 8e852eb5ac4..5919cbfafcb 100644 --- a/src/plots/geo/layout/defaults.js +++ b/src/plots/geo/layout/defaults.js @@ -9,8 +9,7 @@ 'use strict'; -var Lib = require('../../../lib'); -var Plots = require('../../plots'); +var handleSubplotDefaults = require('../../subplot_defaults'); var constants = require('../../../constants/geo_constants'); var layoutAttributes = require('./layout_attributes'); var supplyGeoAxisLayoutDefaults = require('./axis_defaults'); @@ -19,31 +18,12 @@ var supplyGeoAxisLayoutDefaults = require('./axis_defaults'); module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) { if(!layoutOut._hasGeo) return; - var geos = Plots.findSubplotIds(fullData, 'geo'), - geosLength = geos.length; - - var geoLayoutIn, geoLayoutOut; - - function coerce(attr, dflt) { - return Lib.coerce(geoLayoutIn, geoLayoutOut, layoutAttributes, attr, dflt); - } - - for(var i = 0; i < geosLength; i++) { - var geo = geos[i]; - - // geo traces get a layout geo for free! - if(layoutIn[geo]) geoLayoutIn = layoutIn[geo]; - else geoLayoutIn = layoutIn[geo] = {}; - - geoLayoutIn = layoutIn[geo]; - geoLayoutOut = {}; - - coerce('domain.x'); - coerce('domain.y', [i / geosLength, (i + 1) / geosLength]); - - handleGeoDefaults(geoLayoutIn, geoLayoutOut, coerce); - layoutOut[geo] = geoLayoutOut; - } + handleSubplotDefaults(layoutIn, layoutOut, fullData, { + type: 'geo', + attributes: layoutAttributes, + handleDefaults: handleGeoDefaults, + partition: 'y' + }); }; function handleGeoDefaults(geoLayoutIn, geoLayoutOut, coerce) { diff --git a/src/plots/gl3d/layout/defaults.js b/src/plots/gl3d/layout/defaults.js index c4a107f9b38..ee2d1e4d264 100644 --- a/src/plots/gl3d/layout/defaults.js +++ b/src/plots/gl3d/layout/defaults.js @@ -9,8 +9,7 @@ 'use strict'; -var Lib = require('../../../lib'); -var Plots = require('../../plots'); +var handleSubplotDefaults = require('../../subplot_defaults'); var layoutAttributes = require('./layout_attributes'); var supplyGl3dAxisLayoutDefaults = require('./axis_defaults'); @@ -18,103 +17,86 @@ var supplyGl3dAxisLayoutDefaults = require('./axis_defaults'); module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) { if(!layoutOut._hasGL3D) return; - var scenes = Plots.findSubplotIds(fullData, 'gl3d'), - scenesLength = scenes.length; - - var sceneLayoutIn, sceneLayoutOut; - - function coerce(attr, dflt) { - return Lib.coerce(sceneLayoutIn, sceneLayoutOut, layoutAttributes, attr, dflt); - } + var hasNon3D = ( + layoutOut._hasCartesian || + layoutOut._hasGeo || + layoutOut._hasGL2D || + layoutOut._hasPie || + layoutOut._hasTernary + ); // some layout-wide attribute are used in all scenes // if 3D is the only visible plot type function getDfltFromLayout(attr) { - var isOnlyGL3D = !( - layoutOut._hasCartesian || - layoutOut._hasGeo || - layoutOut._hasGL2D || - layoutOut._hasPie - ); + if(hasNon3D) return; var isValid = layoutAttributes[attr].values.indexOf(layoutIn[attr]) !== -1; + if(isValid) return layoutIn[attr]; + } - if(isOnlyGL3D && isValid) return layoutIn[attr]; + handleSubplotDefaults(layoutIn, layoutOut, fullData, { + type: 'gl3d', + attributes: layoutAttributes, + handleDefaults: handleGl3dDefaults, + font: layoutOut.font, + fullData: fullData, + getDfltFromLayout: getDfltFromLayout + }); +}; + +function handleGl3dDefaults(sceneLayoutIn, sceneLayoutOut, coerce, opts) { + /* + * Scene numbering proceeds as follows + * scene + * scene2 + * scene3 + * + * and d.scene will be undefined or some number or number string + * + * Also write back a blank scene object to user layout so that some + * attributes like aspectratio can be written back dynamically. + */ + + coerce('bgcolor'); + + var cameraKeys = Object.keys(layoutAttributes.camera); + + for(var j = 0; j < cameraKeys.length; j++) { + coerce('camera.' + cameraKeys[j] + '.x'); + coerce('camera.' + cameraKeys[j] + '.y'); + coerce('camera.' + cameraKeys[j] + '.z'); } - for(var i = 0; i < scenesLength; i++) { - var scene = scenes[i]; - - /* - * Scene numbering proceeds as follows - * scene - * scene2 - * scene3 - * - * and d.scene will be undefined or some number or number string - * - * Also write back a blank scene object to user layout so that some - * attributes like aspectratio can be written back dynamically. - */ - - // gl3d traces get a layout scene for free! - if(layoutIn[scene]) sceneLayoutIn = layoutIn[scene]; - else layoutIn[scene] = sceneLayoutIn = {}; - - sceneLayoutOut = layoutOut[scene] || {}; - - coerce('bgcolor'); - - var cameraKeys = Object.keys(layoutAttributes.camera); - - for(var j = 0; j < cameraKeys.length; j++) { - coerce('camera.' + cameraKeys[j] + '.x'); - coerce('camera.' + cameraKeys[j] + '.y'); - coerce('camera.' + cameraKeys[j] + '.z'); - } - - coerce('domain.x', [i / scenesLength, (i+1) / scenesLength]); - coerce('domain.y'); - - /* - * coerce to positive number (min 0) but also do not accept 0 (>0 not >=0) - * note that 0's go false with the !! call - */ - var hasAspect = !!coerce('aspectratio.x') && - !!coerce('aspectratio.y') && - !!coerce('aspectratio.z'); - - var defaultAspectMode = hasAspect ? 'manual' : 'auto'; - var aspectMode = coerce('aspectmode', defaultAspectMode); - - /* - * We need aspectratio object in all the Layouts as it is dynamically set - * in the calculation steps, ie, we cant set the correct data now, it happens later. - * We must also account for the case the user sends bad ratio data with 'manual' set - * for the mode. In this case we must force change it here as the default coerce - * misses it above. - */ - if(!hasAspect) { - sceneLayoutIn.aspectratio = sceneLayoutOut.aspectratio = {x: 1, y: 1, z: 1}; - - if(aspectMode === 'manual') sceneLayoutOut.aspectmode = 'auto'; - } - - /* - * scene arrangements need to be implemented: For now just splice - * along the horizontal direction. ie. - * x:[0,1] -> x:[0,0.5], x:[0.5,1] -> - * x:[0, 0.333] x:[0.333,0.666] x:[0.666, 1] - */ - supplyGl3dAxisLayoutDefaults(sceneLayoutIn, sceneLayoutOut, { - font: layoutOut.font, - scene: scene, - data: fullData - }); - - coerce('dragmode', getDfltFromLayout('dragmode')); - coerce('hovermode', getDfltFromLayout('hovermode')); - - layoutOut[scene] = sceneLayoutOut; + /* + * coerce to positive number (min 0) but also do not accept 0 (>0 not >=0) + * note that 0's go false with the !! call + */ + var hasAspect = !!coerce('aspectratio.x') && + !!coerce('aspectratio.y') && + !!coerce('aspectratio.z'); + + var defaultAspectMode = hasAspect ? 'manual' : 'auto'; + var aspectMode = coerce('aspectmode', defaultAspectMode); + + /* + * We need aspectratio object in all the Layouts as it is dynamically set + * in the calculation steps, ie, we cant set the correct data now, it happens later. + * We must also account for the case the user sends bad ratio data with 'manual' set + * for the mode. In this case we must force change it here as the default coerce + * misses it above. + */ + if(!hasAspect) { + sceneLayoutIn.aspectratio = sceneLayoutOut.aspectratio = {x: 1, y: 1, z: 1}; + + if(aspectMode === 'manual') sceneLayoutOut.aspectmode = 'auto'; } -}; + + supplyGl3dAxisLayoutDefaults(sceneLayoutIn, sceneLayoutOut, { + font: opts.font, + scene: opts.id, + data: opts.fullData + }); + + coerce('dragmode', opts.getDfltFromLayout('dragmode')); + coerce('hovermode', opts.getDfltFromLayout('hovermode')); +} diff --git a/src/plots/subplot_defaults.js b/src/plots/subplot_defaults.js new file mode 100644 index 00000000000..15e42217b87 --- /dev/null +++ b/src/plots/subplot_defaults.js @@ -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 Lib = require('../lib'); +var Plots = require('./plots'); + + +/** + * Find and supply defaults to all subplots of a given type + * This handles subplots that are contained within one container - so + * gl3d, geo, ternary... but not 2d axes which have separate x and y axes + * finds subplots, coerces their `domain` attributes, then calls the + * given handleDefaults function to fill in everything else. + * + * layoutIn: the complete user-supplied input layout + * layoutOut: the complete finished layout + * fullData: the finished data array, used only to find subplots + * opts: { + * type: subplot type string + * attributes: subplot attributes object + * partition: 'x' or 'y', which direction to divide domain space by default + * (default 'x', ie side-by-side subplots) + * TODO: this option is only here because 3D and geo made opposite + * choices in this regard previously and I didn't want to change it. + * Instead we should do: + * - something consistent + * - something more square (4 cuts 2x2, 5/6 cuts 2x3, etc.) + * - something that includes all subplot types in one arrangement, + * now that we can have them together! + * handleDefaults: function of (subplotLayoutIn, subplotLayoutOut, coerce, opts) + * this opts object is passed through to handleDefaults, so attach any + * additional items needed by this function here as well + * } + */ +module.exports = function handleSubplotDefaults(layoutIn, layoutOut, fullData, opts) { + var subplotType = opts.type, + subplotAttributes = opts.attributes, + handleDefaults = opts.handleDefaults, + partition = opts.partition || 'x'; + + var ids = Plots.findSubplotIds(fullData, subplotType), + idsLength = ids.length; + + var subplotLayoutIn, subplotLayoutOut; + + function coerce(attr, dflt) { + return Lib.coerce(subplotLayoutIn, subplotLayoutOut, subplotAttributes, attr, dflt); + } + + for(var i = 0; i < idsLength; i++) { + var id = ids[i]; + + // ternary traces get a layout ternary for free! + if(layoutIn[id]) subplotLayoutIn = layoutIn[id]; + else subplotLayoutIn = layoutIn[id] = {}; + + layoutOut[id] = subplotLayoutOut = {}; + + coerce('domain.' + partition, [i / idsLength, (i + 1) / idsLength]); + coerce('domain.' + {x: 'y', y: 'x'}[partition]); + + opts.id = id; + handleDefaults(subplotLayoutIn, subplotLayoutOut, coerce, opts); + } +}; From 5cd86fcf9353aefa6c9a61e1f97b44c4470fcc67 Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Sun, 20 Mar 2016 00:38:12 +0100 Subject: [PATCH 05/76] ternary scaffolding --- src/plot_api/plot_api.js | 4 +- src/plots/layout_attributes.js | 7 ++- src/plots/plots.js | 2 + src/plots/ternary/index.js | 71 +++++++++++++++++++++++ src/plots/ternary/layout/axis_defaults.js | 35 +++++++++++ src/plots/ternary/layout/defaults.js | 32 ++++++++++ 6 files changed, 149 insertions(+), 2 deletions(-) create mode 100644 src/plots/ternary/index.js create mode 100644 src/plots/ternary/layout/axis_defaults.js create mode 100644 src/plots/ternary/layout/defaults.js diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index 1753264abf2..6aba9c10188 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -280,6 +280,7 @@ Plotly.plot = function(gd, data, layout, config) { if(fullLayout._hasCartesian || fullLayout._hasPie) { plotRegistry.cartesian.plot(gd); } + if(fullLayout._hasTernary) plotRegistry.ternary.plot(gd); // clean up old scenes that no longer have associated data // will this be a performance hit? @@ -2595,7 +2596,8 @@ function makePlotFramework(gd) { if(fullLayout._hasCartesian) makeCartesianPlotFramwork(gd, subplots); - // single shape and pie layers for the whole plot + // single ternary, shape and pie layers for the whole plot + fullLayout._ternarylayer = fullLayout._paper.append('g').classed('ternarylayer', true); fullLayout._shapelayer = fullLayout._paper.append('g').classed('shapelayer', true); fullLayout._pielayer = fullLayout._paper.append('g').classed('pielayer', true); diff --git a/src/plots/layout_attributes.js b/src/plots/layout_attributes.js index 41781deca30..601251a1a8d 100644 --- a/src/plots/layout_attributes.js +++ b/src/plots/layout_attributes.js @@ -188,6 +188,10 @@ module.exports = { valType: 'boolean', dflt: false }, + _hasTernary: { + valType: 'boolean', + dflt: false + }, _composedModules: { '*': 'Fx' }, @@ -200,6 +204,7 @@ module.exports = { 'geo': 'geo', 'legend': 'Legend', 'annotations': 'Annotations', - 'shapes': 'Shapes' + 'shapes': 'Shapes', + 'ternary': 'ternary' } }; diff --git a/src/plots/plots.js b/src/plots/plots.js index 6d3693b754c..0635212c18f 100644 --- a/src/plots/plots.js +++ b/src/plots/plots.js @@ -470,6 +470,7 @@ plots.supplyDefaults = function(gd) { else if(plots.traceIs(fullTrace, 'geo')) newFullLayout._hasGeo = true; else if(plots.traceIs(fullTrace, 'pie')) newFullLayout._hasPie = true; else if(plots.traceIs(fullTrace, 'gl2d')) newFullLayout._hasGL2D = true; + else if(plots.traceIs(fullTrace, 'ternary')) newFullLayout._hasTernary = true; else if('r' in fullTrace) newFullLayout._hasPolar = true; _module = fullTrace._module; @@ -714,6 +715,7 @@ plots.supplyLayoutGlobalDefaults = function(layoutIn, layoutOut) { coerce('_hasGeo'); coerce('_hasPie'); coerce('_hasGL2D'); + coerce('_hasTernary'); }; plots.supplyLayoutModuleDefaults = function(layoutIn, layoutOut, fullData) { diff --git a/src/plots/ternary/index.js b/src/plots/ternary/index.js new file mode 100644 index 00000000000..b33cdd019d3 --- /dev/null +++ b/src/plots/ternary/index.js @@ -0,0 +1,71 @@ +/** +* 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 Ternary = require('./ternary'); + +var Plots = require('../../plots/plots'); + + +exports.name = 'ternary'; + +exports.attr = 'ternary'; + +exports.idRoot = 'ternary'; + +exports.idRegex = /^ternary([2-9]|[1-9][0-9]+)?$/; + +exports.attrRegex = /^ternary([2-9]|[1-9][0-9]+)?$/; + +exports.attributes = require('./layout/attributes'); + +exports.layoutAttributes = require('./layout/layout_attributes'); + +exports.supplyLayoutDefaults = require('./layout/defaults'); + +exports.plot = function plotTernary(gd) { + var fullLayout = gd._fullLayout, + fullData = gd._fullData, + ternaryIds = Plots.getSubplotIds(fullLayout, 'ternary'); + + for(var i = 0; i < ternaryIds.length; i++) { + var ternaryId = ternaryIds[i], + fullTernaryData = Plots.getSubplotData(fullData, 'ternary', ternaryId), + ternary = fullLayout[ternaryId]._ternary; + + // If ternary is not instantiated, create one! + if(ternary === undefined) { + ternary = new Ternary({ + id: ternaryId, + graphDiv: gd, + container: fullLayout._ternarycontainer.node() + }, + fullLayout + ); + + fullLayout[ternaryId]._ternary = ternary; + } + + ternary.plot(fullTernaryData, fullLayout, gd._promises); + } +}; + +exports.clean = function(newFullData, newFullLayout, oldFullData, oldFullLayout) { + var oldTernaryKeys = Plots.getSubplotIds(oldFullLayout, 'ternary'); + + for(var i = 0; i < oldTernaryKeys.length; i++) { + var oldTernaryKey = oldTernaryKeys[i]; + var oldTernary = oldFullLayout[oldTernaryKey]._ternary; + + if(!newFullLayout[oldTernaryKey] && !!oldTernary) { + oldTernary.ternaryDiv.remove(); + } + } +}; diff --git a/src/plots/ternary/layout/axis_defaults.js b/src/plots/ternary/layout/axis_defaults.js new file mode 100644 index 00000000000..8fdd4799987 --- /dev/null +++ b/src/plots/ternary/layout/axis_defaults.js @@ -0,0 +1,35 @@ +/** +* 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 Lib = require('../../../lib'); +var layoutAttributes = require('./axis_attributes'); + +var axesNames = ['aaxis', 'baxis', 'caxis']; + + +module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, options) { + var containerIn, containerOut; + + function coerce(attr, dflt) { + return Lib.coerce(containerIn, containerOut, layoutAttributes, attr, dflt); + } + + for(var j = 0; j < axesNames.length; j++) { + var axName = axesNames[j]; + containerIn = layoutIn[axName] || {}; + + containerOut = { + _name: axName + }; + + // TODO - what can we reuse from cartesian? + } +}; diff --git a/src/plots/ternary/layout/defaults.js b/src/plots/ternary/layout/defaults.js new file mode 100644 index 00000000000..393ae9b6f89 --- /dev/null +++ b/src/plots/ternary/layout/defaults.js @@ -0,0 +1,32 @@ +/** +* 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 handleSubplotDefaults = require('../../subplot_defaults'); +var layoutAttributes = require('./layout_attributes'); +var supplyTernaryAxisLayoutDefaults = require('./axis_defaults'); + +module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) { + if(!layoutOut._hasTernary) return; + + handleSubplotDefaults(layoutIn, layoutOut, fullData, { + type: 'ternary', + attributes: layoutAttributes, + handleDefaults: handleTernaryDefaults, + font: layoutOut.font + }); +}; + +function handleTernaryDefaults(ternaryLayoutIn, ternaryLayoutOut, coerce, options) { + coerce('bgcolor'); + coerce('sum'); + + supplyTernaryAxisLayoutDefaults(ternaryLayoutIn, ternaryLayoutOut, options); +} From aee10b6d5fdc3833875bf9326dff20886c2f85e0 Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Sun, 20 Mar 2016 00:38:40 +0100 Subject: [PATCH 06/76] tyop --- src/traces/scatter/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/traces/scatter/index.js b/src/traces/scatter/index.js index a7a9cafd08e..3b576a561d0 100644 --- a/src/traces/scatter/index.js +++ b/src/traces/scatter/index.js @@ -40,7 +40,7 @@ Scatter.meta = { 'The data visualized as scatter point or lines is set in `x` and `y`.', 'Text (appearing either on the chart or on hover only) is via `text`.', 'Bubble charts are achieved by setting `marker.size` and/or `marker.color`', - 'to a numerical arrays.' + 'to numerical arrays.' ].join(' ') }; From 330574b90b051b38cbaaa41cafbf5c189ac6b453 Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Sun, 20 Mar 2016 03:01:58 +0100 Subject: [PATCH 07/76] common axis.color --- src/components/color/attributes.js | 6 ++++++ src/components/color/index.js | 18 ++++++++++++++++++ src/plots/cartesian/axis_defaults.js | 12 ++++++++---- src/plots/cartesian/layout_attributes.js | 10 ++++++++++ src/plots/cartesian/layout_defaults.js | 19 ++++++++++++------- src/plots/cartesian/tick_defaults.js | 9 +++++++-- src/plots/gl3d/layout/axis_attributes.js | 1 + src/plots/gl3d/layout/axis_defaults.js | 17 +++++++++++++---- src/plots/gl3d/layout/defaults.js | 11 ++++++++--- src/plots/ternary/layout/axis_attributes.js | 11 +---------- test/jasmine/tests/axes_test.js | 6 ++++-- test/jasmine/tests/gl3daxes_test.js | 13 ++++++++----- 12 files changed, 96 insertions(+), 37 deletions(-) diff --git a/src/components/color/attributes.js b/src/components/color/attributes.js index 6febca3091c..8e9a0b0d2b4 100644 --- a/src/components/color/attributes.js +++ b/src/components/color/attributes.js @@ -28,3 +28,9 @@ exports.defaultLine = '#444'; exports.lightLine = '#eee'; exports.background = '#fff'; + +// with axis.color and Color.interp we aren't using lightLine +// itself anymore, instead interpolating between axis.color +// and the background color. lightFraction gives back exactly +// lightLine if the other colors are defaults. +exports.lightFraction = (0xe - 0x4) / (0xf - 0x4); diff --git a/src/components/color/index.js b/src/components/color/index.js index 655c514652a..48682faf9af 100644 --- a/src/components/color/index.js +++ b/src/components/color/index.js @@ -19,6 +19,7 @@ color.defaults = colorAttrs.defaults; color.defaultLine = colorAttrs.defaultLine; color.lightLine = colorAttrs.lightLine; color.background = colorAttrs.background; +var lightFraction = colorAttrs.lightFraction; color.tinyRGB = function(tc) { var c = tc.toRgb(); @@ -57,6 +58,23 @@ color.combine = function(front, back) { return tinycolor(fcflat).toRgbString(); }; +// find a color interpolating between two endpoints, +// in rgba space. +color.lightColor = function(front, back, backFraction) { + if(backFraction===undefined) backFraction = lightFraction; + + var c1 = tinycolor(front).toRgb(), + c2 = tinycolor(back || color.background).toRgb(), + frontfraction = 1 - backFraction, + cInterp = { + r: frontfraction * c1.r + backFraction * c2.r, + g: frontfraction * c1.g + backFraction * c2.g, + b: frontfraction * c1.b + backFraction * c2.b, + a: frontfraction * c1.a + backFraction * c2.a + }; + return tinycolor(cInterp).toRgbString(); +}; + color.stroke = function(s, c) { var tc = tinycolor(c); s.style({'stroke': color.tinyRGB(tc), 'stroke-opacity': tc.getAlpha()}); diff --git a/src/plots/cartesian/axis_defaults.js b/src/plots/cartesian/axis_defaults.js index 2e2e5e19366..609f35b5390 100644 --- a/src/plots/cartesian/axis_defaults.js +++ b/src/plots/cartesian/axis_defaults.js @@ -13,6 +13,7 @@ var isNumeric = require('fast-isnumeric'); var Lib = require('../../lib'); var Plots = require('../plots'); +var lightColor = require('../../components/color').lightColor; var layoutAttributes = require('./layout_attributes'); var handleTickValueDefaults = require('./tick_value_defaults'); @@ -33,6 +34,7 @@ var axisIds = require('./axis_ids'); * showGrid: boolean, should gridlines be shown by default? * noHover: boolean, this axis doesn't support hover effects? * data: the plot data to use in choosing auto type + * bgColor: the plot background color, to calculate default gridline colors */ module.exports = function handleAxisDefaults(containerIn, containerOut, coerce, options) { var letter = options.letter, @@ -66,11 +68,13 @@ module.exports = function handleAxisDefaults(containerIn, containerOut, coerce, setConvert(containerOut); + var dfltColor = coerce('color'); + coerce('title', defaultTitle); Lib.coerceFont(coerce, 'titlefont', { family: font.family, size: Math.round(font.size * 1.2), - color: font.color + color: font.color || dfltColor }); var validRange = ( @@ -92,7 +96,7 @@ module.exports = function handleAxisDefaults(containerIn, containerOut, coerce, handleTickValueDefaults(containerIn, containerOut, coerce, axType); handleTickDefaults(containerIn, containerOut, coerce, axType, options); - var lineColor = Lib.coerce2(containerIn, containerOut, layoutAttributes, 'linecolor'), + var lineColor = Lib.coerce2(containerIn, containerOut, layoutAttributes, 'linecolor', dfltColor), lineWidth = Lib.coerce2(containerIn, containerOut, layoutAttributes, 'linewidth'), showLine = coerce('showline', !!lineColor || !!lineWidth); @@ -103,7 +107,7 @@ module.exports = function handleAxisDefaults(containerIn, containerOut, coerce, if(showLine || containerOut.ticks) coerce('mirror'); - var gridColor = Lib.coerce2(containerIn, containerOut, layoutAttributes, 'gridcolor'), + var gridColor = Lib.coerce2(containerIn, containerOut, layoutAttributes, 'gridcolor', lightColor(dfltColor, options.bgColor)), gridWidth = Lib.coerce2(containerIn, containerOut, layoutAttributes, 'gridwidth'), showGridLines = coerce('showgrid', options.showGrid || !!gridColor || !!gridWidth); @@ -112,7 +116,7 @@ module.exports = function handleAxisDefaults(containerIn, containerOut, coerce, delete containerOut.gridwidth; } - var zeroLineColor = Lib.coerce2(containerIn, containerOut, layoutAttributes, 'zerolinecolor'), + var zeroLineColor = Lib.coerce2(containerIn, containerOut, layoutAttributes, 'zerolinecolor', dfltColor), zeroLineWidth = Lib.coerce2(containerIn, containerOut, layoutAttributes, 'zerolinewidth'), showZeroLine = coerce('zeroline', options.showGrid || !!zeroLineColor || !!zeroLineWidth); diff --git a/src/plots/cartesian/layout_attributes.js b/src/plots/cartesian/layout_attributes.js index 8ec877fc6a2..cffb74140ae 100644 --- a/src/plots/cartesian/layout_attributes.js +++ b/src/plots/cartesian/layout_attributes.js @@ -13,6 +13,16 @@ var colorAttrs = require('../../components/color/attributes'); var extendFlat = require('../../lib/extend').extendFlat; module.exports = { + color: { + valType: 'color', + dflt: colorAttrs.defaultLine, + role: 'style', + description: [ + 'Sets default for all colors associated with this axis', + 'all at once: line, font, tick, and grid colors.', + 'Individual pieces can override this.' + ].join(' ') + }, title: { valType: 'string', role: 'info', diff --git a/src/plots/cartesian/layout_defaults.js b/src/plots/cartesian/layout_defaults.js index 7f454594d4e..6534409cb04 100644 --- a/src/plots/cartesian/layout_defaults.js +++ b/src/plots/cartesian/layout_defaults.js @@ -11,6 +11,7 @@ var Lib = require('../../lib'); var Plots = require('../plots'); +var Color = require('../../components/color'); var constants = require('./constants'); var layoutAttributes = require('./layout_attributes'); @@ -103,6 +104,15 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) { var xaList = xaListCartesian.concat(xaListGl2d).sort(axSort), yaList = yaListCartesian.concat(yaListGl2d).sort(axSort); + // plot_bgcolor only makes sense if there's a (2D) plot! + // TODO: bgcolor for each subplot, to inherit from the main one + var plot_bgcolor = Color.background; + if(xaList.length && yaList.length) { + plot_bgcolor = Lib.coerce(layoutIn, layoutOut, Plots.layoutAttributes, 'plot_bgcolor'); + } + + var bgColor = Color.combine(plot_bgcolor, layoutOut.paper_bgcolor); + xaList.concat(yaList).forEach(function(axName) { var axLetter = axName.charAt(0), axLayoutIn = layoutIn[axName] || {}, @@ -113,7 +123,8 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) { outerTicks: outerTicks[axName], showGrid: !noGrids[axName], name: axName, - data: fullData + data: fullData, + bgColor: bgColor }, positioningOptions = { letter: axLetter, @@ -138,10 +149,4 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) { } }); - - // plot_bgcolor only makes sense if there's a (2D) plot! - // TODO: bgcolor for each subplot, to inherit from the main one - if(xaList.length && yaList.length) { - Lib.coerce(layoutIn, layoutOut, Plots.layoutAttributes, 'plot_bgcolor'); - } }; diff --git a/src/plots/cartesian/tick_defaults.js b/src/plots/cartesian/tick_defaults.js index 7e8440617ec..b128a7f6b2f 100644 --- a/src/plots/cartesian/tick_defaults.js +++ b/src/plots/cartesian/tick_defaults.js @@ -20,7 +20,7 @@ var layoutAttributes = require('./layout_attributes'); module.exports = function handleTickDefaults(containerIn, containerOut, coerce, axType, options) { var tickLen = Lib.coerce2(containerIn, containerOut, layoutAttributes, 'ticklen'), tickWidth = Lib.coerce2(containerIn, containerOut, layoutAttributes, 'tickwidth'), - tickColor = Lib.coerce2(containerIn, containerOut, layoutAttributes, 'tickcolor'), + tickColor = Lib.coerce2(containerIn, containerOut, layoutAttributes, 'tickcolor', containerOut.color), showTicks = coerce('ticks', (options.outerTicks || tickLen || tickWidth || tickColor) ? 'outside' : ''); if(!showTicks) { @@ -39,7 +39,12 @@ module.exports = function handleTickDefaults(containerIn, containerOut, coerce, var showTickLabels = coerce('showticklabels'); if(showTickLabels) { - Lib.coerceFont(coerce, 'tickfont', options.font || {}); + var font = options.font || {}; + Lib.coerceFont(coerce, 'tickfont', { + family: font.family, + size: font.size, + color: font.color || containerOut.color + }); coerce('tickangle'); if(axType !== 'category') { diff --git a/src/plots/gl3d/layout/axis_attributes.js b/src/plots/gl3d/layout/axis_attributes.js index dbc8811bf18..15bfb6b483a 100644 --- a/src/plots/gl3d/layout/axis_attributes.js +++ b/src/plots/gl3d/layout/axis_attributes.js @@ -67,6 +67,7 @@ module.exports = { dflt: true, description: 'Sets whether or not this axis is labeled' }, + color: axesAttrs.color, title: axesAttrs.title, titlefont: axesAttrs.titlefont, type: axesAttrs.type, diff --git a/src/plots/gl3d/layout/axis_defaults.js b/src/plots/gl3d/layout/axis_defaults.js index c0473aefbf4..803878d4605 100644 --- a/src/plots/gl3d/layout/axis_defaults.js +++ b/src/plots/gl3d/layout/axis_defaults.js @@ -10,12 +10,17 @@ 'use strict'; var Lib = require('../../../lib'); +var Color = require('../../../components/color'); + var layoutAttributes = require('./axis_attributes'); var handleAxisDefaults = require('../../cartesian/axis_defaults'); var axesNames = ['xaxis', 'yaxis', 'zaxis']; var noop = function() {}; +// TODO: hard-coded lightness fraction based on gridline default colors +// that differ from other subplot types. +var gridLightness = (204 - 0x44) / (255 - 0x44); module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, options) { var containerIn, containerOut; @@ -40,10 +45,11 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, options) { font: options.font, letter: axName[0], data: options.data, - showGrid: true + showGrid: true, + bgColor: options.bgColorCombined }); - coerce('gridcolor'); + coerce('gridcolor', Color.lightColor(containerOut.color, options.bgColorCombined, gridLightness)); coerce('title', axName[0]); // shouldn't this be on-par with 2D? containerOut.setScale = noop; @@ -51,10 +57,13 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, options) { if(coerce('showspikes')) { coerce('spikesides'); coerce('spikethickness'); - coerce('spikecolor'); + // TODO: this makes the default spikes #444 rather than + // #000, is that OK? + coerce('spikecolor', containerOut.color); } - if(coerce('showbackground')) coerce('backgroundcolor'); coerce('showaxeslabels'); + // TODO: relate backgroundcolor to common containerOut.color? + if(coerce('showbackground')) coerce('backgroundcolor'); } }; diff --git a/src/plots/gl3d/layout/defaults.js b/src/plots/gl3d/layout/defaults.js index ee2d1e4d264..066f7ec2fa4 100644 --- a/src/plots/gl3d/layout/defaults.js +++ b/src/plots/gl3d/layout/defaults.js @@ -9,6 +9,8 @@ 'use strict'; +var Color = require('../../../components/color'); + var handleSubplotDefaults = require('../../subplot_defaults'); var layoutAttributes = require('./layout_attributes'); var supplyGl3dAxisLayoutDefaults = require('./axis_defaults'); @@ -40,7 +42,8 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) { handleDefaults: handleGl3dDefaults, font: layoutOut.font, fullData: fullData, - getDfltFromLayout: getDfltFromLayout + getDfltFromLayout: getDfltFromLayout, + paper_bgcolor: layoutOut.paper_bgcolor }); }; @@ -57,7 +60,8 @@ function handleGl3dDefaults(sceneLayoutIn, sceneLayoutOut, coerce, opts) { * attributes like aspectratio can be written back dynamically. */ - coerce('bgcolor'); + var bgcolor = coerce('bgcolor'), + bgColorCombined = Color.combine(bgcolor, opts.paper_bgcolor); var cameraKeys = Object.keys(layoutAttributes.camera); @@ -94,7 +98,8 @@ function handleGl3dDefaults(sceneLayoutIn, sceneLayoutOut, coerce, opts) { supplyGl3dAxisLayoutDefaults(sceneLayoutIn, sceneLayoutOut, { font: opts.font, scene: opts.id, - data: opts.fullData + data: opts.fullData, + bgColor: bgColorCombined }); coerce('dragmode', opts.getDfltFromLayout('dragmode')); diff --git a/src/plots/ternary/layout/axis_attributes.js b/src/plots/ternary/layout/axis_attributes.js index eb049affd74..b4ae5350ac6 100644 --- a/src/plots/ternary/layout/axis_attributes.js +++ b/src/plots/ternary/layout/axis_attributes.js @@ -10,22 +10,13 @@ var axesAttrs = require('../../cartesian/layout_attributes'); -var colorAttrs = require('../../components/color/attributes'); var extendFlat = require('../../../lib/extend').extendFlat; module.exports = { title: axesAttrs.title, titlefont: axesAttrs.titlefont, - color: { // TODO: support this for all other kinds of axes as well - valType: 'color', - dflt: colorAttrs.defaultLine, - role: 'style', - description: [ - 'Sets line, font, tick, and grid color for this axis', - 'all at once. Individual pieces can override this.' - ].join(' ') - }, + color: axesAttrs.color, // ticks nticks: axesAttrs.nticks, ticks: axesAttrs.ticks, diff --git a/test/jasmine/tests/axes_test.js b/test/jasmine/tests/axes_test.js index abb8a3c9953..6d4eab3721b 100644 --- a/test/jasmine/tests/axes_test.js +++ b/test/jasmine/tests/axes_test.js @@ -3,6 +3,7 @@ var PlotlyInternal = require('@src/plotly'); var Plots = require('@src/plots/plots'); var Lib = require('@src/lib'); var Color = require('@src/components/color'); +var tinycolor = require('tinycolor2'); var handleTickValueDefaults = require('@src/plots/cartesian/tick_value_defaults'); var Axes = PlotlyInternal.Axes; @@ -221,10 +222,11 @@ describe('Test axes', function() { yaxis: {} }; supplyLayoutDefaults(layoutIn, layoutOut, fullData); + var lightLine = tinycolor(Color.lightLine).toRgbString(); expect(layoutOut.xaxis.gridwidth).toBe(1); - expect(layoutOut.xaxis.gridcolor).toBe(Color.lightLine); + expect(tinycolor(layoutOut.xaxis.gridcolor).toRgbString()).toBe(lightLine); expect(layoutOut.yaxis.gridwidth).toBe(1); - expect(layoutOut.yaxis.gridcolor).toBe(Color.lightLine); + expect(tinycolor(layoutOut.yaxis.gridcolor).toRgbString()).toBe(lightLine); }); it('should set gridcolor/gridwidth to undefined if showgrid is false', function() { diff --git a/test/jasmine/tests/gl3daxes_test.js b/test/jasmine/tests/gl3daxes_test.js index 5b3f0f5934e..cbf77f36e85 100644 --- a/test/jasmine/tests/gl3daxes_test.js +++ b/test/jasmine/tests/gl3daxes_test.js @@ -30,7 +30,7 @@ describe('Test Gl3dAxes', function() { 'showspikes': true, 'spikesides': true, 'spikethickness': 2, - 'spikecolor': 'rgb(0,0,0)', + 'spikecolor': '#444', 'showbackground': false, 'showaxeslabels': true }, @@ -42,7 +42,7 @@ describe('Test Gl3dAxes', function() { 'showspikes': true, 'spikesides': true, 'spikethickness': 2, - 'spikecolor': 'rgb(0,0,0)', + 'spikecolor': '#444', 'showbackground': false, 'showaxeslabels': true }, @@ -54,7 +54,7 @@ describe('Test Gl3dAxes', function() { 'showspikes': true, 'spikesides': true, 'spikethickness': 2, - 'spikecolor': 'rgb(0,0,0)', + 'spikecolor': '#444', 'showbackground': false, 'showaxeslabels': true } @@ -64,14 +64,17 @@ describe('Test Gl3dAxes', function() { var keys = Object.keys(validObject); for(var i = 0; i < keys.length; i++) { var k = keys[i]; - if(validObject[k] !== testObject[k]) return false; + expect(validObject[k]).toBe(testObject[k]); + // if(validObject[k] !== testObject[k]) return false; } return true; } supplyLayoutDefaults(layoutIn, layoutOut, options); ['xaxis', 'yaxis', 'zaxis'].forEach(function(axis) { - expect(checkKeys(expected[axis], layoutOut[axis])).toBe(true); + // expect( + checkKeys(expected[axis], layoutOut[axis]); + // ).toBe(true); }); }); }); From 2769e9c3f2d695ae86ff78fda6d76003a6c50f71 Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Tue, 22 Mar 2016 07:42:28 +0100 Subject: [PATCH 08/76] break up tick_defaults -> tick_label_defaults, tick_mark_defalts --- src/components/colorbar/defaults.js | 8 +++-- src/plots/cartesian/axis_defaults.js | 8 +++-- ...ick_defaults.js => tick_label_defaults.js} | 17 ++-------- src/plots/cartesian/tick_mark_defaults.js | 31 +++++++++++++++++++ 4 files changed, 44 insertions(+), 20 deletions(-) rename src/plots/cartesian/{tick_defaults.js => tick_label_defaults.js} (73%) create mode 100644 src/plots/cartesian/tick_mark_defaults.js diff --git a/src/components/colorbar/defaults.js b/src/components/colorbar/defaults.js index feba990b80f..c3b36748bde 100644 --- a/src/components/colorbar/defaults.js +++ b/src/components/colorbar/defaults.js @@ -11,7 +11,8 @@ var Lib = require('../../lib'); var handleTickValueDefaults = require('../../plots/cartesian/tick_value_defaults'); -var handleTickDefaults = require('../../plots/cartesian/tick_defaults'); +var handleTickMarkDefaults = require('../../plots/cartesian/tick_mark_defaults'); +var handleTickLabelDefaults = require('../../plots/cartesian/tick_label_defaults'); var attributes = require('./attributes'); @@ -52,7 +53,10 @@ module.exports = function colorbarDefaults(containerIn, containerOut, layout) { handleTickValueDefaults(colorbarIn, colorbarOut, coerce, 'linear'); - handleTickDefaults(colorbarIn, colorbarOut, coerce, 'linear', + handleTickLabelDefaults(colorbarIn, colorbarOut, coerce, + {outerTicks: false, font: layout.font, noHover: true}); + + handleTickMarkDefaults(colorbarIn, colorbarOut, coerce, 'linear', {outerTicks: false, font: layout.font, noHover: true}); coerce('title'); diff --git a/src/plots/cartesian/axis_defaults.js b/src/plots/cartesian/axis_defaults.js index 609f35b5390..f1b1dc5c402 100644 --- a/src/plots/cartesian/axis_defaults.js +++ b/src/plots/cartesian/axis_defaults.js @@ -17,7 +17,8 @@ var lightColor = require('../../components/color').lightColor; var layoutAttributes = require('./layout_attributes'); var handleTickValueDefaults = require('./tick_value_defaults'); -var handleTickDefaults = require('./tick_defaults'); +var handleTickMarkDefaults = require('./tick_mark_defaults'); +var handleTickLabelDefaults = require('./tick_label_defaults'); var setConvert = require('./set_convert'); var cleanDatum = require('./clean_datum'); var axisIds = require('./axis_ids'); @@ -74,7 +75,7 @@ module.exports = function handleAxisDefaults(containerIn, containerOut, coerce, Lib.coerceFont(coerce, 'titlefont', { family: font.family, size: Math.round(font.size * 1.2), - color: font.color || dfltColor + color: dfltColor }); var validRange = ( @@ -94,7 +95,8 @@ module.exports = function handleAxisDefaults(containerIn, containerOut, coerce, coerce('fixedrange'); handleTickValueDefaults(containerIn, containerOut, coerce, axType); - handleTickDefaults(containerIn, containerOut, coerce, axType, options); + handleTickLabelDefaults(containerIn, containerOut, coerce, axType, options); + handleTickMarkDefaults(containerIn, containerOut, coerce, options); var lineColor = Lib.coerce2(containerIn, containerOut, layoutAttributes, 'linecolor', dfltColor), lineWidth = Lib.coerce2(containerIn, containerOut, layoutAttributes, 'linewidth'), diff --git a/src/plots/cartesian/tick_defaults.js b/src/plots/cartesian/tick_label_defaults.js similarity index 73% rename from src/plots/cartesian/tick_defaults.js rename to src/plots/cartesian/tick_label_defaults.js index b128a7f6b2f..fdae2f0fbad 100644 --- a/src/plots/cartesian/tick_defaults.js +++ b/src/plots/cartesian/tick_label_defaults.js @@ -11,24 +11,11 @@ var Lib = require('../../lib'); -var layoutAttributes = require('./layout_attributes'); - /** * options: inherits font, outerTicks, noHover from axes.handleAxisDefaults */ -module.exports = function handleTickDefaults(containerIn, containerOut, coerce, axType, options) { - var tickLen = Lib.coerce2(containerIn, containerOut, layoutAttributes, 'ticklen'), - tickWidth = Lib.coerce2(containerIn, containerOut, layoutAttributes, 'tickwidth'), - tickColor = Lib.coerce2(containerIn, containerOut, layoutAttributes, 'tickcolor', containerOut.color), - showTicks = coerce('ticks', (options.outerTicks || tickLen || tickWidth || tickColor) ? 'outside' : ''); - - if(!showTicks) { - delete containerOut.ticklen; - delete containerOut.tickwidth; - delete containerOut.tickcolor; - } - +module.exports = function handleTickLabelDefaults(containerIn, containerOut, coerce, axType, options) { var showAttrDflt = getShowAttrDflt(containerIn); var tickPrefix = coerce('tickprefix'); @@ -43,7 +30,7 @@ module.exports = function handleTickDefaults(containerIn, containerOut, coerce, Lib.coerceFont(coerce, 'tickfont', { family: font.family, size: font.size, - color: font.color || containerOut.color + color: containerOut.color || font.color }); coerce('tickangle'); diff --git a/src/plots/cartesian/tick_mark_defaults.js b/src/plots/cartesian/tick_mark_defaults.js new file mode 100644 index 00000000000..8a05652182e --- /dev/null +++ b/src/plots/cartesian/tick_mark_defaults.js @@ -0,0 +1,31 @@ +/** +* 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 Lib = require('../../lib'); + +var layoutAttributes = require('./layout_attributes'); + + +/** + * options: inherits outerTicks from axes.handleAxisDefaults + */ +module.exports = function handleTickDefaults(containerIn, containerOut, coerce, options) { + var tickLen = Lib.coerce2(containerIn, containerOut, layoutAttributes, 'ticklen'), + tickWidth = Lib.coerce2(containerIn, containerOut, layoutAttributes, 'tickwidth'), + tickColor = Lib.coerce2(containerIn, containerOut, layoutAttributes, 'tickcolor', containerOut.color), + showTicks = coerce('ticks', (options.outerTicks || tickLen || tickWidth || tickColor) ? 'outside' : ''); + + if(!showTicks) { + delete containerOut.ticklen; + delete containerOut.tickwidth; + delete containerOut.tickcolor; + } +}; From 1b9f43e8014a9af9ce804c587092659107dc2bf8 Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Tue, 22 Mar 2016 07:43:19 +0100 Subject: [PATCH 09/76] finish first cut ternary supplyLayoutDefaults --- src/plots/ternary/layout/axis_attributes.js | 4 +- src/plots/ternary/layout/axis_defaults.js | 71 +++++++++++++++++---- src/plots/ternary/layout/defaults.js | 21 ++++-- 3 files changed, 79 insertions(+), 17 deletions(-) diff --git a/src/plots/ternary/layout/axis_attributes.js b/src/plots/ternary/layout/axis_attributes.js index b4ae5350ac6..c2148a9faf8 100644 --- a/src/plots/ternary/layout/axis_attributes.js +++ b/src/plots/ternary/layout/axis_attributes.js @@ -26,11 +26,13 @@ module.exports = { showticklabels: axesAttrs.showticklabels, tickfont: axesAttrs.tickfont, tickangle: axesAttrs.tickangle, + tickformat: axesAttrs.tickformat, + hoverformat: axesAttrs.hoverformat, // lines and grids showline: extendFlat({}, axesAttrs.showline, {dflt: true}), linecolor: axesAttrs.linecolor, linewidth: axesAttrs.linewidth, - showgrid: axesAttrs.showgrid, + showgrid: extendFlat({}, axesAttrs.showgrid, {dflt: true}), gridcolor: axesAttrs.gridcolor, gridwidth: axesAttrs.gridwidth, // range diff --git a/src/plots/ternary/layout/axis_defaults.js b/src/plots/ternary/layout/axis_defaults.js index 8fdd4799987..943e473c3d6 100644 --- a/src/plots/ternary/layout/axis_defaults.js +++ b/src/plots/ternary/layout/axis_defaults.js @@ -10,26 +10,73 @@ 'use strict'; var Lib = require('../../../lib'); -var layoutAttributes = require('./axis_attributes'); - -var axesNames = ['aaxis', 'baxis', 'caxis']; +var lightColor = require('../../components/color').lightColor; +var layoutAttributes = require('./axis_attributes'); +var handleTickMarkDefaults = require('../../cartesian/tick_mark_defaults'); -module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, options) { - var containerIn, containerOut; +module.exports = function supplyLayoutDefaults(containerIn, containerOut, options) { function coerce(attr, dflt) { return Lib.coerce(containerIn, containerOut, layoutAttributes, attr, dflt); } - for(var j = 0; j < axesNames.length; j++) { - var axName = axesNames[j]; - containerIn = layoutIn[axName] || {}; + function coerce2(attr, dflt) { + return Lib.coerce2(containerIn, containerOut, layoutAttributes, attr, dflt); + } + + containerOut.type = 'linear'; // no other types allowed for ternary + + var axName = containerOut._name; + + var dfltColor = coerce('color'); + + coerce('title', 'Click to enter component ' + + axName.charAt(0).toUpperCase() + ' title'); + Lib.coerceFont(coerce, 'titlefont', { + family: options.font.family, + size: Math.round(options.font.size * 1.2), + color: dfltColor + }); + + // range is just set by 'min' - max is determined by the other axes mins + coerce('min'); + + coerce('nticks'); + + handleTickMarkDefaults(containerIn, containerOut, coerce, 'linear', + {outerticks: false}); + + // TODO - below is a bit repetitious from cartesian still... + + var showTickLabels = coerce('showticklabels'); + if(showTickLabels) { + Lib.coerceFont(coerce, 'tickfont', { + family: options.font.family, + size: options.font.size, + color: containerOut.color + }); + coerce('tickangle'); + coerce('tickformat'); + } + + coerce('hoverformat'); + + var lineColor = coerce2('linecolor', dfltColor), + lineWidth = coerce2('linewidth'), + showLine = coerce('showline', !!lineColor || !!lineWidth); + + if(!showLine) { + delete containerOut.linecolor; + delete containerOut.linewidth; + } - containerOut = { - _name: axName - }; + var gridColor = coerce2('gridcolor', lightColor(dfltColor, options.bgColor)), + gridWidth = coerce2('gridwidth'), + showGridLines = coerce('showgrid', !!gridColor || !!gridWidth); - // TODO - what can we reuse from cartesian? + if(!showGridLines) { + delete containerOut.gridcolor; + delete containerOut.gridwidth; } }; diff --git a/src/plots/ternary/layout/defaults.js b/src/plots/ternary/layout/defaults.js index 393ae9b6f89..43ea3d7cd96 100644 --- a/src/plots/ternary/layout/defaults.js +++ b/src/plots/ternary/layout/defaults.js @@ -9,9 +9,13 @@ 'use strict'; +var Color = require('../../components/color'); + var handleSubplotDefaults = require('../../subplot_defaults'); var layoutAttributes = require('./layout_attributes'); -var supplyTernaryAxisLayoutDefaults = require('./axis_defaults'); +var handleAxisDefaults = require('./axis_defaults'); + +var axesNames = ['aaxis', 'baxis', 'caxis']; module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) { if(!layoutOut._hasTernary) return; @@ -20,13 +24,22 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) { type: 'ternary', attributes: layoutAttributes, handleDefaults: handleTernaryDefaults, - font: layoutOut.font + font: layoutOut.font, + paper_bgcolor: layoutOut.paper_bgcolor }); }; function handleTernaryDefaults(ternaryLayoutIn, ternaryLayoutOut, coerce, options) { - coerce('bgcolor'); + var bgColor = coerce('bgcolor'); coerce('sum'); + options.bgColor = Color.combine(bgColor, options.paper_bgcolor); + var axName, containerIn, containerOut; + + for(var j = 0; j < axesNames.length; j++) { + axName = axesNames[j]; + containerIn = ternaryLayoutIn[axName] || {}; + containerOut = {_name: axName}; - supplyTernaryAxisLayoutDefaults(ternaryLayoutIn, ternaryLayoutOut, options); + handleAxisDefaults(containerIn, containerOut, options); + } } From 4595ac688a1bee1dfa294abba97050c9c00e8e54 Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Tue, 22 Mar 2016 07:44:45 +0100 Subject: [PATCH 10/76] coerce2 local func --- src/plots/cartesian/axis_defaults.js | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/plots/cartesian/axis_defaults.js b/src/plots/cartesian/axis_defaults.js index f1b1dc5c402..6fd557ad0a2 100644 --- a/src/plots/cartesian/axis_defaults.js +++ b/src/plots/cartesian/axis_defaults.js @@ -44,6 +44,10 @@ module.exports = function handleAxisDefaults(containerIn, containerOut, coerce, (options.title || (letter.toUpperCase() + ' axis')) + ' title'; + function coerce2(attr, dflt) { + return Lib.coerce2(containerIn, containerOut, layoutAttributes, attr, dflt); + } + // set up some private properties if(options.name) { containerOut._name = options.name; @@ -98,8 +102,8 @@ module.exports = function handleAxisDefaults(containerIn, containerOut, coerce, handleTickLabelDefaults(containerIn, containerOut, coerce, axType, options); handleTickMarkDefaults(containerIn, containerOut, coerce, options); - var lineColor = Lib.coerce2(containerIn, containerOut, layoutAttributes, 'linecolor', dfltColor), - lineWidth = Lib.coerce2(containerIn, containerOut, layoutAttributes, 'linewidth'), + var lineColor = coerce2('linecolor', dfltColor), + lineWidth = coerce2('linewidth'), showLine = coerce('showline', !!lineColor || !!lineWidth); if(!showLine) { @@ -109,8 +113,8 @@ module.exports = function handleAxisDefaults(containerIn, containerOut, coerce, if(showLine || containerOut.ticks) coerce('mirror'); - var gridColor = Lib.coerce2(containerIn, containerOut, layoutAttributes, 'gridcolor', lightColor(dfltColor, options.bgColor)), - gridWidth = Lib.coerce2(containerIn, containerOut, layoutAttributes, 'gridwidth'), + var gridColor = coerce2('gridcolor', lightColor(dfltColor, options.bgColor)), + gridWidth = coerce2('gridwidth'), showGridLines = coerce('showgrid', options.showGrid || !!gridColor || !!gridWidth); if(!showGridLines) { @@ -118,8 +122,8 @@ module.exports = function handleAxisDefaults(containerIn, containerOut, coerce, delete containerOut.gridwidth; } - var zeroLineColor = Lib.coerce2(containerIn, containerOut, layoutAttributes, 'zerolinecolor', dfltColor), - zeroLineWidth = Lib.coerce2(containerIn, containerOut, layoutAttributes, 'zerolinewidth'), + var zeroLineColor = coerce2('zerolinecolor', dfltColor), + zeroLineWidth = coerce2('zerolinewidth'), showZeroLine = coerce('zeroline', options.showGrid || !!zeroLineColor || !!zeroLineWidth); if(!showZeroLine) { From 3ccc3eaff219e8f37db38ed0dcd77b051488adef Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Wed, 23 Mar 2016 10:11:52 +0100 Subject: [PATCH 11/76] scatterternary supplyDefaults --- src/traces/scatter/defaults.js | 10 +-- src/traces/scatter/line_shape_defaults.js | 17 +++++ src/traces/scatterternary/attributes.js | 6 +- src/traces/scatterternary/defaults.js | 87 +++++++++++++++++++++++ src/traces/scatterternary/index.js | 34 +++++++++ 5 files changed, 145 insertions(+), 9 deletions(-) create mode 100644 src/traces/scatter/line_shape_defaults.js create mode 100644 src/traces/scatterternary/defaults.js create mode 100644 src/traces/scatterternary/index.js diff --git a/src/traces/scatter/defaults.js b/src/traces/scatter/defaults.js index c18250b8208..3690160053f 100644 --- a/src/traces/scatter/defaults.js +++ b/src/traces/scatter/defaults.js @@ -17,6 +17,7 @@ var subTypes = require('./subtypes'); var handleXYDefaults = require('./xy_defaults'); var handleMarkerDefaults = require('./marker_defaults'); var handleLineDefaults = require('./line_defaults'); +var handleLineShapeDefaults = require('./line_shape_defaults'); var handleTextDefaults = require('./text_defaults'); var handleFillColorDefaults = require('./fillcolor_defaults'); var errorBarsSupplyDefaults = require('../../components/errorbars/defaults'); @@ -40,7 +41,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout if(subTypes.hasLines(traceOut)) { handleLineDefaults(traceIn, traceOut, defaultColor, coerce); - lineShapeDefaults(traceIn, traceOut, coerce); + handleLineShapeDefaults(traceIn, traceOut, coerce); coerce('connectgaps'); } @@ -59,14 +60,9 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout coerce('fill'); if(traceOut.fill !== 'none') { handleFillColorDefaults(traceIn, traceOut, defaultColor, coerce); - if(!subTypes.hasLines(traceOut)) lineShapeDefaults(traceIn, traceOut, coerce); + if(!subTypes.hasLines(traceOut)) handleLineShapeDefaults(traceIn, traceOut, coerce); } errorBarsSupplyDefaults(traceIn, traceOut, defaultColor, {axis: 'y'}); errorBarsSupplyDefaults(traceIn, traceOut, defaultColor, {axis: 'x', inherit: 'y'}); }; - -function lineShapeDefaults(traceIn, traceOut, coerce) { - var shape = coerce('line.shape'); - if(shape === 'spline') coerce('line.smoothing'); -} diff --git a/src/traces/scatter/line_shape_defaults.js b/src/traces/scatter/line_shape_defaults.js new file mode 100644 index 00000000000..07aaf13e82a --- /dev/null +++ b/src/traces/scatter/line_shape_defaults.js @@ -0,0 +1,17 @@ +/** +* 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'; + + +// common to 'scatter', 'scatter3d', 'scattergeo' and 'scattergl' +module.exports = function handleLineShapeDefaults(traceIn, traceOut, coerce) { + var shape = coerce('line.shape'); + if(shape === 'spline') coerce('line.smoothing'); +}; diff --git a/src/traces/scatterternary/attributes.js b/src/traces/scatterternary/attributes.js index 490f5d49daa..c5ffe691f30 100644 --- a/src/traces/scatterternary/attributes.js +++ b/src/traces/scatterternary/attributes.js @@ -51,13 +51,14 @@ module.exports = { sum: { valType: 'number', role: 'info', - dflt: 1, + dflt: 0, min: 0, description: [ 'The number each triplet should sum to,', 'if only two of `a`, `b`, and `c` are provided.', 'This overrides `ternary.sum` to normalize this specific', - 'trace, but does not affect the values displayed on the axes.' + 'trace, but does not affect the values displayed on the axes.', + '0 (or missing) means to use ternary.sum' ].join(' ') }, mode: extendFlat({}, scatterAttrs.mode, {dflt: 'markers'}), @@ -87,6 +88,7 @@ module.exports = { marker: { symbol: scatterMarkerAttrs.symbol, opacity: scatterMarkerAttrs.opacity, + maxdisplayed: scatterMarkerAttrs.maxdisplayed, size: scatterMarkerAttrs.size, sizeref: scatterMarkerAttrs.sizeref, sizemin: scatterMarkerAttrs.sizemin, diff --git a/src/traces/scatterternary/defaults.js b/src/traces/scatterternary/defaults.js new file mode 100644 index 00000000000..fa5a2d2a535 --- /dev/null +++ b/src/traces/scatterternary/defaults.js @@ -0,0 +1,87 @@ +/** +* 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 Lib = require('../../lib'); + +var constants = require('../scatter/constants'); +var subTypes = require('../scatter/subtypes'); +var handleMarkerDefaults = require('../scatter/marker_defaults'); +var handleLineDefaults = require('../scatter/line_defaults'); +var handleLineShapeDefaults = require('../scatter/line_shape_defaults'); +var handleTextDefaults = require('../scatter/text_defaults'); +var handleFillColorDefaults = require('../scatter/fillcolor_defaults'); + +var attributes = require('./attributes'); + + +module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) { + function coerce(attr, dflt) { + return Lib.coerce(traceIn, traceOut, attributes, attr, dflt); + } + + var a = coerce('a') || [], + b = coerce('b') || [], + c = coerce('c') || [], + len; + + // allow any one array to be missing, len is the minimum + // of any that have data + if(a.length) { + len = a.length; + if(b.length) { + len = Math.min(len, b.length); + if(c.length) len = Math.min(len, c.length); + } + else len = Math.min(len, c.length); + } + else len = Math.min(b.length, c.length); + + if(!len) { + traceOut.visible = false; + return; + } + + // cut all data arrays down to same length + if(len < a.length) traceOut.a = a.slice(0, len); + if(len < b.length) traceOut.b = b.slice(0, len); + if(len < c.length) traceOut.c = c.slice(0, len); + + coerce('sum'); + + coerce('text'); + + var defaultMode = len < constants.PTS_LINESONLY ? 'lines+markers' : 'lines'; + coerce('mode', defaultMode); + + if(subTypes.hasLines(traceOut)) { + handleLineDefaults(traceIn, traceOut, defaultColor, coerce); + handleLineShapeDefaults(traceIn, traceOut, coerce); + coerce('connectgaps'); + } + + if(subTypes.hasMarkers(traceOut)) { + handleMarkerDefaults(traceIn, traceOut, defaultColor, layout, coerce); + } + + if(subTypes.hasText(traceOut)) { + handleTextDefaults(traceIn, traceOut, layout, coerce); + } + + if(subTypes.hasMarkers(traceOut) || subTypes.hasText(traceOut)) { + coerce('marker.maxdisplayed'); + } + + coerce('fill'); + if(traceOut.fill !== 'none') { + handleFillColorDefaults(traceIn, traceOut, defaultColor, coerce); + if(!subTypes.hasLines(traceOut)) handleLineShapeDefaults(traceIn, traceOut, coerce); + } +}; diff --git a/src/traces/scatterternary/index.js b/src/traces/scatterternary/index.js new file mode 100644 index 00000000000..fa75cad106f --- /dev/null +++ b/src/traces/scatterternary/index.js @@ -0,0 +1,34 @@ +/** +* 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 ScatterTernary = {}; + +ScatterTernary.attributes = require('./attributes'); +ScatterTernary.supplyDefaults = require('./defaults'); +ScatterTernary.colorbar = require('../scatter/colorbar'); +ScatterTernary.calc = require('./calc'); +ScatterTernary.plot = require('./plot').plot; +ScatterTernary.style = require('./style'); +ScatterTernary.hoverPoints = require('./hover'); +ScatterTernary.selectPoints = require('./select'); + +ScatterTernary.moduleType = 'trace'; +ScatterTernary.name = 'scatterternary'; +ScatterTernary.basePlotModule = require('../../plots/ternary'); +ScatterTernary.categories = ['ternary', 'symbols', 'markerColorscale', 'showLegend']; +ScatterTernary.meta = { + hrName: 'scatter_ternary', + description: [ + 'Provides similar functionality to the *scatter* type but on a ternary plot.', + 'The data is provided by at least two arrays out of `a`, `b`, `c` triplets.' + ].join(' ') +}; + +module.exports = ScatterTernary; From 9ec6d8f5b19a04d1bdf89904ca7e55c67a40b671 Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Wed, 23 Mar 2016 21:09:22 +0100 Subject: [PATCH 12/76] scatterternary calc --- src/traces/scatterternary/calc.js | 97 +++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 src/traces/scatterternary/calc.js diff --git a/src/traces/scatterternary/calc.js b/src/traces/scatterternary/calc.js new file mode 100644 index 00000000000..606b74612c1 --- /dev/null +++ b/src/traces/scatterternary/calc.js @@ -0,0 +1,97 @@ +/** +* 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 Axes = require('../../plots/cartesian/axes'); +var Lib = require('../../lib'); + +var subTypes = require('./subtypes'); +var calcMarkerColorscale = require('../scatter/marker_colorscale_calc'); + +var dataArrays = ['a', 'b', 'c']; +var arraysToFill = {a: ['b', 'c'], b: ['a', 'c'], c: ['a', 'b']}; + + +module.exports = function calc(gd, trace) { + var ternary = gd._fullLayout[trace.subplot], + displaySum = ternary.sum, + normSum = trace.sum || displaySum; + + var i, j, dataArray, newArray, fillArray1, fillArray2; + + // fill in one missing component + for(i = 0; i < dataArrays.length; i++) { + dataArray = dataArrays[i]; + if(trace[dataArray] && trace[dataArray.length]) continue; + + fillArray1 = arraysToFill[dataArray][0]; + fillArray2 = arraysToFill[dataArray][1]; + newArray = new Array(fillArray1.length); + for(j = 0; j < fillArray1.length; j++) { + newArray[j] = normSum - fillArray1[j] - fillArray2[j]; + } + trace[dataArray] = newArray; + } + + // make the calcdata array + var serieslen = trace.a.length; + var cd = new Array(serieslen); + var a, b, c, norm, x, y; + for(i = 0; i < serieslen; i++) { + a = trace.a[i]; + b = trace.b[i]; + c = trace.c[i]; + if(isNumeric(a) && isNumeric(b) && isNumeric(c)) { + a = Number(a); + b = Number(b); + c = Number(c); + norm = displaySum / (a + b + c); + if(norm !== 1) { + a *= norm; + b *= norm; + c *= norm; + } + // map a, b, c onto x and y where the full scale of y + // is [0, sum], and x is [-sum, sum] + // TODO: this makes `a` always the top, `b` the bottom left, + // and `c` the bottom right. Do we want options to rearrange + // these? + y = a; + x = c - b; + cd[i] = {x: x, y: y, a: a, b: b, c: c}; + } + else cd[i] = {x: false, y: false}; + } + + // fill in some extras + var marker, s; + if(subTypes.hasMarkers(trace)) { + // Treat size like x or y arrays --- Run d2c + // this needs to go before ppad computation + marker = trace.marker; + s = marker.size; + + if(Array.isArray(s)) { + var ax = {type: 'linear'}; + Axes.setConvert(ax); + s = ax.makeCalcdata(trace.marker, 'size'); + if(s.length > serieslen) s.splice(serieslen, s.length - serieslen); + } + } + + calcMarkerColorscale(trace); + + // this has migrated up from arraysToCalcdata as we have a reference to 's' here + if(typeof s !== undefined) Lib.mergeArray(s, cd, 'ms'); + + return cd; +}; From abdce19ffcbbdc8ac1856c64fa9489e03b1c3133 Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Thu, 24 Mar 2016 21:17:53 +0100 Subject: [PATCH 13/76] lib.filterVisible --- src/lib/filter_visible.js | 22 ++++++++++++++++++++++ src/plots/geo/geo.js | 18 +++--------------- 2 files changed, 25 insertions(+), 15 deletions(-) create mode 100644 src/lib/filter_visible.js diff --git a/src/lib/filter_visible.js b/src/lib/filter_visible.js new file mode 100644 index 00000000000..cfea8b73941 --- /dev/null +++ b/src/lib/filter_visible.js @@ -0,0 +1,22 @@ +/** +* 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'; + +module.exports = function filterVisible(dataIn) { + var dataOut = []; + + for(var i = 0; i < dataIn.length; i++) { + var trace = dataIn[i]; + + if(trace.visible === true) dataOut.push(trace); + } + + return dataOut; +}; diff --git a/src/plots/geo/geo.js b/src/plots/geo/geo.js index 9a20153fa53..d46bd7010e9 100644 --- a/src/plots/geo/geo.js +++ b/src/plots/geo/geo.js @@ -17,6 +17,8 @@ var Color = require('../../components/color'); var Drawing = require('../../components/drawing'); var Axes = require('../../plots/cartesian/axes'); +var filterVisible = require('../../lib/filter_visible'); + var addProjectionsToD3 = require('./projections'); var createGeoScale = require('./set_scale'); var createGeoZoom = require('./zoom'); @@ -139,20 +141,6 @@ proto.plot = function(geoData, fullLayout, promises) { // to avoid making multiple request while streaming }; -// filter out non-visible trace -// geo plot routine use the classic join/enter/exit pattern to update traces -function filterData(dataIn) { - var dataOut = []; - - for(var i = 0; i < dataIn.length; i++) { - var trace = dataIn[i]; - - if(trace.visible === true) dataOut.push(trace); - } - - return dataOut; -} - proto.onceTopojsonIsLoaded = function(geoData, geoLayout) { var i; @@ -190,7 +178,7 @@ proto.onceTopojsonIsLoaded = function(geoData, geoLayout) { var moduleData = traceHash[moduleNames[i]]; var _module = moduleData[0]._module; - _module.plot(this, filterData(moduleData), geoLayout); + _module.plot(this, filterVisible(moduleData), geoLayout); } this.traceHash = traceHash; From c2f7f7169c6529e7f6dcd12f8fc9c8d280e9823e Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Wed, 30 Mar 2016 02:55:06 +0200 Subject: [PATCH 14/76] ternary + scatterternary basic functionality --- lib/index.js | 3 +- lib/scatterternary.js | 9 + src/components/titles/index.js | 8 +- src/plots/cartesian/axes.js | 93 +++--- src/plots/plots.js | 1 + src/plots/ternary/index.js | 7 +- src/plots/ternary/layout/axis_attributes.js | 2 +- src/plots/ternary/layout/axis_defaults.js | 6 +- src/plots/ternary/layout/defaults.js | 24 +- src/plots/ternary/ternary.js | 325 ++++++++++++++++++++ src/traces/scatterternary/calc.js | 8 +- src/traces/scatterternary/hover.js | 27 ++ src/traces/scatterternary/index.js | 4 +- src/traces/scatterternary/plot.js | 31 ++ src/traces/scatterternary/select.js | 31 ++ src/traces/scatterternary/style.js | 24 ++ 16 files changed, 548 insertions(+), 55 deletions(-) create mode 100644 lib/scatterternary.js create mode 100644 src/plots/ternary/ternary.js create mode 100644 src/traces/scatterternary/hover.js create mode 100644 src/traces/scatterternary/plot.js create mode 100644 src/traces/scatterternary/select.js create mode 100644 src/traces/scatterternary/style.js diff --git a/lib/index.js b/lib/index.js index 8a7548e57a7..631fed56656 100644 --- a/lib/index.js +++ b/lib/index.js @@ -29,7 +29,8 @@ Core.register([ require('./mesh3d'), require('./scattergeo'), require('./choropleth'), - require('./scattergl') + require('./scattergl'), + require('./scatterternary') ]); module.exports = Core; diff --git a/lib/scatterternary.js b/lib/scatterternary.js new file mode 100644 index 00000000000..e6b5bb4a4b3 --- /dev/null +++ b/lib/scatterternary.js @@ -0,0 +1,9 @@ +/** +* 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. +*/ + +module.exports = require('../src/traces/scatterternary'); diff --git a/src/components/titles/index.js b/src/components/titles/index.js index 0da50d3d9fc..4fa0463669f 100644 --- a/src/components/titles/index.js +++ b/src/components/titles/index.js @@ -97,9 +97,9 @@ Titles.draw = function(gd, title) { } else if(axletter === 'x') { xa = cont; - ya = (xa.anchor === 'free') ? + ya = xa._counteraxis || ((xa.anchor === 'free') ? {_offset: gs.t + (1 - (xa.position || 0)) * gs.h, _length: 0} : - axisIds.getFromId(gd, xa.anchor); + axisIds.getFromId(gd, xa.anchor)); x = xa._offset + xa._length / 2; y = ya._offset + ((xa.side === 'top') ? @@ -112,9 +112,9 @@ Titles.draw = function(gd, title) { } else if(axletter === 'y') { ya = cont; - xa = (ya.anchor === 'free') ? + xa = ya._counteraxis || ((ya.anchor === 'free') ? {_offset: gs.l + (ya.position || 0) * gs.w, _length: 0} : - axisIds.getFromId(gd, ya.anchor); + axisIds.getFromId(gd, ya.anchor)); y = ya._offset + ya._length / 2; x = xa._offset + ((ya.side === 'right') ? diff --git a/src/plots/cartesian/axes.js b/src/plots/cartesian/axes.js index c562a50fdf7..49cf9149171 100644 --- a/src/plots/cartesian/axes.js +++ b/src/plots/cartesian/axes.js @@ -1373,12 +1373,19 @@ axes.doTicks = function(td, axid, skipTitle) { pad = (ax.linewidth||1) / 2, labelStandoff = (ax.ticks==='outside' ? ax.ticklen : 1) + (ax.linewidth||0), + labelShift = 0, gridWidth = Plotly.Drawing.crispRound(td, ax.gridwidth, 1), zeroLineWidth = Plotly.Drawing.crispRound(td, ax.zerolinewidth, gridWidth), tickWidth = Plotly.Drawing.crispRound(td, ax.tickwidth, 1), - sides, transfn, tickprefix, tickmid, + sides, transfn, tickpathfn, i; + if(ax._counterangle && ax.ticks==='outside') { + var caRad = ax._counterangle * Math.PI / 180; + labelStandoff = ax.ticklen * Math.cos(caRad) + (ax.linewidth || 0); + labelShift = ax.ticklen * Math.sin(caRad); + } + // positioning arguments for x vs y axes if(axletter==='x') { sides = ['bottom', 'top']; @@ -1387,16 +1394,26 @@ axes.doTicks = function(td, axid, skipTitle) { }; // dumb templating with string concat // would be better to use an actual template - tickprefix = 'M0,'; - tickmid = 'v'; + tickpathfn = function(shift, len) { + if(ax._counterangle) { + var caRad = ax._counterangle * Math.PI / 180; + return 'M0,' + shift + 'l' + (Math.sin(caRad) * len) + ',' + (Math.cos(caRad) * len); + } + else return 'M0,' + shift + 'v' + len; + }; } else if(axletter==='y') { sides = ['left', 'right']; transfn = function(d) { return 'translate(0,'+ax.l2p(d.x)+')'; }; - tickprefix = 'M'; - tickmid = ',0h'; + tickpathfn = function(shift, len) { + if(ax._counterangle) { + var caRad = ax._counterangle * Math.PI / 180; + return 'M' + shift + ',0l' + (Math.cos(caRad) * len) + ',' + (-Math.sin(caRad) * len); + } + else return 'M' + shift + ',0h' + len; + }; } else { console.log('unrecognized doTicks axis', axid); @@ -1444,10 +1461,10 @@ axes.doTicks = function(td, axid, skipTitle) { return; } - var labelx, labely, labelanchor, labelpos0; + var labelx, labely, labelanchor, labelpos0, flipit; if(axletter==='x') { - var flipit = axside==='bottom' ? 1 : -1; - labelx = function(d) { return d.dx; }; + flipit = axside==='bottom' ? 1 : -1; + labelx = function(d) { return d.dx + labelShift * flipit; }; labelpos0 = position + (labelStandoff+pad)*flipit; labely = function(d) { return d.dy+labelpos0+d.fontSize * @@ -1461,11 +1478,11 @@ axes.doTicks = function(td, axid, skipTitle) { }; } else { - labely = function(d) { return d.dy+d.fontSize/2; }; + flipit = axside==='right' ? 1 : -1; + labely = function(d) { return d.dy+d.fontSize/2 - labelShift * flipit; }; labelx = function(d) { return d.dx + position + (labelStandoff + pad + - (Math.abs(ax.tickangle)===90 ? d.fontSize/2 : 0)) * - (axside==='right' ? 1 : -1); + (Math.abs(ax.tickangle)===90 ? d.fontSize/2 : 0)) * flipit; }; labelanchor = function(angle) { if(isNumeric(angle) && Math.abs(angle)===90) { @@ -1630,8 +1647,8 @@ axes.doTicks = function(td, axid, skipTitle) { var gridcontainer = plotinfo.gridlayer, zlcontainer = plotinfo.zerolinelayer, gridvals = plotinfo['hidegrid'+axletter]?[]:valsClipped, - gridpath = 'M0,0'+((axletter==='x') ? 'v' : 'h') + - counteraxis._length, + gridpath = ax._gridpath || + 'M0,0'+((axletter==='x') ? 'v' : 'h') + counteraxis._length, grid = gridcontainer.selectAll('path.'+gcls) .data(ax.showgrid===false ? [] : gridvals, datafn); grid.enter().append('path').classed(gcls, 1) @@ -1649,31 +1666,38 @@ axes.doTicks = function(td, axid, skipTitle) { grid.exit().remove(); // zero line - var hasBarsOrFill = false; - for(var i = 0; i < td._fullData.length; i++) { - if(traceHasBarsOrFill(td._fullData[i], subplot)) { - hasBarsOrFill = true; - break; + if(zlcontainer) { + var hasBarsOrFill = false; + for(var i = 0; i < td._fullData.length; i++) { + if(traceHasBarsOrFill(td._fullData[i], subplot)) { + hasBarsOrFill = true; + break; + } } + var showZl = (ax.range[0]*ax.range[1]<=0) && ax.zeroline && + (ax.type==='linear' || ax.type==='-') && gridvals.length && + (hasBarsOrFill || clipEnds({x: 0}) || !ax.showline); + var zl = zlcontainer.selectAll('path.'+zcls) + .data(showZl ? [{x: 0}] : []); + zl.enter().append('path').classed(zcls,1).classed('zl',1) + .classed('crisp',1) + .attr('d',gridpath); + zl.attr('transform',transfn) + .call(Plotly.Color.stroke, ax.zerolinecolor || Plotly.Color.defaultLine) + .style('stroke-width', zeroLineWidth+'px'); + zl.exit().remove(); } - var showZl = (ax.range[0]*ax.range[1]<=0) && ax.zeroline && - (ax.type==='linear' || ax.type==='-') && gridvals.length && - (hasBarsOrFill || clipEnds({x: 0}) || !ax.showline); - - var zl = zlcontainer.selectAll('path.'+zcls) - .data(showZl ? [{x: 0}] : []); - zl.enter().append('path').classed(zcls,1).classed('zl',1) - .classed('crisp',1) - .attr('d',gridpath); - zl.attr('transform',transfn) - .call(Plotly.Color.stroke, ax.zerolinecolor || Plotly.Color.defaultLine) - .style('stroke-width', zeroLineWidth+'px'); - zl.exit().remove(); } if(independent) { - drawTicks(ax._axislayer, tickprefix + (ax._pos+pad*ticksign[2]) + - tickmid + (ticksign[2]*ax.ticklen)); + drawTicks(ax._axislayer, tickpathfn(ax._pos+pad*ticksign[2], ticksign[2]*ax.ticklen)); + if(ax._counteraxis) { + var fictionalPlotinfo = { + gridlayer: ax._gridlayer, + zerolinelayer: ax._zerolinelayer + }; + drawGrid(fictionalPlotinfo, ax._counteraxis); + } return drawLabels(ax._axislayer,ax._pos); } else { @@ -1713,8 +1737,7 @@ axes.doTicks = function(td, axid, skipTitle) { var pos = linepositions[sidei], tsign = ticksign[sidei]; if(showside && isNumeric(pos)) { - tickpath += tickprefix + (pos+pad*tsign) + - tickmid + (tsign*ax.ticklen); + tickpath += tickpathfn(pos+pad*tsign, tsign*ax.ticklen); } }); diff --git a/src/plots/plots.js b/src/plots/plots.js index 0635212c18f..c5e6ce65d9f 100644 --- a/src/plots/plots.js +++ b/src/plots/plots.js @@ -635,6 +635,7 @@ plots.supplyDataDefaults = function(traceIn, i, layout) { // differently for 3D cases. coerceSubplotAttr('gl3d', 'scene'); coerceSubplotAttr('geo', 'geo'); + coerceSubplotAttr('ternary', 'subplot'); // module-specific attributes --- note: we need to send a trace into // the 3D modules to have it removed from the webgl context. diff --git a/src/plots/ternary/index.js b/src/plots/ternary/index.js index b33cdd019d3..84bbd38b634 100644 --- a/src/plots/ternary/index.js +++ b/src/plots/ternary/index.js @@ -16,7 +16,7 @@ var Plots = require('../../plots/plots'); exports.name = 'ternary'; -exports.attr = 'ternary'; +exports.attr = 'subplot'; exports.idRoot = 'ternary'; @@ -45,7 +45,7 @@ exports.plot = function plotTernary(gd) { ternary = new Ternary({ id: ternaryId, graphDiv: gd, - container: fullLayout._ternarycontainer.node() + container: fullLayout._ternarylayer.node() }, fullLayout ); @@ -65,7 +65,8 @@ exports.clean = function(newFullData, newFullLayout, oldFullData, oldFullLayout) var oldTernary = oldFullLayout[oldTernaryKey]._ternary; if(!newFullLayout[oldTernaryKey] && !!oldTernary) { - oldTernary.ternaryDiv.remove(); + oldTernary.plotContainer.remove(); + oldFullLayout.clipDef.remove(); } } }; diff --git a/src/plots/ternary/layout/axis_attributes.js b/src/plots/ternary/layout/axis_attributes.js index c2148a9faf8..92c8f95db74 100644 --- a/src/plots/ternary/layout/axis_attributes.js +++ b/src/plots/ternary/layout/axis_attributes.js @@ -18,7 +18,7 @@ module.exports = { titlefont: axesAttrs.titlefont, color: axesAttrs.color, // ticks - nticks: axesAttrs.nticks, + nticks: extendFlat({}, axesAttrs.nticks, {dflt: 6, min: 1}), ticks: axesAttrs.ticks, ticklen: axesAttrs.ticklen, tickwidth: axesAttrs.tickwidth, diff --git a/src/plots/ternary/layout/axis_defaults.js b/src/plots/ternary/layout/axis_defaults.js index 943e473c3d6..80306e60edf 100644 --- a/src/plots/ternary/layout/axis_defaults.js +++ b/src/plots/ternary/layout/axis_defaults.js @@ -10,7 +10,7 @@ 'use strict'; var Lib = require('../../../lib'); -var lightColor = require('../../components/color').lightColor; +var lightColor = require('../../../components/color').lightColor; var layoutAttributes = require('./axis_attributes'); var handleTickMarkDefaults = require('../../cartesian/tick_mark_defaults'); @@ -71,7 +71,9 @@ module.exports = function supplyLayoutDefaults(containerIn, containerOut, option delete containerOut.linewidth; } - var gridColor = coerce2('gridcolor', lightColor(dfltColor, options.bgColor)), + // default grid color is darker here (backFraction 0.6, vs default 0.909) + // because the grid is not square so the eye needs heavier cues to follow + var gridColor = coerce2('gridcolor', lightColor(dfltColor, options.bgColor, 0.6)), gridWidth = coerce2('gridwidth'), showGridLines = coerce('showgrid', !!gridColor || !!gridWidth); diff --git a/src/plots/ternary/layout/defaults.js b/src/plots/ternary/layout/defaults.js index 43ea3d7cd96..047208ec562 100644 --- a/src/plots/ternary/layout/defaults.js +++ b/src/plots/ternary/layout/defaults.js @@ -9,7 +9,7 @@ 'use strict'; -var Color = require('../../components/color'); +var Color = require('../../../components/color'); var handleSubplotDefaults = require('../../subplot_defaults'); var layoutAttributes = require('./layout_attributes'); @@ -31,15 +31,33 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) { function handleTernaryDefaults(ternaryLayoutIn, ternaryLayoutOut, coerce, options) { var bgColor = coerce('bgcolor'); - coerce('sum'); + var sum = coerce('sum'); options.bgColor = Color.combine(bgColor, options.paper_bgcolor); var axName, containerIn, containerOut; + // TODO: allow most (if not all) axis attributes to be set + // in the outer container and used as defaults in the individual axes? + for(var j = 0; j < axesNames.length; j++) { axName = axesNames[j]; containerIn = ternaryLayoutIn[axName] || {}; - containerOut = {_name: axName}; + containerOut = ternaryLayoutOut[axName] = {_name: axName}; handleAxisDefaults(containerIn, containerOut, options); } + + // if the min values contradict each other, set them all to default (0) + // and delete *all* the inputs so the user doesn't get confused later by + // changing one and having them all change. + var aaxis = ternaryLayoutOut.aaxis, + baxis = ternaryLayoutOut.baxis, + caxis = ternaryLayoutOut.caxis; + if(aaxis.min + baxis.min + caxis.min >= sum) { + aaxis.min = 0; + baxis.min = 0; + caxis.min = 0; + if(ternaryLayoutIn.aaxis) delete ternaryLayoutIn.aaxis.min; + if(ternaryLayoutIn.baxis) delete ternaryLayoutIn.baxis.min; + if(ternaryLayoutIn.caxis) delete ternaryLayoutIn.caxis.min; + } } diff --git a/src/plots/ternary/ternary.js b/src/plots/ternary/ternary.js new file mode 100644 index 00000000000..7abccfa74cd --- /dev/null +++ b/src/plots/ternary/ternary.js @@ -0,0 +1,325 @@ +/** +* 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 d3 = require('d3'); +var Color = require('../../components/color'); +var Drawing = require('../../components/drawing'); +var setConvert = require('../cartesian/set_convert'); +var extendFlat = require('../../lib/extend').extendFlat; +var Axes = require('../cartesian/axes'); +var filterVisible = require('../../lib/filter_visible'); + + +function Ternary(options, fullLayout) { + this.id = options.id; + this.graphDiv = options.graphDiv; + this.container = fullLayout._ternarylayer; + this.defs = fullLayout._defs; + this.layoutId = fullLayout._uid; + this.traceHash = {}; + + this.makeFramework(); +} + +module.exports = Ternary; + +var proto = Ternary.prototype; + +proto.plot = function(ternaryData, fullLayout) { + var _this = this, + ternaryLayout = fullLayout[_this.id], + graphSize = fullLayout._size, + i; + + _this.adjustLayout(ternaryLayout, graphSize); + + var traceHashOld = _this.traceHash; + var traceHash = {}; + + for(i = 0; i < ternaryData.length; i++) { + var trace = ternaryData[i]; + + traceHash[trace.type] = traceHash[trace.type] || []; + traceHash[trace.type].push(trace); + } + + var moduleNamesOld = Object.keys(traceHashOld); + var moduleNames = Object.keys(traceHash); + + // when a trace gets deleted, make sure that its module's + // plot method is called so that it is properly + // removed from the DOM. + for(i = 0; i < moduleNamesOld.length; i++) { + var moduleName = moduleNamesOld[i]; + + if(moduleNames.indexOf(moduleName) === -1) { + var fakeModule = traceHashOld[moduleName][0]; + fakeModule.visible = false; + traceHash[moduleName] = [fakeModule]; + } + } + + moduleNames = Object.keys(traceHash); + + for(i = 0; i < moduleNames.length; i++) { + var moduleData = traceHash[moduleNames[i]]; + var _module = moduleData[0]._module; + + _module.plot(_this, filterVisible(moduleData), ternaryLayout); + } + + _this.traceHash = traceHash; + + _this.layers.plotbg.select('path').call(Color.fill, ternaryLayout.bgcolor); +}; + +proto.makeFramework = function() { + var _this = this; + + var defGroup = _this.defs.selectAll('g.clips') + .data([0]); + defGroup.enter().append('g') + .classed('clips', true); + + // clippath for this ternary subplot + var clipId = 'clip' + _this.layoutId + _this.id; + _this.clipDef = defGroup.selectAll('#' + clipId) + .data([0]); + _this.clipDef.enter().append('clipPath').attr('id', clipId) + .append('path').attr('d', 'M0,0Z'); + + // container for everything in this ternary subplot + _this.plotContainer = _this.container.selectAll('g.' + _this.id) + .data([0]); + _this.plotContainer.enter().append('g') + .classed(_this.id, true); + + _this.layers = {}; + + // inside that container, we have one container for the data, and + // one each for the three axes around it. + var plotLayers = [ + 'plotbg', + 'backplot', + 'grids', + 'frontplot', + 'aaxis', 'baxis', 'caxis','axlines' + ]; + var toplevel = _this.plotContainer.selectAll('g.toplevel') + .data(plotLayers); + toplevel.enter().append('g') + .attr('class', function(d) { return 'toplevel ' + d; }) + .each(function(d) { + var s = d3.select(this); + _this.layers[d] = s; + + // containers for different trace types. + // NOTE - this is different from cartesian, where all traces + // are in front of grids. Here I'm putting maps behind the grids + // so the grids will always be visible if they're requested. + // Perhaps we want that for cartesian too? + if(d === 'frontplot') s.append('g').classed('scatterlayer', true); + else if(d === 'backplot') s.append('g').classed('maplayer', true); + else if(d === 'plotbg') s.append('path').attr('d', 'M0,0Z'); + else if(d === 'axlines') { + s.selectAll('path').data(['aline', 'bline', 'cline']) + .enter().append('path').each(function(d) { + d3.select(this).classed(d, true); + }); + } + }); + + var grids = _this.plotContainer.select('.grids').selectAll('g.grid') + .data(['agrid', 'bgrid', 'cgrid']); + grids.enter().append('g') + .attr('class', function(d) { return 'grid ' + d; }) + .each(function(d) { _this.layers[d] = d3.select(this); }); + + _this.plotContainer.selectAll('.backplot,.frontplot,.grids') + .call(Drawing.setClipUrl, clipId); +}; + +var w_over_h = Math.sqrt(4/3); + +proto.adjustLayout = function(ternaryLayout, graphSize) { + var _this = this, + domain = ternaryLayout.domain, + xDomainCenter = (domain.x[0] + domain.x[1]) / 2, + yDomainCenter = (domain.y[0] + domain.y[1]) / 2, + xDomain = domain.x[1] - domain.x[0], + yDomain = domain.y[1] - domain.y[0], + wmax = xDomain * graphSize.w, + hmax = yDomain * graphSize.h, + sum = ternaryLayout.sum; + + var x0, y0, w, h, xDomainFinal, yDomainFinal; + + if(wmax > w_over_h * hmax) { + h = hmax; + w = h * w_over_h; + } + else { + w = wmax; + h = w / w_over_h; + } + + xDomainFinal = xDomain * w / wmax; + yDomainFinal = yDomain * h / hmax; + + x0 = graphSize.l + graphSize.w * xDomainCenter - w / 2; + y0 = graphSize.t + graphSize.h * yDomainCenter - h / 2; + + _this.x0 = x0; + _this.y0 = y0; + _this.w = w; + _this.h = h; + + // set up the x and y axis objects we'll use to lay out the points + _this.xaxis = { + type: 'linear', + range: [-sum, sum], + domain: [ + xDomainCenter - xDomainFinal / 2, + xDomainCenter + xDomainFinal / 2 + ], + _id: 'x', + _td: _this.graphDiv + }; + setConvert(_this.xaxis); + _this.xaxis.setScale(); + + _this.yaxis = { + type: 'linear', + range: [0, sum], + domain: [ + yDomainCenter - yDomainFinal / 2, + yDomainCenter + yDomainFinal / 2 + ], + _id: 'y', + _td: _this.graphDiv + }; + setConvert(_this.yaxis); + _this.yaxis.setScale(); + + // set up the modified axes for tick drawing + var amin = ternaryLayout.aaxis.min, + bmin = ternaryLayout.baxis.min, + cmin = ternaryLayout.caxis.min, + yDomain0 = _this.yaxis.domain[0]; + + // aaxis goes up the left side. Set it up as a y axis, but with + // fictitious angles and domain, but then rotate and translate + // it into place at the end + var aaxis = _this.aaxis = extendFlat({}, ternaryLayout.aaxis, { + range: [amin, sum - bmin - cmin], + side: 'left', + _counterangle: 30, + // tickangle = 'auto' means 0 anyway for a y axis, need to coerce to 0 here + // so we can shift by 30. + tickangle: (+ternaryLayout.aaxis.tickangle || 0) - 30, + domain: [yDomain0, yDomain0 + yDomainFinal * w_over_h], + _axislayer: _this.layers.aaxis, + _gridlayer: _this.layers.agrid, + _pos: 0, //_this.xaxis.domain[0] * graphSize.w, + _td: _this.graphDiv, + _id: 'y', + _length: w, + _gridpath: 'M0,0l' + h + ',-' + (w / 2) + }); + setConvert(aaxis); + + // baxis goes across the bottom (backward). We can set it up as an x axis + // without any enclosing transformation. + var baxis = _this.baxis = extendFlat({}, ternaryLayout.baxis, { + range: [sum - amin - cmin, bmin], + side: 'bottom', + _counterangle: 30, + domain: _this.xaxis.domain, + _axislayer: _this.layers.baxis, + _gridlayer: _this.layers.bgrid, + _counteraxis: _this.aaxis, + _pos: 0, //(1 - yDomain0) * graphSize.h, + _td: _this.graphDiv, + _id: 'x', + _length: w, + _gridpath: 'M0,0l-' + (w / 2) + ',-' + h + }); + setConvert(baxis); + aaxis._counteraxis = baxis; + + // caxis goes down the right side. Set it up as a y axis, with + // post-transformation similar to aaxis + var caxis = _this.caxis = extendFlat({}, ternaryLayout.caxis, { + range: [sum - amin - bmin, cmin], + side: 'right', + _counterangle: 30, + tickangle: (+ternaryLayout.caxis.tickangle || 0) + 30, + domain: [yDomain0, yDomain0 + yDomainFinal * w_over_h], + _axislayer: _this.layers.caxis, + _gridlayer: _this.layers.cgrid, + _counteraxis: _this.baxis, + _pos: 0, //_this.xaxis.domain[1] * graphSize.w, + _td: _this.graphDiv, + _id: 'y', + _length: w, + _gridpath: 'M0,0l-' + h + ',' + (w / 2) + }); + setConvert(caxis); + + var triangleClip = 'M' + x0 + ',' + (y0 + h) + 'h' + w + 'l-' + (w/2) + ',-' + h + 'Z'; + _this.clipDef.select('path').attr('d', triangleClip); + _this.layers.plotbg.select('path').attr('d', triangleClip); + + var plotTransform = 'translate(' + x0 + ',' + y0 + ')'; + _this.plotContainer.selectAll('.scatterlayer,.maplayer') + .attr('transform', plotTransform); + + // TODO: shift axes to accommodate linewidth*sin(30) tick mark angle + + var bTransform = 'translate(' + x0 + ',' + (y0 + h) + ')'; + + _this.layers.baxis.attr('transform', bTransform); + _this.layers.bgrid.attr('transform', bTransform); + + var aTransform = 'translate(' + (x0 + w / 2) + ',' + y0 + ')rotate(30)'; + _this.layers.aaxis.attr('transform', aTransform); + _this.layers.agrid.attr('transform', aTransform); + + var cTransform = 'translate(' + (x0 + w / 2) + ',' + y0 + ')rotate(-30)'; + _this.layers.caxis.attr('transform', cTransform); + _this.layers.cgrid.attr('transform', cTransform); + + // TODO: titles (3rd arg true below ignores them) + Axes.doTicks(_this.graphDiv, aaxis, true); + Axes.doTicks(_this.graphDiv, baxis, true); + Axes.doTicks(_this.graphDiv, caxis, true); + + // remove crispEdges - all the off-square angles in ternary plots + // make these counterproductive. + _this.plotContainer.selectAll('.crisp').classed('crisp', false); + + var axlines = _this.layers.axlines; + axlines.select('.aline') + .attr('d', aaxis.showline ? + 'M' + x0 + ',' + (y0 + h) + 'l' + (w / 2) + ',-' + h : 'M0,0') + .call(Color.stroke, aaxis.linecolor || '#000') + .style('stroke-width', (aaxis.linewidth || 0) + 'px'); + axlines.select('.bline') + .attr('d', baxis.showline ? + 'M' + x0 + ',' + (y0 + h) + 'h' + w : 'M0,0') + .call(Color.stroke, baxis.linecolor || '#000') + .style('stroke-width', (baxis.linewidth || 0) + 'px'); + axlines.select('.cline') + .attr('d', caxis.showline ? + 'M' + (x0 + w / 2) + ',' + y0 + 'l' + (w / 2) + ',' + h : 'M0,0') + .call(Color.stroke, caxis.linecolor || '#000') + .style('stroke-width', (caxis.linewidth || 0) + 'px'); +}; diff --git a/src/traces/scatterternary/calc.js b/src/traces/scatterternary/calc.js index 606b74612c1..89b46f65fd4 100644 --- a/src/traces/scatterternary/calc.js +++ b/src/traces/scatterternary/calc.js @@ -14,7 +14,7 @@ var isNumeric = require('fast-isnumeric'); var Axes = require('../../plots/cartesian/axes'); var Lib = require('../../lib'); -var subTypes = require('./subtypes'); +var subTypes = require('../scatter/subtypes'); var calcMarkerColorscale = require('../scatter/marker_colorscale_calc'); var dataArrays = ['a', 'b', 'c']; @@ -31,10 +31,10 @@ module.exports = function calc(gd, trace) { // fill in one missing component for(i = 0; i < dataArrays.length; i++) { dataArray = dataArrays[i]; - if(trace[dataArray] && trace[dataArray.length]) continue; + if(trace[dataArray] && trace[dataArray].length) continue; - fillArray1 = arraysToFill[dataArray][0]; - fillArray2 = arraysToFill[dataArray][1]; + fillArray1 = trace[arraysToFill[dataArray][0]]; + fillArray2 = trace[arraysToFill[dataArray][1]]; newArray = new Array(fillArray1.length); for(j = 0; j < fillArray1.length; j++) { newArray[j] = normSum - fillArray1[j] - fillArray2[j]; diff --git a/src/traces/scatterternary/hover.js b/src/traces/scatterternary/hover.js new file mode 100644 index 00000000000..2cc3f1426cc --- /dev/null +++ b/src/traces/scatterternary/hover.js @@ -0,0 +1,27 @@ +/** +* 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 scatterHover = require('../scatter/hover'); + + +module.exports = function hoverPoints(pointData, xval, yval, hovermode) { + var scatterPointData = scatterHover(pointData, xval, yval, hovermode); + if(!scatterPointData || scatterPointData[0].index === false) return; + + var newPointData = scatterPointData[0], + cdi = newPointData.cd[newPointData.index]; + + newPointData.a = cdi.a; + newPointData.b = cdi.b; + newPointData.c = cdi.c; + + // TODO: how to actually display a, b, and c? +}; diff --git a/src/traces/scatterternary/index.js b/src/traces/scatterternary/index.js index fa75cad106f..5fd1953cf50 100644 --- a/src/traces/scatterternary/index.js +++ b/src/traces/scatterternary/index.js @@ -14,7 +14,7 @@ ScatterTernary.attributes = require('./attributes'); ScatterTernary.supplyDefaults = require('./defaults'); ScatterTernary.colorbar = require('../scatter/colorbar'); ScatterTernary.calc = require('./calc'); -ScatterTernary.plot = require('./plot').plot; +ScatterTernary.plot = require('./plot'); ScatterTernary.style = require('./style'); ScatterTernary.hoverPoints = require('./hover'); ScatterTernary.selectPoints = require('./select'); @@ -26,7 +26,7 @@ ScatterTernary.categories = ['ternary', 'symbols', 'markerColorscale', 'showLege ScatterTernary.meta = { hrName: 'scatter_ternary', description: [ - 'Provides similar functionality to the *scatter* type but on a ternary plot.', + 'Provides similar functionality to the *scatter* type but on a ternary phase diagram.', 'The data is provided by at least two arrays out of `a`, `b`, `c` triplets.' ].join(' ') }; diff --git a/src/traces/scatterternary/plot.js b/src/traces/scatterternary/plot.js new file mode 100644 index 00000000000..f7996576563 --- /dev/null +++ b/src/traces/scatterternary/plot.js @@ -0,0 +1,31 @@ +/** +* 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 scatterPlot = require('../scatter/plot'); + + +module.exports = function plot(ternary, data) { + // mimic cartesian plotinfo + var plotinfo = { + x: function() { return ternary.xaxis; }, + y: function() { return ternary.yaxis; }, + plot: ternary.plotContainer + }; + + var calcdata = new Array(data.length), + fullCalcdata = ternary.graphDiv.calcdata, + i, j; + for(i = 0; i < fullCalcdata.length; i++) { + j = data.indexOf(fullCalcdata[i][0].trace); + if(j !== -1) calcdata[j] = fullCalcdata[i]; + } + scatterPlot(ternary.graphDiv, plotinfo, calcdata); +}; diff --git a/src/traces/scatterternary/select.js b/src/traces/scatterternary/select.js new file mode 100644 index 00000000000..226e3c15e1a --- /dev/null +++ b/src/traces/scatterternary/select.js @@ -0,0 +1,31 @@ +/** +* 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 scatterSelect = require('../scatter/select'); + + +module.exports = function selectPoints(searchInfo, polygon) { + var selection = scatterSelect(searchInfo, polygon); + if(!selection) return; + + var cd = searchInfo.cd, + pt, cdi, i; + + for(i = 0; i < selection.length; i++) { + pt = selection[i]; + cdi = cd[pt.pointNumber]; + pt.a = cdi.a; + pt.b = cdi.b; + pt.c = cdi.c; + } + + return selection; +}; diff --git a/src/traces/scatterternary/style.js b/src/traces/scatterternary/style.js new file mode 100644 index 00000000000..7b92f4d3200 --- /dev/null +++ b/src/traces/scatterternary/style.js @@ -0,0 +1,24 @@ +/** +* 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 scatterStyle = require('../scatter/style'); + + +module.exports = function style(graphDiv) { + for(var i = 0; i < graphDiv._modules.length; i++) { + // we're just going to call scatter style... if we already + // called it, don't need to redo. + // Later though we may want differences, or we may make style + // more specific in its scope, then we can remove this. + if(graphDiv._modules[i].name === 'scatter') return; + } + scatterStyle(graphDiv); +}; From 3f998908ae57af198cb33b9df1cb0d38a47b08c2 Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Wed, 30 Mar 2016 03:10:37 +0200 Subject: [PATCH 15/76] ignore errorbars with scatterternary --- src/components/errorbars/plot.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/errorbars/plot.js b/src/components/errorbars/plot.js index 0df1b4060b8..49e20445f25 100644 --- a/src/components/errorbars/plot.js +++ b/src/components/errorbars/plot.js @@ -22,8 +22,8 @@ module.exports = function plot(traces, plotinfo) { traces.each(function(d) { var trace = d[0].trace, - xObj = trace.error_x, - yObj = trace.error_y; + xObj = trace.error_x || {}, + yObj = trace.error_y || {}; var sparse = ( subTypes.hasMarkers(trace) && From dc5cb63832d97b4f49723c75693d81d93ae6704e Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Wed, 30 Mar 2016 12:34:17 +0200 Subject: [PATCH 16/76] fix colorbar defaults bug and update tests --- src/components/colorbar/defaults.js | 2 +- test/jasmine/tests/plotschema_test.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/colorbar/defaults.js b/src/components/colorbar/defaults.js index c3b36748bde..117e8457272 100644 --- a/src/components/colorbar/defaults.js +++ b/src/components/colorbar/defaults.js @@ -53,7 +53,7 @@ module.exports = function colorbarDefaults(containerIn, containerOut, layout) { handleTickValueDefaults(colorbarIn, colorbarOut, coerce, 'linear'); - handleTickLabelDefaults(colorbarIn, colorbarOut, coerce, + handleTickLabelDefaults(colorbarIn, colorbarOut, coerce, 'linear', {outerTicks: false, font: layout.font, noHover: true}); handleTickMarkDefaults(colorbarIn, colorbarOut, coerce, 'linear', diff --git a/test/jasmine/tests/plotschema_test.js b/test/jasmine/tests/plotschema_test.js index 9e7a9fcfc0d..197c2485d0c 100644 --- a/test/jasmine/tests/plotschema_test.js +++ b/test/jasmine/tests/plotschema_test.js @@ -90,7 +90,7 @@ describe('plot schema', function() { it('all subplot objects should contain _isSubplotObj', function() { var IS_SUBPLOT_OBJ = '_isSubplotObj', - astrs = ['xaxis', 'yaxis', 'scene', 'geo'], + astrs = ['xaxis', 'yaxis', 'scene', 'geo', 'ternary'], list = []; // check if the subplot objects have '_isSubplotObj' From f43fe6405190bb60fe83b5fd831209cc539461d3 Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Wed, 30 Mar 2016 12:35:15 +0200 Subject: [PATCH 17/76] break up graph_interact --- src/components/annotations/index.js | 16 +- src/components/colorbar/draw.js | 17 +- src/components/dragelement/align.js | 31 + src/components/dragelement/cursor.js | 33 + src/components/dragelement/index.js | 167 +++++ src/components/dragelement/unhover.js | 43 ++ src/components/legend/draw.js | 17 +- src/components/modebar/buttons.js | 3 +- src/lib/setcursor.js | 20 + src/plots/cartesian/dragbox.js | 652 +++++++++++++++++++ src/plots/cartesian/graph_interact.js | 863 +------------------------- src/plots/ternary/ternary.js | 1 + 12 files changed, 983 insertions(+), 880 deletions(-) create mode 100644 src/components/dragelement/align.js create mode 100644 src/components/dragelement/cursor.js create mode 100644 src/components/dragelement/index.js create mode 100644 src/components/dragelement/unhover.js create mode 100644 src/lib/setcursor.js create mode 100644 src/plots/cartesian/dragbox.js diff --git a/src/components/annotations/index.js b/src/components/annotations/index.js index f033d3265f7..520b7b78083 100644 --- a/src/components/annotations/index.js +++ b/src/components/annotations/index.js @@ -12,6 +12,8 @@ var Plotly = require('../../plotly'); var d3 = require('d3'); var isNumeric = require('fast-isnumeric'); +var setCursor = require('../../lib/setcursor'); +var dragElement = require('../dragelement'); var annotations = module.exports = {}; @@ -583,7 +585,7 @@ annotations.draw = function(gd, index, opt, value) { annx0, anny0; - Plotly.Fx.dragElement({ + dragElement.init({ element: arrowdrag.node(), prepFn: function() { annx0 = Number(ann.attr('x')); @@ -641,7 +643,7 @@ annotations.draw = function(gd, index, opt, value) { y0, update; - Plotly.Fx.dragElement({ + dragElement.init({ element: ann.node(), prepFn: function() { x0 = Number(ann.attr('x')); @@ -662,7 +664,7 @@ annotations.draw = function(gd, index, opt, value) { var widthFraction = options._xsize / gs.w, xLeft = options.x + options._xshift / gs.w - widthFraction / 2; - update[annbase + '.x'] = Plotly.Fx.dragAlign(xLeft + dx / gs.w, + update[annbase + '.x'] = dragElement.align(xLeft + dx / gs.w, widthFraction, 0, 1, options.xanchor); } @@ -671,11 +673,11 @@ annotations.draw = function(gd, index, opt, value) { var heightFraction = options._ysize / gs.h, yBottom = options.y - options._yshift / gs.h - heightFraction / 2; - update[annbase + '.y'] = Plotly.Fx.dragAlign(yBottom - dy / gs.h, + update[annbase + '.y'] = dragElement.align(yBottom - dy / gs.h, heightFraction, 0, 1, options.yanchor); } if(!xa || !ya) { - csr = Plotly.Fx.dragCursors( + csr = dragElement.cursor( xa ? 0.5 : update[annbase + '.x'], ya ? 0.5 : update[annbase + '.y'], options.xanchor, options.yanchor @@ -694,10 +696,10 @@ annotations.draw = function(gd, index, opt, value) { x1 + ',' + y1 + ')' }); - Plotly.Fx.setCursor(ann, csr); + setCursor(ann, csr); }, doneFn: function(dragged) { - Plotly.Fx.setCursor(ann); + setCursor(ann); if(dragged) { Plotly.relayout(gd, update); var notesBox = document.querySelector('.js-notes-box-panel'); diff --git a/src/components/colorbar/draw.js b/src/components/colorbar/draw.js index 8dc4545accf..20fc11fe224 100644 --- a/src/components/colorbar/draw.js +++ b/src/components/colorbar/draw.js @@ -14,8 +14,9 @@ var d3 = require('d3'); var Plotly = require('../../plotly'); var Plots = require('../../plots/plots'); var Axes = require('../../plots/cartesian/axes'); -var Fx = require('../../plots/cartesian/graph_interact'); +var dragElement = require('../dragelement'); var Lib = require('../../lib'); +var setCursor = require('../../lib/setcursor'); var Drawing = require('../drawing'); var Color = require('../color'); var Titles = require('../titles'); @@ -462,11 +463,11 @@ module.exports = function draw(gd, id) { xf, yf; - Fx.dragElement({ + dragElement.init({ element: container.node(), prepFn: function() { t0 = container.attr('transform'); - Fx.setCursor(container); + setCursor(container); }, moveFn: function(dx, dy) { var gs = gd._fullLayout._size; @@ -474,17 +475,17 @@ module.exports = function draw(gd, id) { container.attr('transform', t0+' ' + 'translate('+dx+','+dy+')'); - xf = Fx.dragAlign(xLeftFrac + (dx/gs.w), thickFrac, + xf = dragElement.align(xLeftFrac + (dx/gs.w), thickFrac, 0, 1, opts.xanchor); - yf = Fx.dragAlign(yBottomFrac - (dy/gs.h), lenFrac, + yf = dragElement.align(yBottomFrac - (dy/gs.h), lenFrac, 0, 1, opts.yanchor); - var csr = Fx.dragCursors(xf, yf, + var csr = dragElement.cursor(xf, yf, opts.xanchor, opts.yanchor); - Fx.setCursor(container, csr); + setCursor(container, csr); }, doneFn: function(dragged) { - Fx.setCursor(container); + setCursor(container); if(dragged && xf!==undefined && yf!==undefined) { var idNum = id.substr(2), diff --git a/src/components/dragelement/align.js b/src/components/dragelement/align.js new file mode 100644 index 00000000000..66e5c8f681f --- /dev/null +++ b/src/components/dragelement/align.js @@ -0,0 +1,31 @@ +/** +* 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'; + + +// for automatic alignment on dragging, <1/3 means left align, +// >2/3 means right, and between is center. Pick the right fraction +// based on where you are, and return the fraction corresponding to +// that position on the object +module.exports = function align(v, dv, v0, v1, anchor) { + var vmin = (v - v0) / (v1 - v0), + vmax = vmin + dv / (v1 - v0), + vc = (vmin + vmax) / 2; + + // explicitly specified anchor + if(anchor === 'left' || anchor === 'bottom') return vmin; + if(anchor === 'center' || anchor === 'middle') return vc; + if(anchor === 'right' || anchor === 'top') return vmax; + + // automatic based on position + if(vmin < (2/3) - vc) return vmin; + if(vmax > (4/3) - vc) return vmax; + return vc; +}; diff --git a/src/components/dragelement/cursor.js b/src/components/dragelement/cursor.js new file mode 100644 index 00000000000..08cbfab29c3 --- /dev/null +++ b/src/components/dragelement/cursor.js @@ -0,0 +1,33 @@ +/** +* 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 Lib = require('../../lib'); + + +// set cursors pointing toward the closest corner/side, +// to indicate alignment +// x and y are 0-1, fractions of the plot area +var cursorset = [['sw-resize','s-resize','se-resize'], + ['w-resize','move','e-resize'], + ['nw-resize','n-resize','ne-resize']]; +module.exports = function getCursor(x,y,xanchor,yanchor) { + if(xanchor==='left') x=0; + else if(xanchor==='center') x=1; + else if(xanchor==='right') x=2; + else x = Lib.constrain(Math.floor(x*3),0,2); + + if(yanchor==='bottom') y=0; + else if(yanchor==='middle') y=1; + else if(yanchor==='top') y=2; + else y = Lib.constrain(Math.floor(y*3),0,2); + + return cursorset[y][x]; +}; diff --git a/src/components/dragelement/index.js b/src/components/dragelement/index.js new file mode 100644 index 00000000000..6c441beff3b --- /dev/null +++ b/src/components/dragelement/index.js @@ -0,0 +1,167 @@ +/** +* 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 Plotly = require('../../plotly'); +var Lib = require('../../lib'); + +var constants = require('../../plots/cartesian/constants'); + + +/** + * Abstracts click & drag interactions + * @param {object} options with keys: + * element (required) the DOM element to drag + * prepFn (optional) function(event, startX, startY) + * executed on mousedown + * startX and startY are the clientX and clientY pixel position + * of the mousedown event + * moveFn (optional) function(dx, dy, dragged) + * executed on move + * dx and dy are the net pixel offset of the drag, + * dragged is true/false, has the mouse moved enough to + * constitute a drag + * doneFn (optional) function(dragged, numClicks) + * executed on mouseup, or mouseout of window since + * we don't get events after that + * dragged is as in moveFn + * numClicks is how many clicks we've registered within + * a doubleclick time + */ +var dragElement = module.exports = {}; + +dragElement.align = require('./align'); +dragElement.getCursor = require('./cursor'); + +var unhover = require('./unhover'); +dragElement.unhover = unhover.wrapped; +dragElement.unhover_raw = unhover.raw; + +dragElement.init = function init(options) { + var gd = Lib.getPlotDiv(options.element) || {}, + numClicks = 1, + DBLCLICKDELAY = constants.DBLCLICKDELAY, + startX, + startY, + newMouseDownTime, + dragCover, + initialTarget; + + if(!gd._mouseDownTime) gd._mouseDownTime = 0; + + function onStart(e) { + // make dragging and dragged into properties of gd + // so that others can look at and modify them + gd._dragged = false; + gd._dragging = true; + startX = e.clientX; + startY = e.clientY; + initialTarget = e.target; + + newMouseDownTime = (new Date()).getTime(); + if(newMouseDownTime - gd._mouseDownTime < DBLCLICKDELAY) { + // in a click train + numClicks += 1; + } + else { + // new click train + numClicks = 1; + gd._mouseDownTime = newMouseDownTime; + } + + if(options.prepFn) options.prepFn(e, startX, startY); + + dragCover = coverSlip(); + + dragCover.onmousemove = onMove; + dragCover.onmouseup = onDone; + dragCover.onmouseout = onDone; + + dragCover.style.cursor = window.getComputedStyle(options.element).cursor; + + return Lib.pauseEvent(e); + } + + function onMove(e) { + var dx = e.clientX - startX, + dy = e.clientY - startY, + minDrag = options.minDrag || constants.MINDRAG; + + if(Math.abs(dx) < minDrag) dx = 0; + if(Math.abs(dy) < minDrag) dy = 0; + if(dx||dy) { + gd._dragged = true; + dragElement.unhover(gd); + } + + if(options.moveFn) options.moveFn(dx, dy, gd._dragged); + + return Lib.pauseEvent(e); + } + + function onDone(e) { + dragCover.onmousemove = null; + dragCover.onmouseup = null; + dragCover.onmouseout = null; + Lib.removeElement(dragCover); + + if(!gd._dragging) { + gd._dragged = false; + return; + } + gd._dragging = false; + + // don't count as a dblClick unless the mouseUp is also within + // the dblclick delay + if((new Date()).getTime() - gd._mouseDownTime > DBLCLICKDELAY) { + numClicks = Math.max(numClicks - 1, 1); + } + + if(options.doneFn) options.doneFn(gd._dragged, numClicks); + + if(!gd._dragged) { + var e2 = document.createEvent('MouseEvents'); + e2.initEvent('click', true, true); + initialTarget.dispatchEvent(e2); + } + + finishDrag(gd); + + gd._dragged = false; + + return Lib.pauseEvent(e); + } + + options.element.onmousedown = onStart; + options.element.style.pointerEvents = 'all'; +}; + +function coverSlip() { + var cover = document.createElement('div'); + + cover.className = 'dragcover'; + var cStyle = cover.style; + cStyle.position = 'fixed'; + cStyle.left = 0; + cStyle.right = 0; + cStyle.top = 0; + cStyle.bottom = 0; + cStyle.zIndex = 999999999; + cStyle.background = 'none'; + + document.body.appendChild(cover); + + return cover; +} + +function finishDrag(gd) { + gd._dragging = false; + if(gd._replotPending) Plotly.plot(gd); +} diff --git a/src/components/dragelement/unhover.js b/src/components/dragelement/unhover.js new file mode 100644 index 00000000000..975c4c51596 --- /dev/null +++ b/src/components/dragelement/unhover.js @@ -0,0 +1,43 @@ +/** +* 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 Events = require('../../lib/events'); + + +var unhover = module.exports = {}; + + +unhover.wrapped = function(gd, evt, subplot) { + if(typeof gd === 'string') gd = document.getElementById(gd); + // Important, clear any queued hovers + if(gd._hoverTimer) { + clearTimeout(gd._hoverTimer); + gd._hoverTimer = undefined; + } + unhover.raw(gd,evt,subplot); +}; + + +// remove hover effects on mouse out, and emit unhover event +unhover.raw = function unhover_raw(gd, evt) { + var fullLayout = gd._fullLayout; + if(!evt) evt = {}; + if(evt.target && + Events.triggerHandler(gd, 'plotly_beforehover', evt) === false) { + return; + } + fullLayout._hoverlayer.selectAll('g').remove(); + if(evt.target && gd._hoverdata) { + gd.emit('plotly_unhover', {points: gd._hoverdata}); + } + gd._hoverdata = undefined; +}; diff --git a/src/components/legend/draw.js b/src/components/legend/draw.js index 8fe9f62d35f..bc27e222055 100644 --- a/src/components/legend/draw.js +++ b/src/components/legend/draw.js @@ -13,8 +13,9 @@ var d3 = require('d3'); var Plotly = require('../../plotly'); var Lib = require('../../lib'); +var setCursor = require('../../lib/setcursor'); var Plots = require('../../plots/plots'); -var Fx = require('../../plots/cartesian/graph_interact'); +var dragElement = require('../dragelement'); var Drawing = require('../drawing'); var Color = require('../color'); @@ -288,31 +289,31 @@ module.exports = function draw(gd) { lw, lh; - Fx.dragElement({ + dragElement.init({ element: legend.node(), prepFn: function() { x0 = Number(legend.attr('x')); y0 = Number(legend.attr('y')); lw = Number(legend.attr('width')); lh = Number(legend.attr('height')); - Fx.setCursor(legend); + setCursor(legend); }, moveFn: function(dx, dy) { var gs = gd._fullLayout._size; legend.call(Drawing.setPosition, x0+dx, y0+dy); - xf = Fx.dragAlign(x0+dx, lw, gs.l, gs.l+gs.w, + xf = dragElement.align(x0+dx, lw, gs.l, gs.l+gs.w, opts.xanchor); - yf = Fx.dragAlign(y0+dy+lh, -lh, gs.t+gs.h, gs.t, + yf = dragElement.align(y0+dy+lh, -lh, gs.t+gs.h, gs.t, opts.yanchor); - var csr = Fx.dragCursors(xf, yf, + var csr = dragElement.cursor(xf, yf, opts.xanchor, opts.yanchor); - Fx.setCursor(legend, csr); + setCursor(legend, csr); }, doneFn: function(dragged) { - Fx.setCursor(legend); + setCursor(legend); if(dragged && xf !== undefined && yf !== undefined) { Plotly.relayout(gd, {'legend.x': xf, 'legend.y': yf}); } diff --git a/src/components/modebar/buttons.js b/src/components/modebar/buttons.js index 67d118fc3b9..e3e2a6c03c7 100644 --- a/src/components/modebar/buttons.js +++ b/src/components/modebar/buttons.js @@ -11,6 +11,7 @@ var Plotly = require('../../plotly'); var Lib = require('../../lib'); +var setCursor = require('../../lib/setcursor'); var Snapshot = require('../../snapshot'); var Icons = require('../../../build/ploticon'); @@ -253,7 +254,7 @@ function handleCartesian(gd, ev) { Plotly.relayout(gd, aobj).then(function() { if(astr === 'dragmode') { if(fullLayout._hasCartesian) { - Plotly.Fx.setCursor( + setCursor( fullLayout._paper.select('.nsewdrag'), DRAGCURSORS[val] ); diff --git a/src/lib/setcursor.js b/src/lib/setcursor.js new file mode 100644 index 00000000000..ae37d367cfd --- /dev/null +++ b/src/lib/setcursor.js @@ -0,0 +1,20 @@ +/** +* 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'; + +// works with our CSS cursor classes (see css/_cursor.scss) +// to apply cursors to d3 single-element selections. +// omit cursor to revert to the default. +module.exports = function setCursor(el3, csr) { + (el3.attr('class') || '').split(' ').forEach(function(cls) { + if(cls.indexOf('cursor-')===0) el3.classed(cls,false); + }); + if(csr) el3.classed('cursor-'+csr, true); +}; diff --git a/src/plots/cartesian/dragbox.js b/src/plots/cartesian/dragbox.js new file mode 100644 index 00000000000..2fa9bccd37c --- /dev/null +++ b/src/plots/cartesian/dragbox.js @@ -0,0 +1,652 @@ +/** +* 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 d3 = require('d3'); +var tinycolor = require('tinycolor2'); + +var Plotly = require('../../plotly'); +var setCursor = require('../../lib/setcursor'); +var dragElement = require('../../components/dragelement'); + +var prepSelect = require('./select'); +var constants = require('./constants'); + + +// flag for showing "doubleclick to zoom out" only at the beginning +var SHOWZOOMOUTTIP = true; + +// dragBox: create an element to drag one or more axis ends +// inputs: +// plotinfo - which subplot are we making dragboxes on? +// x,y,w,h - left, top, width, height of the box +// ns - how does this drag the vertical axis? +// 'n' - top only +// 's' - bottom only +// 'ns' - top and bottom together, difference unchanged +// ew - same for horizontal axis +module.exports = function dragBox(gd, plotinfo, x, y, w, h, ns, ew) { + // mouseDown stores ms of first mousedown event in the last + // DBLCLICKDELAY ms on the drag bars + // numClicks stores how many mousedowns have been seen + // within DBLCLICKDELAY so we can check for click or doubleclick events + // dragged stores whether a drag has occurred, so we don't have to + // redraw unnecessarily, ie if no move bigger than MINDRAG or MINZOOM px + var fullLayout = gd._fullLayout, + // if we're dragging two axes at once, also drag overlays + subplots = [plotinfo].concat((ns && ew) ? plotinfo.overlays : []), + xa = [plotinfo.x()], + ya = [plotinfo.y()], + pw = xa[0]._length, + ph = ya[0]._length, + MINDRAG = constants.MINDRAG, + MINZOOM = constants.MINZOOM, + i, + subplotXa, + subplotYa; + + for(i = 1; i < subplots.length; i++) { + subplotXa = subplots[i].x(); + subplotYa = subplots[i].y(); + if(xa.indexOf(subplotXa) === -1) xa.push(subplotXa); + if(ya.indexOf(subplotYa) === -1) ya.push(subplotYa); + } + + function isDirectionActive(axList, activeVal) { + for(i = 0; i < axList.length; i++) { + if(!axList[i].fixedrange) return activeVal; + } + return ''; + } + + var allaxes = xa.concat(ya), + xActive = isDirectionActive(xa, ew), + yActive = isDirectionActive(ya, ns), + cursor = getDragCursor(yActive + xActive, fullLayout.dragmode), + dragClass = ns + ew + 'drag'; + + var dragger3 = plotinfo.draglayer.selectAll('.' + dragClass).data([0]); + dragger3.enter().append('rect') + .classed('drag', true) + .classed(dragClass, true) + .style({fill: 'transparent', 'stroke-width': 0}) + .attr('data-subplot', plotinfo.id); + dragger3.call(Plotly.Drawing.setRect, x, y, w, h) + .call(setCursor,cursor); + var dragger = dragger3.node(); + + // still need to make the element if the axes are disabled + // but nuke its events (except for maindrag which needs them for hover) + // and stop there + if(!yActive && !xActive) { + dragger.onmousedown = null; + dragger.style.pointerEvents = (ns + ew === 'nsew') ? 'all' : 'none'; + return dragger; + } + + function forceNumbers(axRange) { + axRange[0] = Number(axRange[0]); + axRange[1] = Number(axRange[1]); + } + + var dragOptions = { + element: dragger, + gd: gd, + plotinfo: plotinfo, + xaxes: xa, + yaxes: ya, + doubleclick: doubleClick, + prepFn: function(e, startX, startY) { + var dragModeNow = gd._fullLayout.dragmode; + if(ns + ew === 'nsew') { + // main dragger handles all drag modes, and changes + // to pan (or to zoom if it already is pan) on shift + if(e.shiftKey) { + if(dragModeNow === 'pan') dragModeNow = 'zoom'; + else dragModeNow = 'pan'; + } + } + // all other draggers just pan + else dragModeNow = 'pan'; + + if(dragModeNow === 'lasso') dragOptions.minDrag = 1; + else dragOptions.minDrag = undefined; + + if(dragModeNow === 'zoom') { + dragOptions.moveFn = zoomMove; + dragOptions.doneFn = zoomDone; + zoomPrep(e, startX, startY); + } + else if(dragModeNow === 'pan') { + dragOptions.moveFn = plotDrag; + dragOptions.doneFn = dragDone; + clearSelect(); + } + else if(dragModeNow === 'select' || dragModeNow === 'lasso') { + prepSelect(e, startX, startY, dragOptions, dragModeNow); + } + } + }; + + dragElement.init(dragOptions); + + var x0, + y0, + box, + lum, + path0, + dimmed, + zoomMode, + zb, + corners; + + function zoomPrep(e, startX, startY) { + var dragBBox = dragger.getBoundingClientRect(); + x0 = startX - dragBBox.left; + y0 = startY - dragBBox.top; + box = {l: x0, r: x0, w: 0, t: y0, b: y0, h: 0}; + lum = gd._hmpixcount ? + (gd._hmlumcount / gd._hmpixcount) : + tinycolor(gd._fullLayout.plot_bgcolor).getLuminance(); + path0 = path0 = 'M0,0H'+pw+'V'+ph+'H0V0'; + dimmed = false; + zoomMode = 'xy'; + + zb = plotinfo.plot.append('path') + .attr('class', 'zoombox') + .style({ + 'fill': lum>0.2 ? 'rgba(0,0,0,0)' : 'rgba(255,255,255,0)', + 'stroke-width': 0 + }) + .attr('d', path0 + 'Z'); + + corners = plotinfo.plot.append('path') + .attr('class', 'zoombox-corners') + .style({ + fill: Plotly.Color.background, + stroke: Plotly.Color.defaultLine, + 'stroke-width': 1, + opacity: 0 + }) + .attr('d','M0,0Z'); + + clearSelect(); + for(i = 0; i < allaxes.length; i++) forceNumbers(allaxes[i].range); + } + + function clearSelect() { + // until we get around to persistent selections, remove the outline + // here. The selection itself will be removed when the plot redraws + // at the end. + plotinfo.plot.selectAll('.select-outline').remove(); + } + + function zoomMove(dx0, dy0) { + var x1 = Math.max(0, Math.min(pw, dx0 + x0)), + y1 = Math.max(0, Math.min(ph, dy0 + y0)), + dx = Math.abs(x1 - x0), + dy = Math.abs(y1 - y0), + clen = Math.floor(Math.min(dy, dx, MINZOOM) / 2); + + box.l = Math.min(x0, x1); + box.r = Math.max(x0, x1); + box.t = Math.min(y0, y1); + box.b = Math.max(y0, y1); + + // look for small drags in one direction or the other, + // and only drag the other axis + if(!yActive || dy < Math.min(Math.max(dx * 0.6, MINDRAG), MINZOOM)) { + if(dx < MINDRAG) { + zoomMode = ''; + box.r = box.l; + box.t = box.b; + corners.attr('d', 'M0,0Z'); + } + else { + box.t = 0; + box.b = ph; + zoomMode = 'x'; + corners.attr('d', + 'M' + (box.l - 0.5) + ',' + (y0 - MINZOOM - 0.5) + + 'h-3v' + (2 * MINZOOM + 1) + 'h3ZM' + + (box.r + 0.5) + ',' + (y0 - MINZOOM - 0.5) + + 'h3v' + (2 * MINZOOM + 1) + 'h-3Z'); + } + } + else if(!xActive || dx < Math.min(dy * 0.6, MINZOOM)) { + box.l = 0; + box.r = pw; + zoomMode = 'y'; + corners.attr('d', + 'M' + (x0 - MINZOOM - 0.5) + ',' + (box.t - 0.5) + + 'v-3h' + (2 * MINZOOM + 1) + 'v3ZM' + + (x0 - MINZOOM - 0.5) + ',' + (box.b + 0.5) + + 'v3h' + (2 * MINZOOM + 1) + 'v-3Z'); + } + else { + zoomMode = 'xy'; + corners.attr('d', + 'M'+(box.l-3.5)+','+(box.t-0.5+clen)+'h3v'+(-clen)+ + 'h'+clen+'v-3h-'+(clen+3)+'ZM'+ + (box.r+3.5)+','+(box.t-0.5+clen)+'h-3v'+(-clen)+ + 'h'+(-clen)+'v-3h'+(clen+3)+'ZM'+ + (box.r+3.5)+','+(box.b+0.5-clen)+'h-3v'+clen+ + 'h'+(-clen)+'v3h'+(clen+3)+'ZM'+ + (box.l-3.5)+','+(box.b+0.5-clen)+'h3v'+clen+ + 'h'+clen+'v3h-'+(clen+3)+'Z'); + } + box.w = box.r - box.l; + box.h = box.b - box.t; + + // Not sure about the addition of window.scrollX/Y... + // seems to work but doesn't seem robust. + zb.attr('d', + path0+'M'+(box.l)+','+(box.t)+'v'+(box.h)+ + 'h'+(box.w)+'v-'+(box.h)+'h-'+(box.w)+'Z'); + if(!dimmed) { + zb.transition() + .style('fill', lum>0.2 ? 'rgba(0,0,0,0.4)' : + 'rgba(255,255,255,0.3)') + .duration(200); + corners.transition() + .style('opacity',1) + .duration(200); + dimmed = true; + } + } + + function zoomAxRanges(axList, r0Fraction, r1Fraction) { + var i, + axi, + axRange; + + for(i = 0; i < axList.length; i++) { + axi = axList[i]; + if(axi.fixedrange) continue; + + axRange = axi.range; + axi.range = [ + axRange[0] + (axRange[1] - axRange[0]) * r0Fraction, + axRange[0] + (axRange[1] - axRange[0]) * r1Fraction + ]; + } + } + + function zoomDone(dragged, numClicks) { + if(Math.min(box.h, box.w) < MINDRAG * 2) { + if(numClicks === 2) doubleClick(); + + return removeZoombox(gd); + } + + if(zoomMode === 'xy' || zoomMode === 'x') zoomAxRanges(xa, box.l / pw, box.r / pw); + if(zoomMode === 'xy' || zoomMode === 'y') zoomAxRanges(ya, (ph - box.b) / ph, (ph - box.t) / ph); + + removeZoombox(gd); + dragTail(zoomMode); + + if(SHOWZOOMOUTTIP && gd.data && gd._context.showTips) { + Plotly.Lib.notifier('Double-click to
zoom back out','long'); + SHOWZOOMOUTTIP = false; + } + } + + function dragDone(dragged, numClicks) { + var singleEnd = (ns + ew).length === 1; + if(dragged) dragTail(); + else if(numClicks === 2 && !singleEnd) doubleClick(); + else if(numClicks === 1 && singleEnd) { + var ax = ns ? ya[0] : xa[0], + end = (ns==='s' || ew==='w') ? 0 : 1, + attrStr = ax._name + '.range[' + end + ']', + initialText = getEndText(ax, end), + hAlign = 'left', + vAlign = 'middle'; + + if(ax.fixedrange) return; + + if(ns) { + vAlign = (ns === 'n') ? 'top' : 'bottom'; + if(ax.side === 'right') hAlign = 'right'; + } + else if(ew === 'e') hAlign = 'right'; + + dragger3 + .call(Plotly.util.makeEditable, null, { + immediate: true, + background: fullLayout.paper_bgcolor, + text: String(initialText), + fill: ax.tickfont ? ax.tickfont.color : '#444', + horizontalAlign: hAlign, + verticalAlign: vAlign + }) + .on('edit', function(text) { + var v = ax.type==='category' ? ax.c2l(text) : ax.d2l(text); + if(v !== undefined) { + Plotly.relayout(gd, attrStr, v); + } + }); + } + } + + // scroll zoom, on all draggers except corners + var scrollViewBox = [0,0,pw,ph], + // wait a little after scrolling before redrawing + redrawTimer = null, + REDRAWDELAY = 300, + mainplot = plotinfo.mainplot ? + fullLayout._plots[plotinfo.mainplot] : plotinfo; + + function zoomWheel(e) { + // deactivate mousewheel scrolling on embedded graphs + // devs can override this with layout._enablescrollzoom, + // but _ ensures this setting won't leave their page + if(!gd._context.scrollZoom && !fullLayout._enablescrollzoom) { + return; + } + var pc = gd.querySelector('.plotly'); + + // if the plot has scrollbars (more than a tiny excess) + // disable scrollzoom too. + if(pc.scrollHeight-pc.clientHeight>10 || + pc.scrollWidth-pc.clientWidth>10) { + return; + } + + clearTimeout(redrawTimer); + + var wheelDelta = -e.deltaY; + if(!isFinite(wheelDelta)) wheelDelta = e.wheelDelta / 10; + if(!isFinite(wheelDelta)) { + console.log('did not find wheel motion attributes', e); + return; + } + + var zoom = Math.exp(-Math.min(Math.max(wheelDelta, -20), 20) / 100), + gbb = mainplot.draglayer.select('.nsewdrag') + .node().getBoundingClientRect(), + xfrac = (e.clientX - gbb.left) / gbb.width, + vbx0 = scrollViewBox[0] + scrollViewBox[2]*xfrac, + yfrac = (gbb.bottom - e.clientY)/gbb.height, + vby0 = scrollViewBox[1]+scrollViewBox[3]*(1-yfrac), + i; + + function zoomWheelOneAxis(ax, centerFraction, zoom) { + if(ax.fixedrange) return; + forceNumbers(ax.range); + var axRange = ax.range, + v0 = axRange[0] + (axRange[1] - axRange[0]) * centerFraction; + ax.range = [v0 + (axRange[0] - v0) * zoom, v0 + (axRange[1] - v0) * zoom]; + } + + if(ew) { + for(i = 0; i < xa.length; i++) zoomWheelOneAxis(xa[i], xfrac, zoom); + scrollViewBox[2] *= zoom; + scrollViewBox[0] = vbx0 - scrollViewBox[2] * xfrac; + } + if(ns) { + for(i = 0; i < ya.length; i++) zoomWheelOneAxis(ya[i], yfrac, zoom); + scrollViewBox[3] *= zoom; + scrollViewBox[1] = vby0 - scrollViewBox[3] * (1 - yfrac); + } + + // viewbox redraw at first + updateViewBoxes(scrollViewBox); + ticksAndAnnotations(ns,ew); + + // then replot after a delay to make sure + // no more scrolling is coming + redrawTimer = setTimeout(function() { + scrollViewBox = [0,0,pw,ph]; + dragTail(); + }, REDRAWDELAY); + + return Plotly.Lib.pauseEvent(e); + } + + // everything but the corners gets wheel zoom + if(ns.length*ew.length!==1) { + // still seems to be some confusion about onwheel vs onmousewheel... + if(dragger.onwheel!==undefined) dragger.onwheel = zoomWheel; + else if(dragger.onmousewheel!==undefined) dragger.onmousewheel = zoomWheel; + } + + // plotDrag: move the plot in response to a drag + function plotDrag(dx,dy) { + function dragAxList(axList, pix) { + for(var i = 0; i < axList.length; i++) { + var axi = axList[i]; + if(!axi.fixedrange) { + axi.range = [axi._r[0] - pix / axi._m, axi._r[1] - pix / axi._m]; + } + } + } + + if(xActive === 'ew' || yActive === 'ns') { + if(xActive) dragAxList(xa, dx); + if(yActive) dragAxList(ya, dy); + updateViewBoxes([xActive ? -dx : 0, yActive ? -dy : 0, pw, ph]); + ticksAndAnnotations(yActive, xActive); + return; + } + + // common transform for dragging one end of an axis + // d>0 is compressing scale (cursor is over the plot, + // the axis end should move with the cursor) + // d<0 is expanding (cursor is off the plot, axis end moves + // nonlinearly so you can expand far) + function dZoom(d) { + return 1-((d>=0) ? Math.min(d,0.9) : + 1/(1/Math.max(d,-0.3)+3.222)); + } + + // dz: set a new value for one end (0 or 1) of an axis array ax, + // and return a pixel shift for that end for the viewbox + // based on pixel drag distance d + // TODO: this makes (generally non-fatal) errors when you get + // near floating point limits + function dz(ax, end, d) { + var otherEnd = 1 - end, + movedi = 0; + for(var i = 0; i < ax.length; i++) { + var axi = ax[i]; + if(axi.fixedrange) continue; + movedi = i; + axi.range[end] = axi._r[otherEnd] + + (axi._r[end] - axi._r[otherEnd]) / dZoom(d / axi._length); + } + return ax[movedi]._length * (ax[movedi]._r[end] - ax[movedi].range[end]) / + (ax[movedi]._r[end] - ax[movedi]._r[otherEnd]); + } + + if(xActive === 'w') dx = dz(xa, 0, dx); + else if(xActive === 'e') dx = dz(xa, 1, -dx); + else if(!xActive) dx = 0; + + if(yActive === 'n') dy = dz(ya, 1, dy); + else if(yActive === 's') dy = dz(ya, 0, -dy); + else if(!yActive) dy = 0; + + updateViewBoxes([ + (xActive === 'w') ? dx : 0, + (yActive === 'n') ? dy : 0, + pw - dx, + ph - dy + ]); + ticksAndAnnotations(yActive, xActive); + } + + function ticksAndAnnotations(ns, ew) { + var activeAxIds = [], + i; + + function pushActiveAxIds(axList) { + for(i = 0; i < axList.length; i++) { + if(!axList[i].fixedrange) activeAxIds.push(axList[i]._id); + } + } + + if(ew) pushActiveAxIds(xa); + if(ns) pushActiveAxIds(ya); + + for(i = 0; i < activeAxIds.length; i++) { + Plotly.Axes.doTicks(gd, activeAxIds[i], true); + } + + function redrawObjs(objArray, module) { + var obji; + for(i = 0; i < objArray.length; i++) { + obji = objArray[i]; + if((ew && activeAxIds.indexOf(obji.xref) !== -1) || + (ns && activeAxIds.indexOf(obji.yref) !== -1)) { + module.draw(gd, i); + } + } + } + + redrawObjs(fullLayout.annotations || [], Plotly.Annotations); + redrawObjs(fullLayout.shapes || [], Plotly.Shapes); + } + + function doubleClick() { + var doubleClickConfig = gd._context.doubleClick, + axList = (xActive ? xa : []).concat(yActive ? ya : []), + attrs = {}; + + var ax, i; + + if(doubleClickConfig === 'autosize') { + for(i = 0; i < axList.length; i++) { + ax = axList[i]; + if(!ax.fixedrange) attrs[ax._name + '.autorange'] = true; + } + } + else if(doubleClickConfig === 'reset') { + for(i = 0; i < axList.length; i++) { + ax = axList[i]; + + if(!ax._rangeInitial) { + attrs[ax._name + '.autorange'] = true; + } + else { + attrs[ax._name + '.range'] = ax._rangeInitial.slice(); + } + } + } + else if(doubleClickConfig === 'reset+autosize') { + for(i = 0; i < axList.length; i++) { + ax = axList[i]; + + if(ax.fixedrange) continue; + if(ax._rangeInitial === undefined || + ax.range[0] === ax._rangeInitial[0] && + ax.range[1] === ax._rangeInitial[1] + ) { + attrs[ax._name + '.autorange'] = true; + } + else attrs[ax._name + '.range'] = ax._rangeInitial.slice(); + } + } + + gd.emit('plotly_doubleclick', null); + Plotly.relayout(gd, attrs); + } + + // dragTail - finish a drag event with a redraw + function dragTail(zoommode) { + var attrs = {}; + // revert to the previous axis settings, then apply the new ones + // through relayout - this lets relayout manage undo/redo + for(var i = 0; i < allaxes.length; i++) { + var axi = allaxes[i]; + if(zoommode && zoommode.indexOf(axi._id.charAt(0))===-1) { + continue; + } + if(axi._r[0] !== axi.range[0]) attrs[axi._name+'.range[0]'] = axi.range[0]; + if(axi._r[1] !== axi.range[1]) attrs[axi._name+'.range[1]'] = axi.range[1]; + + axi.range=axi._r.slice(); + } + + updateViewBoxes([0,0,pw,ph]); + Plotly.relayout(gd,attrs); + } + + // updateViewBoxes - find all plot viewboxes that should be + // affected by this drag, and update them. look for all plots + // sharing an affected axis (including the one being dragged) + function updateViewBoxes(viewBox) { + var plotinfos = fullLayout._plots, + subplots = Object.keys(plotinfos), + i, + plotinfo2, + xa2, + ya2, + editX, + editY; + + for(i = 0; i < subplots.length; i++) { + plotinfo2 = plotinfos[subplots[i]]; + xa2 = plotinfo2.x(); + ya2 = plotinfo2.y(); + editX = ew && xa.indexOf(xa2)!==-1 && !xa2.fixedrange; + editY = ns && ya.indexOf(ya2)!==-1 && !ya2.fixedrange; + + if(editX || editY) { + var newVB = [0,0,xa2._length,ya2._length]; + if(editX) { + newVB[0] = viewBox[0]; + newVB[2] = viewBox[2]; + } + if(editY) { + newVB[1] = viewBox[1]; + newVB[3] = viewBox[3]; + } + plotinfo2.plot.attr('viewBox',newVB.join(' ')); + } + } + } + + return dragger; +}; + +function getEndText(ax, end) { + var initialVal = ax.range[end], + diff = Math.abs(initialVal - ax.range[1 - end]), + dig; + + if(ax.type === 'date') { + return Plotly.Lib.ms2DateTime(initialVal, diff); + } + else if(ax.type==='log') { + dig = Math.ceil(Math.max(0, -Math.log(diff) / Math.LN10)) + 3; + return d3.format('.' + dig + 'g')(Math.pow(10, initialVal)); + } + else { // linear numeric (or category... but just show numbers here) + dig = Math.floor(Math.log(Math.abs(initialVal)) / Math.LN10) - + Math.floor(Math.log(diff) / Math.LN10) + 4; + return d3.format('.'+String(dig)+'g')(initialVal); + } +} + +function getDragCursor(nsew, dragmode) { + if(!nsew) return 'pointer'; + if(nsew === 'nsew') { + if(dragmode === 'pan') return 'move'; + return 'crosshair'; + } + return nsew.toLowerCase() + '-resize'; +} + +function removeZoombox(gd) { + d3.select(gd) + .selectAll('.zoombox,.js-zoombox-backdrop,.js-zoombox-menu,.zoombox-corners') + .remove(); +} diff --git a/src/plots/cartesian/graph_interact.js b/src/plots/cartesian/graph_interact.js index 19c428761fa..68363debb77 100644 --- a/src/plots/cartesian/graph_interact.js +++ b/src/plots/cartesian/graph_interact.js @@ -17,8 +17,9 @@ var Plotly = require('../../plotly'); var Lib = require('../../lib'); var Events = require('../../lib/events'); -var prepSelect = require('./select'); var constants = require('./constants'); +var dragBox = require('./dragbox'); +var dragElement = require('../../components/dragElement'); var fx = module.exports = {}; @@ -137,7 +138,7 @@ fx.init = function(gd) { maindrag.onmouseout = function(evt) { if(gd._dragging) return; - fx.unhover(gd, evt); + dragElement.unhover(gd, evt); }; maindrag.onclick = function(evt) { @@ -289,16 +290,6 @@ fx.hover = function(gd, evt, subplot) { }, constants.HOVERMINTIME); }; -fx.unhover = function(gd, evt, subplot) { - if(typeof gd === 'string') gd = document.getElementById(gd); - // Important, clear any queued hovers - if(gd._hoverTimer) { - clearTimeout(gd._hoverTimer); - gd._hoverTimer = undefined; - } - unhover(gd,evt,subplot); -}; - // The actual implementation is here: function hover(gd, evt, subplot) { @@ -331,7 +322,7 @@ function hover(gd, evt, subplot) { if(['x','y','closest'].indexOf(hovermode)===-1 || !gd.calcdata || gd.querySelector('.zoombox') || gd._dragging) { - return unhover(gd, evt); + return dragElement.unhover_raw(gd, evt); } // hoverData: the set of candidate points we've found to highlight @@ -403,7 +394,7 @@ function hover(gd, evt, subplot) { // in case hover was called from mouseout into hovertext, // it's possible you're not actually over the plot anymore if(xpx<0 || xpx>dbb.width || ypx<0 || ypx>dbb.height) { - return unhover(gd,evt); + return dragElement.unhover_raw(gd,evt); } } else { @@ -422,7 +413,7 @@ function hover(gd, evt, subplot) { if(!isNumeric(xvalArray[0]) || !isNumeric(yvalArray[0])) { console.log('Plotly.Fx.hover failed', evt, gd); - return unhover(gd, evt); + return dragElement.unhover_raw(gd, evt); } } @@ -521,7 +512,7 @@ function hover(gd, evt, subplot) { } // nothing left: remove all labels and quit - if(hoverData.length===0) return unhover(gd,evt); + if(hoverData.length===0) return dragElement.unhover_raw(gd,evt); // if there's more than one horz bar trace, // rotate the labels so they don't overlap @@ -1276,21 +1267,6 @@ function hoverChanged(gd, evt, oldhoverdata) { return false; } -// remove hover effects on mouse out, and emit unhover event -function unhover(gd, evt) { - var fullLayout = gd._fullLayout; - if(!evt) evt = {}; - if(evt.target && - Events.triggerHandler(gd, 'plotly_beforehover', evt) === false) { - return; - } - fullLayout._hoverlayer.selectAll('g').remove(); - if(evt.target && gd._hoverdata) { - gd.emit('plotly_unhover', {points: gd._hoverdata}); - } - gd._hoverdata = undefined; -} - // on click fx.click = function(gd,evt) { if(gd._hoverdata && evt && evt.target) { @@ -1301,831 +1277,6 @@ fx.click = function(gd,evt) { }; -// ---------------------------------------------------- -// Axis dragging functions -// ---------------------------------------------------- - -function getDragCursor(nsew, dragmode) { - if(!nsew) return 'pointer'; - if(nsew === 'nsew') { - if(dragmode === 'pan') return 'move'; - return 'crosshair'; - } - return nsew.toLowerCase() + '-resize'; -} - -// flag for showing "doubleclick to zoom out" only at the beginning -var SHOWZOOMOUTTIP = true; - -// dragBox: create an element to drag one or more axis ends -// inputs: -// plotinfo - which subplot are we making dragboxes on? -// x,y,w,h - left, top, width, height of the box -// ns - how does this drag the vertical axis? -// 'n' - top only -// 's' - bottom only -// 'ns' - top and bottom together, difference unchanged -// ew - same for horizontal axis -function dragBox(gd, plotinfo, x, y, w, h, ns, ew) { - // mouseDown stores ms of first mousedown event in the last - // DBLCLICKDELAY ms on the drag bars - // numClicks stores how many mousedowns have been seen - // within DBLCLICKDELAY so we can check for click or doubleclick events - // dragged stores whether a drag has occurred, so we don't have to - // redraw unnecessarily, ie if no move bigger than MINDRAG or MINZOOM px - var fullLayout = gd._fullLayout, - // if we're dragging two axes at once, also drag overlays - subplots = [plotinfo].concat((ns && ew) ? plotinfo.overlays : []), - xa = [plotinfo.x()], - ya = [plotinfo.y()], - pw = xa[0]._length, - ph = ya[0]._length, - MINDRAG = constants.MINDRAG, - MINZOOM = constants.MINZOOM, - i, - subplotXa, - subplotYa; - - for(i = 1; i < subplots.length; i++) { - subplotXa = subplots[i].x(); - subplotYa = subplots[i].y(); - if(xa.indexOf(subplotXa) === -1) xa.push(subplotXa); - if(ya.indexOf(subplotYa) === -1) ya.push(subplotYa); - } - - function isDirectionActive(axList, activeVal) { - for(i = 0; i < axList.length; i++) { - if(!axList[i].fixedrange) return activeVal; - } - return ''; - } - - var allaxes = xa.concat(ya), - xActive = isDirectionActive(xa, ew), - yActive = isDirectionActive(ya, ns), - cursor = getDragCursor(yActive + xActive, fullLayout.dragmode), - dragClass = ns + ew + 'drag'; - - var dragger3 = plotinfo.draglayer.selectAll('.' + dragClass).data([0]); - dragger3.enter().append('rect') - .classed('drag', true) - .classed(dragClass, true) - .style({fill: 'transparent', 'stroke-width': 0}) - .attr('data-subplot', plotinfo.id); - dragger3.call(Plotly.Drawing.setRect, x, y, w, h) - .call(fx.setCursor,cursor); - var dragger = dragger3.node(); - - // still need to make the element if the axes are disabled - // but nuke its events (except for maindrag which needs them for hover) - // and stop there - if(!yActive && !xActive) { - dragger.onmousedown = null; - dragger.style.pointerEvents = (ns + ew === 'nsew') ? 'all' : 'none'; - return dragger; - } - - function forceNumbers(axRange) { - axRange[0] = Number(axRange[0]); - axRange[1] = Number(axRange[1]); - } - - var dragOptions = { - element: dragger, - gd: gd, - plotinfo: plotinfo, - xaxes: xa, - yaxes: ya, - doubleclick: doubleClick, - prepFn: function(e, startX, startY) { - var dragModeNow = gd._fullLayout.dragmode; - if(ns + ew === 'nsew') { - // main dragger handles all drag modes, and changes - // to pan (or to zoom if it already is pan) on shift - if(e.shiftKey) { - if(dragModeNow === 'pan') dragModeNow = 'zoom'; - else dragModeNow = 'pan'; - } - } - // all other draggers just pan - else dragModeNow = 'pan'; - - if(dragModeNow === 'lasso') dragOptions.minDrag = 1; - else dragOptions.minDrag = undefined; - - if(dragModeNow === 'zoom') { - dragOptions.moveFn = zoomMove; - dragOptions.doneFn = zoomDone; - zoomPrep(e, startX, startY); - } - else if(dragModeNow === 'pan') { - dragOptions.moveFn = plotDrag; - dragOptions.doneFn = dragDone; - clearSelect(); - } - else if(dragModeNow === 'select' || dragModeNow === 'lasso') { - prepSelect(e, startX, startY, dragOptions, dragModeNow); - } - } - }; - - fx.dragElement(dragOptions); - - var x0, - y0, - box, - lum, - path0, - dimmed, - zoomMode, - zb, - corners; - - function zoomPrep(e, startX, startY) { - var dragBBox = dragger.getBoundingClientRect(); - x0 = startX - dragBBox.left; - y0 = startY - dragBBox.top; - box = {l: x0, r: x0, w: 0, t: y0, b: y0, h: 0}; - lum = gd._hmpixcount ? - (gd._hmlumcount / gd._hmpixcount) : - tinycolor(gd._fullLayout.plot_bgcolor).getLuminance(); - path0 = path0 = 'M0,0H'+pw+'V'+ph+'H0V0'; - dimmed = false; - zoomMode = 'xy'; - - zb = plotinfo.plot.append('path') - .attr('class', 'zoombox') - .style({ - 'fill': lum>0.2 ? 'rgba(0,0,0,0)' : 'rgba(255,255,255,0)', - 'stroke-width': 0 - }) - .attr('d', path0 + 'Z'); - - corners = plotinfo.plot.append('path') - .attr('class', 'zoombox-corners') - .style({ - fill: Plotly.Color.background, - stroke: Plotly.Color.defaultLine, - 'stroke-width': 1, - opacity: 0 - }) - .attr('d','M0,0Z'); - - clearSelect(); - for(i = 0; i < allaxes.length; i++) forceNumbers(allaxes[i].range); - } - - function clearSelect() { - // until we get around to persistent selections, remove the outline - // here. The selection itself will be removed when the plot redraws - // at the end. - plotinfo.plot.selectAll('.select-outline').remove(); - } - - function zoomMove(dx0, dy0) { - var x1 = Math.max(0, Math.min(pw, dx0 + x0)), - y1 = Math.max(0, Math.min(ph, dy0 + y0)), - dx = Math.abs(x1 - x0), - dy = Math.abs(y1 - y0), - clen = Math.floor(Math.min(dy, dx, MINZOOM) / 2); - - box.l = Math.min(x0, x1); - box.r = Math.max(x0, x1); - box.t = Math.min(y0, y1); - box.b = Math.max(y0, y1); - - // look for small drags in one direction or the other, - // and only drag the other axis - if(!yActive || dy < Math.min(Math.max(dx * 0.6, MINDRAG), MINZOOM)) { - if(dx < MINDRAG) { - zoomMode = ''; - box.r = box.l; - box.t = box.b; - corners.attr('d', 'M0,0Z'); - } - else { - box.t = 0; - box.b = ph; - zoomMode = 'x'; - corners.attr('d', - 'M' + (box.l - 0.5) + ',' + (y0 - MINZOOM - 0.5) + - 'h-3v' + (2 * MINZOOM + 1) + 'h3ZM' + - (box.r + 0.5) + ',' + (y0 - MINZOOM - 0.5) + - 'h3v' + (2 * MINZOOM + 1) + 'h-3Z'); - } - } - else if(!xActive || dx < Math.min(dy * 0.6, MINZOOM)) { - box.l = 0; - box.r = pw; - zoomMode = 'y'; - corners.attr('d', - 'M' + (x0 - MINZOOM - 0.5) + ',' + (box.t - 0.5) + - 'v-3h' + (2 * MINZOOM + 1) + 'v3ZM' + - (x0 - MINZOOM - 0.5) + ',' + (box.b + 0.5) + - 'v3h' + (2 * MINZOOM + 1) + 'v-3Z'); - } - else { - zoomMode = 'xy'; - corners.attr('d', - 'M'+(box.l-3.5)+','+(box.t-0.5+clen)+'h3v'+(-clen)+ - 'h'+clen+'v-3h-'+(clen+3)+'ZM'+ - (box.r+3.5)+','+(box.t-0.5+clen)+'h-3v'+(-clen)+ - 'h'+(-clen)+'v-3h'+(clen+3)+'ZM'+ - (box.r+3.5)+','+(box.b+0.5-clen)+'h-3v'+clen+ - 'h'+(-clen)+'v3h'+(clen+3)+'ZM'+ - (box.l-3.5)+','+(box.b+0.5-clen)+'h3v'+clen+ - 'h'+clen+'v3h-'+(clen+3)+'Z'); - } - box.w = box.r - box.l; - box.h = box.b - box.t; - - // Not sure about the addition of window.scrollX/Y... - // seems to work but doesn't seem robust. - zb.attr('d', - path0+'M'+(box.l)+','+(box.t)+'v'+(box.h)+ - 'h'+(box.w)+'v-'+(box.h)+'h-'+(box.w)+'Z'); - if(!dimmed) { - zb.transition() - .style('fill', lum>0.2 ? 'rgba(0,0,0,0.4)' : - 'rgba(255,255,255,0.3)') - .duration(200); - corners.transition() - .style('opacity',1) - .duration(200); - dimmed = true; - } - } - - function zoomAxRanges(axList, r0Fraction, r1Fraction) { - var i, - axi, - axRange; - - for(i = 0; i < axList.length; i++) { - axi = axList[i]; - if(axi.fixedrange) continue; - - axRange = axi.range; - axi.range = [ - axRange[0] + (axRange[1] - axRange[0]) * r0Fraction, - axRange[0] + (axRange[1] - axRange[0]) * r1Fraction - ]; - } - } - - function zoomDone(dragged, numClicks) { - if(Math.min(box.h, box.w) < MINDRAG * 2) { - if(numClicks === 2) doubleClick(); - - return removeZoombox(gd); - } - - if(zoomMode === 'xy' || zoomMode === 'x') zoomAxRanges(xa, box.l / pw, box.r / pw); - if(zoomMode === 'xy' || zoomMode === 'y') zoomAxRanges(ya, (ph - box.b) / ph, (ph - box.t) / ph); - - removeZoombox(gd); - dragTail(zoomMode); - - if(SHOWZOOMOUTTIP && gd.data && gd._context.showTips) { - Plotly.Lib.notifier('Double-click to
zoom back out','long'); - SHOWZOOMOUTTIP = false; - } - } - - function dragDone(dragged, numClicks) { - var singleEnd = (ns + ew).length === 1; - if(dragged) dragTail(); - else if(numClicks === 2 && !singleEnd) doubleClick(); - else if(numClicks === 1 && singleEnd) { - var ax = ns ? ya[0] : xa[0], - end = (ns==='s' || ew==='w') ? 0 : 1, - attrStr = ax._name + '.range[' + end + ']', - initialText = getEndText(ax, end), - hAlign = 'left', - vAlign = 'middle'; - - if(ax.fixedrange) return; - - if(ns) { - vAlign = (ns === 'n') ? 'top' : 'bottom'; - if(ax.side === 'right') hAlign = 'right'; - } - else if(ew === 'e') hAlign = 'right'; - - dragger3 - .call(Plotly.util.makeEditable, null, { - immediate: true, - background: fullLayout.paper_bgcolor, - text: String(initialText), - fill: ax.tickfont ? ax.tickfont.color : '#444', - horizontalAlign: hAlign, - verticalAlign: vAlign - }) - .on('edit', function(text) { - var v = ax.type==='category' ? ax.c2l(text) : ax.d2l(text); - if(v !== undefined) { - Plotly.relayout(gd, attrStr, v); - } - }); - } - } - - // scroll zoom, on all draggers except corners - var scrollViewBox = [0,0,pw,ph], - // wait a little after scrolling before redrawing - redrawTimer = null, - REDRAWDELAY = 300, - mainplot = plotinfo.mainplot ? - fullLayout._plots[plotinfo.mainplot] : plotinfo; - - function zoomWheel(e) { - // deactivate mousewheel scrolling on embedded graphs - // devs can override this with layout._enablescrollzoom, - // but _ ensures this setting won't leave their page - if(!gd._context.scrollZoom && !fullLayout._enablescrollzoom) { - return; - } - var pc = gd.querySelector('.plotly'); - - // if the plot has scrollbars (more than a tiny excess) - // disable scrollzoom too. - if(pc.scrollHeight-pc.clientHeight>10 || - pc.scrollWidth-pc.clientWidth>10) { - return; - } - - clearTimeout(redrawTimer); - - var wheelDelta = -e.deltaY; - if(!isFinite(wheelDelta)) wheelDelta = e.wheelDelta / 10; - if(!isFinite(wheelDelta)) { - console.log('did not find wheel motion attributes', e); - return; - } - - var zoom = Math.exp(-Math.min(Math.max(wheelDelta, -20), 20) / 100), - gbb = mainplot.draglayer.select('.nsewdrag') - .node().getBoundingClientRect(), - xfrac = (e.clientX - gbb.left) / gbb.width, - vbx0 = scrollViewBox[0] + scrollViewBox[2]*xfrac, - yfrac = (gbb.bottom - e.clientY)/gbb.height, - vby0 = scrollViewBox[1]+scrollViewBox[3]*(1-yfrac), - i; - - function zoomWheelOneAxis(ax, centerFraction, zoom) { - if(ax.fixedrange) return; - forceNumbers(ax.range); - var axRange = ax.range, - v0 = axRange[0] + (axRange[1] - axRange[0]) * centerFraction; - ax.range = [v0 + (axRange[0] - v0) * zoom, v0 + (axRange[1] - v0) * zoom]; - } - - if(ew) { - for(i = 0; i < xa.length; i++) zoomWheelOneAxis(xa[i], xfrac, zoom); - scrollViewBox[2] *= zoom; - scrollViewBox[0] = vbx0 - scrollViewBox[2] * xfrac; - } - if(ns) { - for(i = 0; i < ya.length; i++) zoomWheelOneAxis(ya[i], yfrac, zoom); - scrollViewBox[3] *= zoom; - scrollViewBox[1] = vby0 - scrollViewBox[3] * (1 - yfrac); - } - - // viewbox redraw at first - updateViewBoxes(scrollViewBox); - ticksAndAnnotations(ns,ew); - - // then replot after a delay to make sure - // no more scrolling is coming - redrawTimer = setTimeout(function() { - scrollViewBox = [0,0,pw,ph]; - dragTail(); - }, REDRAWDELAY); - - return Plotly.Lib.pauseEvent(e); - } - - // everything but the corners gets wheel zoom - if(ns.length*ew.length!==1) { - // still seems to be some confusion about onwheel vs onmousewheel... - if(dragger.onwheel!==undefined) dragger.onwheel = zoomWheel; - else if(dragger.onmousewheel!==undefined) dragger.onmousewheel = zoomWheel; - } - - // plotDrag: move the plot in response to a drag - function plotDrag(dx,dy) { - function dragAxList(axList, pix) { - for(var i = 0; i < axList.length; i++) { - var axi = axList[i]; - if(!axi.fixedrange) { - axi.range = [axi._r[0] - pix / axi._m, axi._r[1] - pix / axi._m]; - } - } - } - - if(xActive === 'ew' || yActive === 'ns') { - if(xActive) dragAxList(xa, dx); - if(yActive) dragAxList(ya, dy); - updateViewBoxes([xActive ? -dx : 0, yActive ? -dy : 0, pw, ph]); - ticksAndAnnotations(yActive, xActive); - return; - } - - // common transform for dragging one end of an axis - // d>0 is compressing scale (cursor is over the plot, - // the axis end should move with the cursor) - // d<0 is expanding (cursor is off the plot, axis end moves - // nonlinearly so you can expand far) - function dZoom(d) { - return 1-((d>=0) ? Math.min(d,0.9) : - 1/(1/Math.max(d,-0.3)+3.222)); - } - - // dz: set a new value for one end (0 or 1) of an axis array ax, - // and return a pixel shift for that end for the viewbox - // based on pixel drag distance d - // TODO: this makes (generally non-fatal) errors when you get - // near floating point limits - function dz(ax, end, d) { - var otherEnd = 1 - end, - movedi = 0; - for(var i = 0; i < ax.length; i++) { - var axi = ax[i]; - if(axi.fixedrange) continue; - movedi = i; - axi.range[end] = axi._r[otherEnd] + - (axi._r[end] - axi._r[otherEnd]) / dZoom(d / axi._length); - } - return ax[movedi]._length * (ax[movedi]._r[end] - ax[movedi].range[end]) / - (ax[movedi]._r[end] - ax[movedi]._r[otherEnd]); - } - - if(xActive === 'w') dx = dz(xa, 0, dx); - else if(xActive === 'e') dx = dz(xa, 1, -dx); - else if(!xActive) dx = 0; - - if(yActive === 'n') dy = dz(ya, 1, dy); - else if(yActive === 's') dy = dz(ya, 0, -dy); - else if(!yActive) dy = 0; - - updateViewBoxes([ - (xActive === 'w') ? dx : 0, - (yActive === 'n') ? dy : 0, - pw - dx, - ph - dy - ]); - ticksAndAnnotations(yActive, xActive); - } - - function ticksAndAnnotations(ns, ew) { - var activeAxIds = [], - i; - - function pushActiveAxIds(axList) { - for(i = 0; i < axList.length; i++) { - if(!axList[i].fixedrange) activeAxIds.push(axList[i]._id); - } - } - - if(ew) pushActiveAxIds(xa); - if(ns) pushActiveAxIds(ya); - - for(i = 0; i < activeAxIds.length; i++) { - Plotly.Axes.doTicks(gd, activeAxIds[i], true); - } - - function redrawObjs(objArray, module) { - var obji; - for(i = 0; i < objArray.length; i++) { - obji = objArray[i]; - if((ew && activeAxIds.indexOf(obji.xref) !== -1) || - (ns && activeAxIds.indexOf(obji.yref) !== -1)) { - module.draw(gd, i); - } - } - } - - redrawObjs(fullLayout.annotations || [], Plotly.Annotations); - redrawObjs(fullLayout.shapes || [], Plotly.Shapes); - } - - function doubleClick() { - var doubleClickConfig = gd._context.doubleClick, - axList = (xActive ? xa : []).concat(yActive ? ya : []), - attrs = {}; - - var ax, i; - - if(doubleClickConfig === 'autosize') { - for(i = 0; i < axList.length; i++) { - ax = axList[i]; - if(!ax.fixedrange) attrs[ax._name + '.autorange'] = true; - } - } - else if(doubleClickConfig === 'reset') { - for(i = 0; i < axList.length; i++) { - ax = axList[i]; - - if(!ax._rangeInitial) { - attrs[ax._name + '.autorange'] = true; - } - else { - attrs[ax._name + '.range'] = ax._rangeInitial.slice(); - } - } - } - else if(doubleClickConfig === 'reset+autosize') { - for(i = 0; i < axList.length; i++) { - ax = axList[i]; - - if(ax.fixedrange) continue; - if(ax._rangeInitial === undefined || - ax.range[0] === ax._rangeInitial[0] && - ax.range[1] === ax._rangeInitial[1] - ) { - attrs[ax._name + '.autorange'] = true; - } - else attrs[ax._name + '.range'] = ax._rangeInitial.slice(); - } - } - - gd.emit('plotly_doubleclick', null); - Plotly.relayout(gd, attrs); - } - - // dragTail - finish a drag event with a redraw - function dragTail(zoommode) { - var attrs = {}; - // revert to the previous axis settings, then apply the new ones - // through relayout - this lets relayout manage undo/redo - for(var i = 0; i < allaxes.length; i++) { - var axi = allaxes[i]; - if(zoommode && zoommode.indexOf(axi._id.charAt(0))===-1) { - continue; - } - if(axi._r[0] !== axi.range[0]) attrs[axi._name+'.range[0]'] = axi.range[0]; - if(axi._r[1] !== axi.range[1]) attrs[axi._name+'.range[1]'] = axi.range[1]; - - axi.range=axi._r.slice(); - } - - updateViewBoxes([0,0,pw,ph]); - Plotly.relayout(gd,attrs); - } - - // updateViewBoxes - find all plot viewboxes that should be - // affected by this drag, and update them. look for all plots - // sharing an affected axis (including the one being dragged) - function updateViewBoxes(viewBox) { - var plotinfos = fullLayout._plots, - subplots = Object.keys(plotinfos), - i, - plotinfo2, - xa2, - ya2, - editX, - editY; - - for(i = 0; i < subplots.length; i++) { - plotinfo2 = plotinfos[subplots[i]]; - xa2 = plotinfo2.x(); - ya2 = plotinfo2.y(); - editX = ew && xa.indexOf(xa2)!==-1 && !xa2.fixedrange; - editY = ns && ya.indexOf(ya2)!==-1 && !ya2.fixedrange; - - if(editX || editY) { - var newVB = [0,0,xa2._length,ya2._length]; - if(editX) { - newVB[0] = viewBox[0]; - newVB[2] = viewBox[2]; - } - if(editY) { - newVB[1] = viewBox[1]; - newVB[3] = viewBox[3]; - } - plotinfo2.plot.attr('viewBox',newVB.join(' ')); - } - } - } - - return dragger; -} - -function getEndText(ax, end) { - var initialVal = ax.range[end], - diff = Math.abs(initialVal - ax.range[1 - end]), - dig; - - if(ax.type === 'date') { - return Plotly.Lib.ms2DateTime(initialVal, diff); - } - else if(ax.type==='log') { - dig = Math.ceil(Math.max(0, -Math.log(diff) / Math.LN10)) + 3; - return d3.format('.' + dig + 'g')(Math.pow(10, initialVal)); - } - else { // linear numeric (or category... but just show numbers here) - dig = Math.floor(Math.log(Math.abs(initialVal)) / Math.LN10) - - Math.floor(Math.log(diff) / Math.LN10) + 4; - return d3.format('.'+String(dig)+'g')(initialVal); - } -} - -function finishDrag(gd) { - gd._dragging = false; - if(gd._replotPending) Plotly.plot(gd); -} - -function removeZoombox(gd) { - d3.select(gd) - .selectAll('.zoombox,.js-zoombox-backdrop,.js-zoombox-menu,.zoombox-corners') - .remove(); -} - -// for automatic alignment on dragging, <1/3 means left align, -// >2/3 means right, and between is center. Pick the right fraction -// based on where you are, and return the fraction corresponding to -// that position on the object -fx.dragAlign = function(v, dv, v0, v1, anchor) { - var vmin = (v-v0)/(v1-v0), - vmax = vmin+dv/(v1-v0), - vc = (vmin+vmax)/2; - - // explicitly specified anchor - if(anchor==='left' || anchor==='bottom') return vmin; - if(anchor==='center' || anchor==='middle') return vc; - if(anchor==='right' || anchor==='top') return vmax; - - // automatic based on position - if(vmin<(2/3)-vc) return vmin; - if(vmax>(4/3)-vc) return vmax; - return vc; -}; - - -// set cursors pointing toward the closest corner/side, -// to indicate alignment -// x and y are 0-1, fractions of the plot area -var cursorset = [['sw-resize','s-resize','se-resize'], - ['w-resize','move','e-resize'], - ['nw-resize','n-resize','ne-resize']]; -fx.dragCursors = function(x,y,xanchor,yanchor) { - if(xanchor==='left') x=0; - else if(xanchor==='center') x=1; - else if(xanchor==='right') x=2; - else x = Plotly.Lib.constrain(Math.floor(x*3),0,2); - - if(yanchor==='bottom') y=0; - else if(yanchor==='middle') y=1; - else if(yanchor==='top') y=2; - else y = Plotly.Lib.constrain(Math.floor(y*3),0,2); - - return cursorset[y][x]; -}; - -/** - * Abstracts click & drag interactions - * @param {object} options with keys: - * element (required) the DOM element to drag - * prepFn (optional) function(event, startX, startY) - * executed on mousedown - * startX and startY are the clientX and clientY pixel position - * of the mousedown event - * moveFn (optional) function(dx, dy, dragged) - * executed on move - * dx and dy are the net pixel offset of the drag, - * dragged is true/false, has the mouse moved enough to - * constitute a drag - * doneFn (optional) function(dragged, numClicks) - * executed on mouseup, or mouseout of window since - * we don't get events after that - * dragged is as in moveFn - * numClicks is how many clicks we've registered within - * a doubleclick time - */ -fx.dragElement = function(options) { - var gd = Plotly.Lib.getPlotDiv(options.element) || {}, - numClicks = 1, - DBLCLICKDELAY = constants.DBLCLICKDELAY, - startX, - startY, - newMouseDownTime, - dragCover, - initialTarget; - - if(!gd._mouseDownTime) gd._mouseDownTime = 0; - - function onStart(e) { - // make dragging and dragged into properties of gd - // so that others can look at and modify them - gd._dragged = false; - gd._dragging = true; - startX = e.clientX; - startY = e.clientY; - initialTarget = e.target; - - newMouseDownTime = (new Date()).getTime(); - if(newMouseDownTime - gd._mouseDownTime < DBLCLICKDELAY) { - // in a click train - numClicks += 1; - } - else { - // new click train - numClicks = 1; - gd._mouseDownTime = newMouseDownTime; - } - - if(options.prepFn) options.prepFn(e, startX, startY); - - dragCover = coverSlip(); - - dragCover.onmousemove = onMove; - dragCover.onmouseup = onDone; - dragCover.onmouseout = onDone; - - dragCover.style.cursor = window.getComputedStyle(options.element).cursor; - - return Plotly.Lib.pauseEvent(e); - } - - function onMove(e) { - var dx = e.clientX - startX, - dy = e.clientY - startY, - minDrag = options.minDrag || constants.MINDRAG; - - if(Math.abs(dx) < minDrag) dx = 0; - if(Math.abs(dy) < minDrag) dy = 0; - if(dx||dy) { - gd._dragged = true; - fx.unhover(gd); - } - - if(options.moveFn) options.moveFn(dx, dy, gd._dragged); - - return Plotly.Lib.pauseEvent(e); - } - - function onDone(e) { - dragCover.onmousemove = null; - dragCover.onmouseup = null; - dragCover.onmouseout = null; - Plotly.Lib.removeElement(dragCover); - - if(!gd._dragging) { - gd._dragged = false; - return; - } - gd._dragging = false; - - // don't count as a dblClick unless the mouseUp is also within - // the dblclick delay - if((new Date()).getTime() - gd._mouseDownTime > DBLCLICKDELAY) { - numClicks = Math.max(numClicks - 1, 1); - } - - if(options.doneFn) options.doneFn(gd._dragged, numClicks); - - if(!gd._dragged) { - var e2 = document.createEvent('MouseEvents'); - e2.initEvent('click', true, true); - initialTarget.dispatchEvent(e2); - } - - finishDrag(gd); - - gd._dragged = false; - - return Plotly.Lib.pauseEvent(e); - } - - options.element.onmousedown = onStart; - options.element.style.pointerEvents = 'all'; -}; - -function coverSlip() { - var cover = document.createElement('div'); - - cover.className = 'dragcover'; - var cStyle = cover.style; - cStyle.position = 'fixed'; - cStyle.left = 0; - cStyle.right = 0; - cStyle.top = 0; - cStyle.bottom = 0; - cStyle.zIndex = 999999999; - cStyle.background = 'none'; - - document.body.appendChild(cover); - - return cover; -} - -fx.setCursor = function(el3,csr) { - (el3.attr('class')||'').split(' ').forEach(function(cls) { - if(cls.indexOf('cursor-')===0) { el3.classed(cls,false); } - }); - if(csr) { el3.classed('cursor-'+csr, true); } -}; - // for bar charts and others with finite-size objects: you must be inside // it to see its hover info, so distance is infinite outside. // But make distance inside be at least 1/4 MAXDIST, and a little bigger diff --git a/src/plots/ternary/ternary.js b/src/plots/ternary/ternary.js index 7abccfa74cd..f63792de426 100644 --- a/src/plots/ternary/ternary.js +++ b/src/plots/ternary/ternary.js @@ -107,6 +107,7 @@ proto.makeFramework = function() { // inside that container, we have one container for the data, and // one each for the three axes around it. var plotLayers = [ + 'draglayer', 'plotbg', 'backplot', 'grids', From c79d993069c752769755ecad24743b813bcb463c Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Wed, 30 Mar 2016 16:26:14 +0200 Subject: [PATCH 18/76] hover basic functionality (and much of the code for zoom/pan/select) in ternary --- src/components/errorbars/index.js | 4 +- src/plots/cartesian/dragbox.js | 2 +- src/plots/cartesian/graph_interact.js | 16 +- src/plots/ternary/ternary.js | 299 +++++++++++++++++++++++++- src/traces/scatterternary/hover.js | 8 +- 5 files changed, 316 insertions(+), 13 deletions(-) diff --git a/src/components/errorbars/index.js b/src/components/errorbars/index.js index 4986e60c6b1..8e7dd771ffc 100644 --- a/src/components/errorbars/index.js +++ b/src/components/errorbars/index.js @@ -46,11 +46,11 @@ errorBars.plot = require('./plot'); errorBars.style = require('./style'); errorBars.hoverInfo = function(calcPoint, trace, hoverPoint) { - if(trace.error_y.visible) { + if((trace.error_y || {}).visible) { hoverPoint.yerr = calcPoint.yh - calcPoint.y; if(!trace.error_y.symmetric) hoverPoint.yerrneg = calcPoint.y - calcPoint.ys; } - if(trace.error_x.visible) { + if((trace.error_x || {}).visible) { hoverPoint.xerr = calcPoint.xh - calcPoint.x; if(!trace.error_x.symmetric) hoverPoint.xerrneg = calcPoint.x - calcPoint.xs; } diff --git a/src/plots/cartesian/dragbox.js b/src/plots/cartesian/dragbox.js index 2fa9bccd37c..ab0efbad05b 100644 --- a/src/plots/cartesian/dragbox.js +++ b/src/plots/cartesian/dragbox.js @@ -155,7 +155,7 @@ module.exports = function dragBox(gd, plotinfo, x, y, w, h, ns, ew) { lum = gd._hmpixcount ? (gd._hmlumcount / gd._hmpixcount) : tinycolor(gd._fullLayout.plot_bgcolor).getLuminance(); - path0 = path0 = 'M0,0H'+pw+'V'+ph+'H0V0'; + path0 = 'M0,0H'+pw+'V'+ph+'H0V0'; dimmed = false; zoomMode = 'xy'; diff --git a/src/plots/cartesian/graph_interact.js b/src/plots/cartesian/graph_interact.js index 68363debb77..2139442703f 100644 --- a/src/plots/cartesian/graph_interact.js +++ b/src/plots/cartesian/graph_interact.js @@ -313,9 +313,15 @@ function hover(gd, evt, subplot) { .map(function(pi) { return pi.id; })), xaArray = subplots.map(function(spId) { + if(spId.substr(0,7) === 'ternary') { + return gd._fullLayout[spId]._ternary.xaxis; + } return Plotly.Axes.getFromId(gd, spId, 'x'); }), yaArray = subplots.map(function(spId) { + if(spId.substr(0,7) === 'ternary') { + return gd._fullLayout[spId]._ternary.yaxis; + } return Plotly.Axes.getFromId(gd, spId, 'y'); }), hovermode = evt.hovermode || fullLayout.hovermode; @@ -366,7 +372,7 @@ function hover(gd, evt, subplot) { for(curvenum = 0; curvenum0.2 ? 'rgba(0,0,0,0)' : 'rgba(255,255,255,0)', + 'stroke-width': 0 + }) + .attr('d', path0 + 'Z'); + + corners = plot.append('path') + .attr('class', 'zoombox-corners') + .style({ + fill: Plotly.Color.background, + stroke: Plotly.Color.defaultLine, + 'stroke-width': 1, + opacity: 0 + }) + .attr('d','M0,0Z'); + + clearSelect(); + } + + function zoomMove(dx0, dy0) { + var x1 = x0 + dx0, + y1 = y0 + dy0, + afrac = Math.max(0, 1 - Math.max(y0, y1) / _this.h), + bfrac = Math.max(0, 1 - Math.max(x0 + y0 / Math.sqrt(3), x1 + y1 / Math.sqrt(3)) / _this.w), + cfrac = Math.max(0, Math.min(x0 - y0 / Math.sqrt(3), x1 - y1 / Math.sqrt(3)) / _this.w), + xLeft = ((afrac / 2) + cfrac) * _this.w, + xRight = (1 - (afrac / 2) - bfrac) * _this.w, + xCenter = (xLeft + xRight) / 2, + xSpan = xRight - xLeft, + yBottom = (1 - afrac) * _this.h, + yTop = yBottom - xSpan / w_over_h; + + if(xSpan < constants.MINZOOM) { + mins = mins0; + zb.attr('d', path0 + 'Z'); + corners.attr('d', 'M0,0Z'); + } + else { + mins = { + a: mins0.a + afrac * span0, + b: mins0.b + bfrac * span0, + c: mins0.c + cfrac * span0 + }; + zb.attr('d', path0 + 'M' + xLeft + ',' + yBottom + + 'H' + xRight + 'L' + xCenter + ',' + yTop + + 'L' + xLeft + ',' + yBottom + 'Z'); + corners.attr('d', 'M' + xLeft + ',' + yBottom + BLPATH + + 'M' + xRight + ',' + yBottom + BRPATH + + 'M' + xCenter + ',' + yTop + TOPPATH); + } + + if(!dimmed) { + zb.transition() + .style('fill', lum>0.2 ? 'rgba(0,0,0,0.4)' : + 'rgba(255,255,255,0.3)') + .duration(200); + corners.transition() + .style('opacity',1) + .duration(200); + dimmed = true; + } + } + + function zoomDone(dragged, numClicks) { + if(mins === mins0) { + if(numClicks === 2) doubleClick(); + + return removeZoombox(gd); + } + + removeZoombox(gd); + + var attrs = {}; + attrs[_this.id + '.aaxis.min'] = mins.a; + attrs[_this.id + '.baxis.min'] = mins.b; + attrs[_this.id + '.caxis.min'] = mins.c; + + Plotly.relayout(gd, attrs); + + if(SHOWZOOMOUTTIP && gd.data && gd._context.showTips) { + Lib.notifier('Double-click to
zoom back out','long'); + SHOWZOOMOUTTIP = false; + } + } + + function plotDrag(dx, dy) { + var dxScaled = dx / _this.xaxis._m, + dyScaled = dy / _this.yaxis._m; + mins = { + a: mins0.a - dyScaled, + b: mins0.b + dxScaled + dyScaled / 2, + c: mins0.c - dxScaled + dyScaled / 2 + }; + var minsorted = [mins.a, mins.b, mins.c].sort(), + minindices = { + a: minsorted.indexOf(mins.a), + b: minsorted.indexOf(mins.b), + c: minsorted.indexOf(mins.c) + }; + if(minsorted[0] < 0) { + if(minsorted[1] + minsorted[0] / 2 < 0) { + minsorted[2] -= minsorted[0] + minsorted[1]; + minsorted[0] = minsorted[1] = 0; + } + else { + minsorted[2] -= minsorted[0] / 2; + minsorted[1] -= minsorted[0] / 2; + minsorted[0] = 0; + } + mins = { + a: minsorted[minindices.a], + b: minsorted[minindices.b], + c: minsorted[minindices.c] + }; + dy = (mins0.a - mins.a) * _this.yaxis._m; + dx = (mins0.c - mins.c - mins0.b + mins.b) * _this.xaxis._m; + } + + // move the data (translate, don't redraw) + var plotTransform = 'translate(' + (_this.x0 + dx) + ',' + (_this.y0 + dy) + ')'; + _this.plotContainer.selectAll('.scatterlayer,.maplayer') + .attr('transform', plotTransform); + + // move the ticks + _this.aaxis.range = [mins.a, _this.sum - mins.b - mins.c]; + _this.baxis.range = [mins.b, _this.sum - mins.a - mins.c]; + _this.caxis.range = [mins.c, _this.sum - mins.b - mins.b]; + + Axes.doTicks(_this.graphDiv, _this.aaxis, true); + Axes.doTicks(_this.graphDiv, _this.baxis, true); + Axes.doTicks(_this.graphDiv, _this.caxis, true); + _this.plotContainer.selectAll('.crisp').classed('crisp', false); + } + + function dragDone(dragged, numClicks) { + if(dragged) { + var attrs = {}; + attrs[_this.id + '.aaxis.min'] = mins.a; + attrs[_this.id + '.baxis.min'] = mins.b; + attrs[_this.id + '.caxis.min'] = mins.c; + + Plotly.relayout(gd, attrs); + } + else if(numClicks === 2) doubleClick(); + } + + function clearSelect() { + // until we get around to persistent selections, remove the outline + // here. The selection itself will be removed when the plot redraws + // at the end. + _this.plotContainer.selectAll('.select-outline').remove(); + } + + function doubleClick() { + var attrs = {}; + attrs[_this.id + '.aaxis.min'] = 0; + attrs[_this.id + '.baxis.min'] = 0; + attrs[_this.id + '.caxis.min'] = 0; + gd.emit('plotly_doubleclick', null); + Plotly.relayout(gd, attrs); + } + + dragElement.init(dragOptions); + + // finally, set up hover and click + dragger.onmousemove = function(evt) { + fx.hover(gd, evt, _this.id); + gd._fullLayout._lasthover = dragger; + gd._fullLayout._hoversubplot = _this.id; + }; + + dragger.onmouseout = function(evt) { + if(gd._dragging) return; + + dragElement.unhover(gd, evt); + }; + + dragger.onclick = function(evt) { + fx.click(gd, evt); + }; + + // make a fake plotinfo for fx.hover + // it hardly uses it, could probably be refactored out... + // but specifying subplot by name does seem nice for js applications + // that want to hook into this. + if(!gd._fullLayout._plots) gd._fullLayout._plots = {}; + gd._fullLayout._plots[_this.id] = { + overlays: [], + xaxis: _this.xaxis, + yaxis: _this.yaxis, + x: function() { return _this.xaxis; }, + y: function() { return _this.yaxis; } + }; +}; + +function removeZoombox(gd) { + d3.select(gd) + .selectAll('.zoombox,.js-zoombox-backdrop,.js-zoombox-menu,.zoombox-corners') + .remove(); +} + diff --git a/src/traces/scatterternary/hover.js b/src/traces/scatterternary/hover.js index 2cc3f1426cc..bf823750c1d 100644 --- a/src/traces/scatterternary/hover.js +++ b/src/traces/scatterternary/hover.js @@ -23,5 +23,11 @@ module.exports = function hoverPoints(pointData, xval, yval, hovermode) { newPointData.b = cdi.b; newPointData.c = cdi.c; - // TODO: how to actually display a, b, and c? + newPointData.xLabelVal = undefined; + newPointData.yLabelVal = undefined; + // TODO: nice formatting, and label by axis title, for a, b, and c? + + newPointData.text = 'a: ' + cdi.a + '
b: ' + cdi.b + '
c: ' + cdi.c; + + return scatterPointData; }; From 7dbd9646db354b8c6a5e9459d2cd18dc21008bae Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Wed, 30 Mar 2016 23:52:55 +0200 Subject: [PATCH 19/76] more ternary interaction futzing and fix scaling with nonzero mins --- src/components/modebar/manage.js | 11 +++--- src/plot_api/plot_api.js | 1 + src/plots/cartesian/graph_interact.js | 7 ++++ src/plots/ternary/layout/axis_defaults.js | 5 ++- src/plots/ternary/ternary.js | 41 +++++++++++++++-------- src/traces/scatterternary/defaults.js | 2 ++ src/traces/scatterternary/hover.js | 17 +++++++++- src/traces/scatterternary/plot.js | 4 +++ src/traces/scatterternary/select.js | 2 ++ 9 files changed, 69 insertions(+), 21 deletions(-) diff --git a/src/components/modebar/manage.js b/src/components/modebar/manage.js index 65ea0c16ff3..64b91bd407c 100644 --- a/src/components/modebar/manage.js +++ b/src/components/modebar/manage.js @@ -77,7 +77,8 @@ function getButtonGroups(gd, buttonsToRemove, buttonsToAdd) { hasGL3D = fullLayout._hasGL3D, hasGeo = fullLayout._hasGeo, hasPie = fullLayout._hasPie, - hasGL2D = fullLayout._hasGL2D; + hasGL2D = fullLayout._hasGL2D, + hasTernary = fullLayout._hasTernary; var groups = []; @@ -98,7 +99,7 @@ function getButtonGroups(gd, buttonsToRemove, buttonsToAdd) { // graphs with more than one plot types get 'union buttons' // which reset the view or toggle hover labels across all subplots. - if((hasCartesian || hasGL2D || hasPie) + hasGeo + hasGL3D > 1) { + if((hasCartesian || hasGL2D || hasPie || hasTernary) + hasGeo + hasGL3D > 1) { addGroup(['resetViews', 'toggleHover']); return appendButtonsToGroups(groups, buttonsToAdd); } @@ -117,16 +118,16 @@ function getButtonGroups(gd, buttonsToRemove, buttonsToAdd) { var allAxesFixed = areAllAxesFixed(fullLayout), dragModeGroup = []; - if((hasCartesian || hasGL2D) && !allAxesFixed) { + if(((hasCartesian || hasGL2D) && !allAxesFixed) || hasTernary) { dragModeGroup = ['zoom2d', 'pan2d']; } - if(hasCartesian && isSelectable(fullData)) { + if((hasCartesian || hasTernary) && isSelectable(fullData)) { dragModeGroup.push('select2d'); dragModeGroup.push('lasso2d'); } if(dragModeGroup.length) addGroup(dragModeGroup); - if((hasCartesian || hasGL2D) && !allAxesFixed) { + if((hasCartesian || hasGL2D) && !allAxesFixed && !hasTernary) { addGroup(['zoomIn2d', 'zoomOut2d', 'autoScale2d', 'resetScale2d']); } diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index 90663d27185..6196606e40c 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -2271,6 +2271,7 @@ Plotly.relayout = function relayout(gd, astr, val) { // 3d or geo at this point just needs to redraw. if(p.parts[0].indexOf('scene') === 0) doplot = true; else if(p.parts[0].indexOf('geo') === 0) doplot = true; + else if(p.parts[0].indexOf('ternary') === 0) doplot = true; else if(fullLayout._hasGL2D && (ai.indexOf('axis') !== -1 || p.parts[0] === 'plot_bgcolor') ) doplot = true; diff --git a/src/plots/cartesian/graph_interact.js b/src/plots/cartesian/graph_interact.js index 2139442703f..4f6b9374252 100644 --- a/src/plots/cartesian/graph_interact.js +++ b/src/plots/cartesian/graph_interact.js @@ -927,6 +927,13 @@ function createHoverText(hoverData, opts) { if(name.length>15) name = name.substr(0,12)+'...'; } + // used by other modules (initially just ternary) that + // manage their own hoverinfo independent of cleanPoint + // the rest of this will still apply, so such modules + // can still put things in (x|y|z)Label, text, and name + // and hoverinfo will still determine their visibility + if(d.extraText!==undefined) text += d.extraText; + if(d.zLabel!==undefined) { if(d.xLabel!==undefined) text += 'x: ' + d.xLabel + '
'; if(d.yLabel!==undefined) text += 'y: ' + d.yLabel + '
'; diff --git a/src/plots/ternary/layout/axis_defaults.js b/src/plots/ternary/layout/axis_defaults.js index 80306e60edf..c5fd79b2efa 100644 --- a/src/plots/ternary/layout/axis_defaults.js +++ b/src/plots/ternary/layout/axis_defaults.js @@ -31,8 +31,11 @@ module.exports = function supplyLayoutDefaults(containerIn, containerOut, option var dfltColor = coerce('color'); - coerce('title', 'Click to enter component ' + + var title = coerce('title', 'Click to enter component ' + axName.charAt(0).toUpperCase() + ' title'); + containerOut._hovertitle = title.indexOf('Click to enter') === -1 ? + title : axName.charAt(0); + Lib.coerceFont(coerce, 'titlefont', { family: options.font.family, size: Math.round(options.font.size * 1.2), diff --git a/src/plots/ternary/ternary.js b/src/plots/ternary/ternary.js index f3acf2a3d58..5d5279a145a 100644 --- a/src/plots/ternary/ternary.js +++ b/src/plots/ternary/ternary.js @@ -29,11 +29,7 @@ var fx = require('../cartesian/graph_interact'); function Ternary(options, fullLayout) { this.id = options.id; this.graphDiv = options.graphDiv; - this.container = fullLayout._ternarylayer; - this.defs = fullLayout._defs; - this.layoutId = fullLayout._uid; - this.traceHash = {}; - + this.init(fullLayout); this.makeFramework(); } @@ -41,12 +37,28 @@ module.exports = Ternary; var proto = Ternary.prototype; +proto.init = function(fullLayout) { + this.container = fullLayout._ternarylayer; + this.defs = fullLayout._defs; + this.layoutId = fullLayout._uid; + this.traceHash = {}; +}; + proto.plot = function(ternaryData, fullLayout) { var _this = this, ternaryLayout = fullLayout[_this.id], graphSize = fullLayout._size, i; + if(Lib.getPlotDiv(_this.plotContainer.node()) !== _this.graphDiv) { + // someone deleted the framework - remake it + // TODO: this is getting deleted in (cartesian) makePlotFramework + // turn that into idiomatic d3 (enter/exit, the piece I didn't know + // before was ordering selections) so we don't need this. + _this.init(_this.graphDiv._fullLayout); + _this.makeFramework(); + } + _this.adjustLayout(ternaryLayout, graphSize); var traceHashOld = _this.traceHash; @@ -120,6 +132,7 @@ proto.makeFramework = function() { 'backplot', 'grids', 'frontplot', + 'zoom', 'aaxis', 'baxis', 'caxis','axlines' ]; var toplevel = _this.plotContainer.selectAll('g.toplevel') @@ -200,7 +213,7 @@ proto.adjustLayout = function(ternaryLayout, graphSize) { // set up the x and y axis objects we'll use to lay out the points _this.xaxis = { type: 'linear', - range: [amin + bmin -sum, sum - amin - cmin], + range: [amin + 2 * cmin - sum, sum - amin - 2 * bmin], domain: [ xDomainCenter - xDomainFinal / 2, xDomainCenter + xDomainFinal / 2 @@ -291,7 +304,7 @@ proto.adjustLayout = function(ternaryLayout, graphSize) { _this.layers.plotbg.select('path').attr('d', triangleClip); var plotTransform = 'translate(' + x0 + ',' + y0 + ')'; - _this.plotContainer.selectAll('.scatterlayer,.maplayer') + _this.plotContainer.selectAll('.scatterlayer,.maplayer,.zoom') .attr('transform', plotTransform); // TODO: shift axes to accommodate linewidth*sin(30) tick mark angle @@ -399,22 +412,22 @@ proto.init_interactions = function() { var dragBBox = dragger.getBoundingClientRect(); x0 = startX - dragBBox.left; y0 = startY - dragBBox.top; - mins0 = {a: _this.aaxis.range[0], b: _this.baxis.range[0], c: _this.caxis.range[0]}; + mins0 = {a: _this.aaxis.range[0], b: _this.baxis.range[1], c: _this.caxis.range[1]}; mins = mins0; span0 = _this.aaxis.range[1] - mins0.a; - lum = tinycolor(gd._fullLayout.plot_bgcolor).getLuminance(); - path0 = 'M0,' + _this.h + 'H' + _this.w + 'L' + (_this.w / 2) +', 0L0,0'; + lum = tinycolor(_this.graphDiv._fullLayout[_this.id].bgcolor).getLuminance(); + path0 = 'M0,' + _this.h + 'L' + (_this.w / 2) +', 0L' + _this.w + ',' + _this.h + 'Z'; dimmed = false; - zb = plot.append('path') + zb = _this.layers.zoom.append('path') .attr('class', 'zoombox') .style({ 'fill': lum>0.2 ? 'rgba(0,0,0,0)' : 'rgba(255,255,255,0)', 'stroke-width': 0 }) - .attr('d', path0 + 'Z'); + .attr('d', path0); - corners = plot.append('path') + corners = _this.layers.zoom.append('path') .attr('class', 'zoombox-corners') .style({ fill: Plotly.Color.background, @@ -442,7 +455,7 @@ proto.init_interactions = function() { if(xSpan < constants.MINZOOM) { mins = mins0; - zb.attr('d', path0 + 'Z'); + zb.attr('d', path0); corners.attr('d', 'M0,0Z'); } else { diff --git a/src/traces/scatterternary/defaults.js b/src/traces/scatterternary/defaults.js index fa5a2d2a535..aa173d62077 100644 --- a/src/traces/scatterternary/defaults.js +++ b/src/traces/scatterternary/defaults.js @@ -84,4 +84,6 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout handleFillColorDefaults(traceIn, traceOut, defaultColor, coerce); if(!subTypes.hasLines(traceOut)) handleLineShapeDefaults(traceIn, traceOut, coerce); } + + coerce('hoverinfo', (layout._dataLength === 1) ? 'a+b+c+text' : undefined); }; diff --git a/src/traces/scatterternary/hover.js b/src/traces/scatterternary/hover.js index bf823750c1d..212b7d18d83 100644 --- a/src/traces/scatterternary/hover.js +++ b/src/traces/scatterternary/hover.js @@ -10,6 +10,7 @@ 'use strict'; var scatterHover = require('../scatter/hover'); +var Axes = require('../../plots/cartesian/axes'); module.exports = function hoverPoints(pointData, xval, yval, hovermode) { @@ -27,7 +28,21 @@ module.exports = function hoverPoints(pointData, xval, yval, hovermode) { newPointData.yLabelVal = undefined; // TODO: nice formatting, and label by axis title, for a, b, and c? - newPointData.text = 'a: ' + cdi.a + '
b: ' + cdi.b + '
c: ' + cdi.c; + var trace = newPointData.trace, + ternary = trace._ternary, + hoverinfo = trace.hoverinfo.split('+'), + text = []; + + function textPart(ax, val) { + text.push(ax._hovertitle + ': ' + Axes.tickText(ax, val, 'hover').text); + } + + if(hoverinfo.indexOf('all') !== -1) hoverinfo = ['a', 'b', 'c']; + if(hoverinfo.indexOf('a') !== -1) textPart(ternary.aaxis, cdi.a); + if(hoverinfo.indexOf('b') !== -1) textPart(ternary.baxis, cdi.b); + if(hoverinfo.indexOf('c') !== -1) textPart(ternary.caxis, cdi.c); + + newPointData.extraText = text.join('
'); return scatterPointData; }; diff --git a/src/traces/scatterternary/plot.js b/src/traces/scatterternary/plot.js index f7996576563..8fb76c9b076 100644 --- a/src/traces/scatterternary/plot.js +++ b/src/traces/scatterternary/plot.js @@ -26,6 +26,10 @@ module.exports = function plot(ternary, data) { for(i = 0; i < fullCalcdata.length; i++) { j = data.indexOf(fullCalcdata[i][0].trace); if(j !== -1) calcdata[j] = fullCalcdata[i]; + + // while we're here and have references to both the Ternary object + // and fullData, connect the two (for use by hover) + data[j]._ternary = ternary; } scatterPlot(ternary.graphDiv, plotinfo, calcdata); }; diff --git a/src/traces/scatterternary/select.js b/src/traces/scatterternary/select.js index 226e3c15e1a..bca550e040c 100644 --- a/src/traces/scatterternary/select.js +++ b/src/traces/scatterternary/select.js @@ -25,6 +25,8 @@ module.exports = function selectPoints(searchInfo, polygon) { pt.a = cdi.a; pt.b = cdi.b; pt.c = cdi.c; + delete pt.x; + delete pt.y; } return selection; From c24e444c798fb720c3101654729b5af73364ba73 Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Thu, 31 Mar 2016 00:27:20 +0200 Subject: [PATCH 20/76] fix ternary zoom --- src/plots/ternary/ternary.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/plots/ternary/ternary.js b/src/plots/ternary/ternary.js index 5d5279a145a..2d023fc6f2a 100644 --- a/src/plots/ternary/ternary.js +++ b/src/plots/ternary/ternary.js @@ -362,6 +362,7 @@ var TOPPATH = 'm0,1l' + (CLEN / 2) + ',' + (CLEN * 0.87) + 'l2.6,-1.5l-' + (CLEN / 2 + 2.6) + ',-' + (CLEN * 0.87 + 4.5) + 'l-' + (CLEN / 2 + 2.6) + ',' + (CLEN * 0.87 + 4.5) + 'l2.6,1.5l' + (CLEN / 2) + ',-' + (CLEN * 0.87) + 'Z'; +var STARTMARKER = 'm0.5,0.5h5v-2h-5v-5h-2v5h-5v2h5v5h2Z'; // I guess this could be shared with cartesian... but for now it's separate. var SHOWZOOMOUTTIP = true; @@ -440,12 +441,16 @@ proto.init_interactions = function() { clearSelect(); } + function getAFrac(x, y) { return 1 - (y / _this.h); } + function getBFrac(x, y) { return 1 - ((x + (_this.h - y) / Math.sqrt(3)) / _this.w); } + function getCFrac(x, y) { return ((x - (_this.h - y) / Math.sqrt(3)) / _this.w); } + function zoomMove(dx0, dy0) { var x1 = x0 + dx0, y1 = y0 + dy0, - afrac = Math.max(0, 1 - Math.max(y0, y1) / _this.h), - bfrac = Math.max(0, 1 - Math.max(x0 + y0 / Math.sqrt(3), x1 + y1 / Math.sqrt(3)) / _this.w), - cfrac = Math.max(0, Math.min(x0 - y0 / Math.sqrt(3), x1 - y1 / Math.sqrt(3)) / _this.w), + afrac = Math.max(0, Math.min(1, getAFrac(x0, y0), getAFrac(x1, y1))), + bfrac = Math.max(0, Math.min(1, getBFrac(x0, y0), getBFrac(x1, y1))), + cfrac = Math.max(0, Math.min(1, getCFrac(x0, y0), getCFrac(x1, y1))), xLeft = ((afrac / 2) + cfrac) * _this.w, xRight = (1 - (afrac / 2) - bfrac) * _this.w, xCenter = (xLeft + xRight) / 2, @@ -467,7 +472,8 @@ proto.init_interactions = function() { zb.attr('d', path0 + 'M' + xLeft + ',' + yBottom + 'H' + xRight + 'L' + xCenter + ',' + yTop + 'L' + xLeft + ',' + yBottom + 'Z'); - corners.attr('d', 'M' + xLeft + ',' + yBottom + BLPATH + + corners.attr('d', 'M' + x0 + ',' + y0 + STARTMARKER + + 'M' + xLeft + ',' + yBottom + BLPATH + 'M' + xRight + ',' + yBottom + BRPATH + 'M' + xCenter + ',' + yTop + TOPPATH); } From 381de10d8d969204bb36a6707948782c2558151d Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Thu, 31 Mar 2016 00:43:23 +0200 Subject: [PATCH 21/76] fix ternary pan --- src/plots/ternary/ternary.js | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/src/plots/ternary/ternary.js b/src/plots/ternary/ternary.js index 2d023fc6f2a..b953e9cd885 100644 --- a/src/plots/ternary/ternary.js +++ b/src/plots/ternary/ternary.js @@ -399,6 +399,7 @@ proto.init_interactions = function() { else if(dragModeNow === 'pan') { dragOptions.moveFn = plotDrag; dragOptions.doneFn = dragDone; + panPrep(); clearSelect(); } else if(dragModeNow === 'select' || dragModeNow === 'lasso') { @@ -413,7 +414,11 @@ proto.init_interactions = function() { var dragBBox = dragger.getBoundingClientRect(); x0 = startX - dragBBox.left; y0 = startY - dragBBox.top; - mins0 = {a: _this.aaxis.range[0], b: _this.baxis.range[1], c: _this.caxis.range[1]}; + mins0 = { + a: _this.aaxis.range[0], + b: _this.baxis.range[1], + c: _this.caxis.range[1] + }; mins = mins0; span0 = _this.aaxis.range[1] - mins0.a; lum = tinycolor(_this.graphDiv._fullLayout[_this.id].bgcolor).getLuminance(); @@ -512,13 +517,22 @@ proto.init_interactions = function() { } } + function panPrep() { + mins0 = { + a: _this.aaxis.range[0], + b: _this.baxis.range[1], + c: _this.caxis.range[1] + }; + mins = mins0; + } + function plotDrag(dx, dy) { var dxScaled = dx / _this.xaxis._m, dyScaled = dy / _this.yaxis._m; mins = { a: mins0.a - dyScaled, - b: mins0.b + dxScaled + dyScaled / 2, - c: mins0.c - dxScaled + dyScaled / 2 + b: mins0.b + (dxScaled + dyScaled) / 2, + c: mins0.c - (dxScaled - dyScaled) / 2 }; var minsorted = [mins.a, mins.b, mins.c].sort(), minindices = { @@ -528,12 +542,12 @@ proto.init_interactions = function() { }; if(minsorted[0] < 0) { if(minsorted[1] + minsorted[0] / 2 < 0) { - minsorted[2] -= minsorted[0] + minsorted[1]; + minsorted[2] += minsorted[0] + minsorted[1]; minsorted[0] = minsorted[1] = 0; } else { - minsorted[2] -= minsorted[0] / 2; - minsorted[1] -= minsorted[0] / 2; + minsorted[2] += minsorted[0] / 2; + minsorted[1] += minsorted[0] / 2; minsorted[0] = 0; } mins = { @@ -552,8 +566,8 @@ proto.init_interactions = function() { // move the ticks _this.aaxis.range = [mins.a, _this.sum - mins.b - mins.c]; - _this.baxis.range = [mins.b, _this.sum - mins.a - mins.c]; - _this.caxis.range = [mins.c, _this.sum - mins.b - mins.b]; + _this.baxis.range = [_this.sum - mins.a - mins.c, mins.b]; + _this.caxis.range = [_this.sum - mins.a - mins.b, mins.c]; Axes.doTicks(_this.graphDiv, _this.aaxis, true); Axes.doTicks(_this.graphDiv, _this.baxis, true); From c47d4f950874fd58915c8837222c42f3343aca6a Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Sat, 2 Apr 2016 00:13:59 +0200 Subject: [PATCH 22/76] fix select on ternaryscatter --- src/plots/cartesian/select.js | 30 +++++++++++++++++++++--------- src/plots/ternary/ternary.js | 15 +++++++++------ 2 files changed, 30 insertions(+), 15 deletions(-) diff --git a/src/plots/cartesian/select.js b/src/plots/cartesian/select.js index 502f0ea6923..3c82d1ccdff 100644 --- a/src/plots/cartesian/select.js +++ b/src/plots/cartesian/select.js @@ -71,15 +71,27 @@ module.exports = function prepSelect(e, startX, startY, dragOptions, mode) { trace = cd[0].trace; if(!trace._module || !trace._module.selectPoints) continue; - if(xAxisIds.indexOf(trace.xaxis) === -1) continue; - if(yAxisIds.indexOf(trace.yaxis) === -1) continue; - - searchTraces.push({ - selectPoints: trace._module.selectPoints, - cd: cd, - xaxis: axes.getFromId(gd, trace.xaxis), - yaxis: axes.getFromId(gd, trace.yaxis) - }); + if(dragOptions.subplot) { + if(trace.subplot !== dragOptions.subplot) continue; + + searchTraces.push({ + selectPoints: trace._module.selectPoints, + cd: cd, + xaxis: dragOptions.xaxes[0], + yaxis: dragOptions.yaxes[0] + }); + } + else { + if(xAxisIds.indexOf(trace.xaxis) === -1) continue; + if(yAxisIds.indexOf(trace.yaxis) === -1) continue; + + searchTraces.push({ + selectPoints: trace._module.selectPoints, + cd: cd, + xaxis: axes.getFromId(gd, trace.xaxis), + yaxis: axes.getFromId(gd, trace.yaxis) + }); + } } function axValue(ax) { diff --git a/src/plots/ternary/ternary.js b/src/plots/ternary/ternary.js index b953e9cd885..61ec72afe9d 100644 --- a/src/plots/ternary/ternary.js +++ b/src/plots/ternary/ternary.js @@ -371,17 +371,20 @@ proto.init_interactions = function() { var _this = this, dragger = _this.layers.plotbg.select('path').node(), gd = _this.graphDiv, - plot = _this.layers.frontplot; + zoomContainer = _this.layers.zoom; // use plotbg for the main interactions var dragOptions = { element: dragger, gd: gd, - plotinfo: {plot: plot}, - xaxes: _this.xaxis, - yaxes: _this.yaxis, + plotinfo: {plot: zoomContainer}, doubleclick: doubleClick, + subplot: _this.id, prepFn: function(e, startX, startY) { + // these aren't available yet when init_interactions + // is called + dragOptions.xaxes = [_this.xaxis]; + dragOptions.yaxes = [_this.yaxis]; var dragModeNow = gd._fullLayout.dragmode; if(e.shiftKey) { if(dragModeNow === 'pan') dragModeNow = 'zoom'; @@ -425,7 +428,7 @@ proto.init_interactions = function() { path0 = 'M0,' + _this.h + 'L' + (_this.w / 2) +', 0L' + _this.w + ',' + _this.h + 'Z'; dimmed = false; - zb = _this.layers.zoom.append('path') + zb = zoomContainer.append('path') .attr('class', 'zoombox') .style({ 'fill': lum>0.2 ? 'rgba(0,0,0,0)' : 'rgba(255,255,255,0)', @@ -433,7 +436,7 @@ proto.init_interactions = function() { }) .attr('d', path0); - corners = _this.layers.zoom.append('path') + corners = zoomContainer.append('path') .attr('class', 'zoombox-corners') .style({ fill: Plotly.Color.background, From 17df3cd94bbe3d873d9e3e1c874369117ab54667 Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Sat, 2 Apr 2016 12:54:34 +0200 Subject: [PATCH 23/76] collect Titles.draw calls to prep for refactoring --- src/plot_api/plot_api.js | 8 ++++++-- src/plots/cartesian/axes.js | 10 +++++++--- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index 6196606e40c..9380c9ede4c 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -2365,7 +2365,7 @@ Plotly.relayout = function relayout(gd, astr, val) { if(doticks) { seq.push(function() { Plotly.Axes.doTicks(gd,'redraw'); - Titles.draw(gd, 'gtitle'); + drawMainTitle(gd); return Plots.previousPromises(gd); }); } @@ -2945,9 +2945,13 @@ function lsInner(gd) { Plotly.Axes.makeClipPaths(gd); - Titles.draw(gd, 'gtitle'); + drawMainTitle(gd); manageModeBar(gd); return gd._promises.length && Promise.all(gd._promises); } + +function drawMainTitle(gd) { + Titles.draw(gd, 'gtitle'); +} diff --git a/src/plots/cartesian/axes.js b/src/plots/cartesian/axes.js index 49cf9149171..5b49b52f8f6 100644 --- a/src/plots/cartesian/axes.js +++ b/src/plots/cartesian/axes.js @@ -1457,7 +1457,7 @@ axes.doTicks = function(td, axid, skipTitle) { var tickLabels=container.selectAll('g.'+tcls).data(vals, datafn); if(!ax.showticklabels || !isNumeric(position)) { tickLabels.remove(); - Titles.draw(td, axid + 'title'); + drawAxTitle(axid); return; } @@ -1624,8 +1624,8 @@ axes.doTicks = function(td, axid, skipTitle) { // update the axis title // (so it can move out of the way if needed) // TODO: separate out scoot so we don't need to do - // a full redraw of the title (modtly relevant for MathJax) - if(!skipTitle) Titles.draw(td, axid + 'title'); + // a full redraw of the title (mostly relevant for MathJax) + if(!skipTitle) drawAxTitle(axid); return axid+' done'; } @@ -1637,6 +1637,10 @@ axes.doTicks = function(td, axid, skipTitle) { return done; } + function drawAxTitle(axid) { + Titles.draw(td, axid + 'title'); + } + function traceHasBarsOrFill(trace, subplot) { if(trace.visible !== true || trace.xaxis + trace.yaxis !== subplot) return false; if(Plotly.Plots.traceIs(trace, 'bar') && trace.orientation === {x: 'h', y: 'v'}[axletter]) return true; From 637a956ee8fdbfb5aa1006b6ef812b63e5091559 Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Sat, 2 Apr 2016 23:49:50 +0200 Subject: [PATCH 24/76] robustify modebar logic and tests --- src/components/modebar/manage.js | 12 ++++++------ test/jasmine/tests/modebar_test.js | 12 ------------ 2 files changed, 6 insertions(+), 18 deletions(-) diff --git a/src/components/modebar/manage.js b/src/components/modebar/manage.js index 64b91bd407c..c85db4a25e9 100644 --- a/src/components/modebar/manage.js +++ b/src/components/modebar/manage.js @@ -73,12 +73,12 @@ function getButtonGroups(gd, buttonsToRemove, buttonsToAdd) { var fullLayout = gd._fullLayout, fullData = gd._fullData; - var hasCartesian = fullLayout._hasCartesian, - hasGL3D = fullLayout._hasGL3D, - hasGeo = fullLayout._hasGeo, - hasPie = fullLayout._hasPie, - hasGL2D = fullLayout._hasGL2D, - hasTernary = fullLayout._hasTernary; + var hasCartesian = !!fullLayout._hasCartesian, + hasGL3D = !!fullLayout._hasGL3D, + hasGeo = !!fullLayout._hasGeo, + hasPie = !!fullLayout._hasPie, + hasGL2D = !!fullLayout._hasGL2D, + hasTernary = !!fullLayout._hasTernary; var groups = []; diff --git a/test/jasmine/tests/modebar_test.js b/test/jasmine/tests/modebar_test.js index 933aafd28f9..1cd43aae7ce 100644 --- a/test/jasmine/tests/modebar_test.js +++ b/test/jasmine/tests/modebar_test.js @@ -309,9 +309,6 @@ describe('ModeBar', function() { var gd = getMockGraphInfo(); gd._fullLayout._hasCartesian = true; gd._fullLayout._hasGL3D = true; - gd._fullLayout._hasGeo = false; - gd._fullLayout._hasGL2D = false; - gd._fullLayout._hasPie = false; manageModeBar(gd); var modeBar = gd._fullLayout._modeBar; @@ -327,10 +324,7 @@ describe('ModeBar', function() { var gd = getMockGraphInfo(); gd._fullLayout._hasCartesian = true; - gd._fullLayout._hasGL3D = false; gd._fullLayout._hasGeo = true; - gd._fullLayout._hasGL2D = false; - gd._fullLayout._hasPie = false; manageModeBar(gd); var modeBar = gd._fullLayout._modeBar; @@ -355,9 +349,6 @@ describe('ModeBar', function() { _module: {selectPoints: true} }]; gd._fullLayout.xaxis = {fixedrange: false}; - gd._fullLayout._hasGL3D = false; - gd._fullLayout._hasGeo = false; - gd._fullLayout._hasGL2D = false; gd._fullLayout._hasPie = true; manageModeBar(gd); @@ -373,11 +364,8 @@ describe('ModeBar', function() { ]); var gd = getMockGraphInfo(); - gd._fullLayout._hasCartesian = false; gd._fullLayout._hasGL3D = true; gd._fullLayout._hasGeo = true; - gd._fullLayout._hasGL2D = false; - gd._fullLayout._hasPie = false; manageModeBar(gd); var modeBar = gd._fullLayout._modeBar; From 0857b4449f521d3cc84b9f081118a68dbf01131c Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Sat, 2 Apr 2016 23:50:13 +0200 Subject: [PATCH 25/76] refactor ugly logic out of Titles.draw --- src/components/colorbar/draw.js | 143 ++++++++++++++++++++------- src/components/titles/index.js | 169 ++++++-------------------------- src/plot_api/plot_api.js | 13 ++- src/plots/cartesian/axes.js | 68 ++++++++++++- 4 files changed, 216 insertions(+), 177 deletions(-) diff --git a/src/components/colorbar/draw.js b/src/components/colorbar/draw.js index 20fc11fe224..30b2191d862 100644 --- a/src/components/colorbar/draw.js +++ b/src/components/colorbar/draw.js @@ -16,6 +16,7 @@ var Plots = require('../../plots/plots'); var Axes = require('../../plots/cartesian/axes'); var dragElement = require('../dragelement'); var Lib = require('../../lib'); +var extendFlat = require('../../lib/extend').extendFlat; var setCursor = require('../../lib/setcursor'); var Drawing = require('../drawing'); var Color = require('../color'); @@ -52,7 +53,8 @@ module.exports = function draw(gd, id) { opts.filllevels = null; function component() { - var fullLayout = gd._fullLayout; + var fullLayout = gd._fullLayout, + gs = fullLayout._size; if((typeof opts.fillcolor !== 'function') && (typeof opts.line.color !== 'function')) { fullLayout._infolayer.selectAll('g.'+id).remove(); @@ -116,17 +118,17 @@ module.exports = function draw(gd, id) { originalPlotWidth = fullLayout.width - fullLayout.margin.l - fullLayout.margin.r, thickPx = Math.round(opts.thickness * (opts.thicknessmode==='fraction' ? originalPlotWidth : 1)), - thickFrac = thickPx / fullLayout._size.w, + thickFrac = thickPx / gs.w, lenPx = Math.round(opts.len * (opts.lenmode==='fraction' ? originalPlotHeight : 1)), - lenFrac = lenPx / fullLayout._size.h, - xpadFrac = opts.xpad/fullLayout._size.w, + lenFrac = lenPx / gs.h, + xpadFrac = opts.xpad/gs.w, yExtraPx = (opts.borderwidth + opts.outlinewidth)/2, - ypadFrac = opts.ypad / fullLayout._size.h, + ypadFrac = opts.ypad / gs.h, // x positioning: do it initially just for left anchor, // then fix at the end (since we don't know the width yet) - xLeft = Math.round(opts.x*fullLayout._size.w + opts.xpad), + xLeft = Math.round(opts.x*gs.w + opts.xpad), // for dragging... this is getting a little muddled... xLeftFrac = opts.x - thickFrac * ({middle: 0.5, right: 1}[opts.xanchor]||0), @@ -134,7 +136,7 @@ module.exports = function draw(gd, id) { // y positioning we can do correctly from the start yBottomFrac = opts.y + lenFrac * (({top: -0.5, bottom: 0.5}[opts.yanchor] || 0) - 0.5), - yBottomPx = Math.round(fullLayout._size.h * (1-yBottomFrac)), + yBottomPx = Math.round(gs.h * (1-yBottomFrac)), yTopPx = yBottomPx-lenPx, titleEl, cbAxisIn = { @@ -243,22 +245,38 @@ module.exports = function draw(gd, id) { s.append('g').classed('cbtitleunshift',true) .append('g').classed('cbtitle',true); s.append('rect').classed('cboutline',true); + s.select('.cbtitle').datum(0); }); - container.attr('transform','translate('+Math.round(fullLayout._size.l)+ - ','+Math.round(fullLayout._size.t)+')'); + container.attr('transform','translate('+Math.round(gs.l)+ + ','+Math.round(gs.t)+')'); // TODO: this opposite transform is a hack until we make it // more rational which items get this offset var titleCont = container.select('.cbtitleunshift') .attr('transform', 'translate(-'+ - Math.round(fullLayout._size.l) + ',-' + - Math.round(fullLayout._size.t) + ')'); + Math.round(gs.l) + ',-' + + Math.round(gs.t) + ')'); cbAxisOut._axislayer = container.select('.cbaxis'); var titleHeight = 0; if(['top', 'bottom'].indexOf(opts.titleside) !== -1) { // draw the title so we know how much room it needs - // when we squish the axis - Titles.draw(gd, cbAxisOut._id + 'title'); + // when we squish the axis. This one only applies to + // top or bottom titles, not right side. + var x = gs.l + (opts.x + xpadFrac) * gs.w, + fontSize = cbAxisOut.titlefont.size, + y; + + if(opts.titleside === 'top') { + y = (1 - (yBottomFrac + lenFrac - ypadFrac)) * gs.h + + gs.t + 3 + fontSize * 0.75; + } + else { + y = (1 - (yBottomFrac + ypadFrac)) * gs.h + + gs.t - 3 - fontSize * 0.25; + } + drawTitle(cbAxisOut._id + 'title', { + attributes: {x: x, y: y, 'text-anchor': 'start'} + }); } function drawAxis() { @@ -295,11 +313,11 @@ module.exports = function draw(gd, id) { titleHeight += 5; if(opts.titleside==='top') { - cbAxisOut.domain[1] -= titleHeight/fullLayout._size.h; + cbAxisOut.domain[1] -= titleHeight/gs.h; titleTrans[1] *= -1; } else { - cbAxisOut.domain[0] += titleHeight/fullLayout._size.h; + cbAxisOut.domain[0] += titleHeight/gs.h; var nlines = Math.max(1, titleText.selectAll('tspan.line').size()); titleTrans[1] += (1-nlines)*lineSize; @@ -314,7 +332,7 @@ module.exports = function draw(gd, id) { container.selectAll('.cbfills,.cblines,.cbaxis') .attr('transform','translate(0,'+ - Math.round(fullLayout._size.h*(1-cbAxisOut.domain[1]))+')'); + Math.round(gs.h*(1-cbAxisOut.domain[1]))+')'); var fills = container.select('.cbfills') .selectAll('rect.cbfill') @@ -371,7 +389,67 @@ module.exports = function draw(gd, id) { (opts.outlinewidth||0)/2 - (opts.ticks==='outside' ? 1 : 0); cbAxisOut.side = 'right'; - return Axes.doTicks(gd, cbAxisOut); + // separate out axis and title drawing, + // so we don't need such complicated logic in Titles.draw + // if title is on the top or bottom, we've already drawn it + // this title call only handles side=right + return Lib.syncOrAsync([ + function() { + return Axes.doTicks(gd, cbAxisOut, true); + }, + function() { + if(['top','bottom'].indexOf(opts.titleside)===-1) { + var fontSize = cbAxisOut.titlefont.size, + y = cbAxisOut._offset + cbAxisOut._length / 2, + x = gs.l + (cbAxisOut.position || 0) * gs.w + ((cbAxisOut.side === 'right') ? + 10 + fontSize*((cbAxisOut.showticklabels ? 1 : 0.5)) : + -10 - fontSize*((cbAxisOut.showticklabels ? 0.5 : 0))); + + // the 'h' + is a hack to get around the fact that + // convertToTspans rotates any 'y...' class by 90 degrees. + // TODO: find a better way to control this. + drawTitle('h' + cbAxisOut._id + 'title', { + avoid: { + selection: d3.select(gd).selectAll('g.' + cbAxisOut._id + 'tick'), + side: opts.titleside, + offsetLeft: gs.l, + offsetTop: gs.t, + maxShift: fullLayout.width + }, + attributes: {x: x, y: y, 'text-anchor': 'middle'}, + transform: {rotate: '-90', offset: 0} + }); + } + }]); + } + + function drawTitle(titleClass, titleOpts) { + var trace = getTrace(), + propName; + if(Plots.traceIs(trace, 'markerColorscale')) { + propName = 'marker.colorbar.title'; + } + else propName = 'colorbar.title'; + + var dfltTitleOpts = { + propContainer: cbAxisOut, + propName: propName, + traceIndex: trace.index, + dfltName: 'colorscale', + containerGroup: container.select('.cbtitle') + }; + + // this class-to-rotate thing with convertToTspans is + // getting hackier and hackier... delete groups with the + // wrong class (in case earlier the colorbar was drawn on + // a different side, I think?) + var otherClass = titleClass.charAt(0) === 'h' ? + titleClass.substr(1) : ('h' + titleClass); + container.selectAll('.' + otherClass + ',.' + otherClass + '-math-group') + .remove(); + + Titles.draw(gd, titleClass, + extendFlat(dfltTitleOpts, titleOpts || {})); } function positionCB() { @@ -394,11 +472,11 @@ module.exports = function draw(gd, id) { else { // note: the formula below works for all titlesides, // (except for top/bottom mathjax, above) - // but the weird fullLayout._size.l is because the titleunshift + // but the weird gs.l is because the titleunshift // transform gets removed by Drawing.bBox titleWidth = Drawing.bBox(titleCont.node()).right - - xLeft - fullLayout._size.l; + xLeft - gs.l; } innerWidth = Math.max(innerWidth,titleWidth); } @@ -435,7 +513,7 @@ module.exports = function draw(gd, id) { var xoffset = ({center: 0.5, right: 1}[opts.xanchor] || 0) * outerwidth; container.attr('transform', - 'translate('+(fullLayout._size.l-xoffset)+','+fullLayout._size.t+')'); + 'translate('+(gs.l-xoffset)+','+gs.t+')'); //auto margin adjustment Plots.autoMargin(gd, id,{ @@ -470,8 +548,6 @@ module.exports = function draw(gd, id) { setCursor(container); }, moveFn: function(dx, dy) { - var gs = gd._fullLayout._size; - container.attr('transform', t0+' ' + 'translate('+dx+','+dy+')'); @@ -480,7 +556,7 @@ module.exports = function draw(gd, id) { yf = dragElement.align(yBottomFrac - (dy/gs.h), lenFrac, 0, 1, opts.yanchor); - var csr = dragElement.cursor(xf, yf, + var csr = dragElement.getCursor(xf, yf, opts.xanchor, opts.yanchor); setCursor(container, csr); }, @@ -488,18 +564,9 @@ module.exports = function draw(gd, id) { setCursor(container); if(dragged && xf!==undefined && yf!==undefined) { - var idNum = id.substr(2), - traceNum; - gd._fullData.some(function(trace) { - if(trace.uid===idNum) { - traceNum = trace.index; - return true; - } - }); - Plotly.restyle(gd, {'colorbar.x': xf, 'colorbar.y': yf}, - traceNum); + getTrace().index); } } }); @@ -507,6 +574,16 @@ module.exports = function draw(gd, id) { return cbDone; } + function getTrace() { + var idNum = id.substr(2), + i, + trace; + for(i = 0; i < gd._fullData.length; i++) { + trace = gd._fullData[i]; + if(trace.uid === idNum) return trace; + } + } + // setter/getters for every item defined in opts Object.keys(opts).forEach(function(name) { component[name] = function(v) { diff --git a/src/components/titles/index.js b/src/components/titles/index.js index 4fa0463669f..cb4a436188e 100644 --- a/src/components/titles/index.js +++ b/src/components/titles/index.js @@ -18,7 +18,6 @@ var Lib = require('../../lib'); var Drawing = require('../drawing'); var Color = require('../color'); var svgTextUtils = require('../../lib/svg_text_utils'); -var axisIds = require('../../plots/cartesian/axis_ids'); var Titles = module.exports = {}; @@ -27,116 +26,22 @@ var Titles = module.exports = {}; * Titles - (re)draw titles on the axes and plot: * title can be 'xtitle', 'ytitle', 'gtitle' */ -Titles.draw = function(gd, title) { - var fullLayout = gd._fullLayout, - gs = fullLayout._size, - axletter = title.charAt(0), - colorbar = (title.substr(1, 2) === 'cb'); - - var cbnum, cont, options; - - if(colorbar) { - var uid = title.substr(3).replace('title', ''); - gd._fullData.some(function(trace, i) { - if(trace.uid === uid) { - cbnum = i; - cont = gd.calcdata[i][0].t.cb.axis; - return true; - } - }); - } - else cont = fullLayout[axisIds.id2name(title.replace('title', ''))] || fullLayout; - - var prop = (cont === fullLayout) ? 'title' : cont._name+'.title', - name = colorbar ? 'colorscale' : - ((cont._id || axletter).toUpperCase()+' axis'), +Titles.draw = function(gd, titleClass, options) { + var cont = options.propContainer, + prop = options.propName, + traceIndex = options.traceIndex, + name = options.dfltName, + avoid = options.avoid || {}, + attributes = options.attributes, + transform = options.transform, + group = options.containerGroup, + + fullLayout = gd._fullLayout, font = cont.titlefont.family, fontSize = cont.titlefont.size, fontColor = cont.titlefont.color, - x, - y, - transform = '', - xa, - ya, - avoid = { - selection: d3.select(gd).selectAll('g.'+cont._id+'tick'), - side: cont.side - }, - // multiples of fontsize to offset label from axis - offsetBase = colorbar ? 0 : 1.5, - avoidTransform; - // find the transform applied to the parents of the avoid selection - // which doesn't get picked up by Drawing.bBox - if(colorbar) { - avoid.offsetLeft = gs.l; - avoid.offsetTop = gs.t; - } - else if(avoid.selection.size()) { - avoidTransform = d3.select(avoid.selection.node().parentNode) - .attr('transform') - .match(/translate\(([-\.\d]+),([-\.\d]+)\)/); - if(avoidTransform) { - avoid.offsetLeft = +avoidTransform[1]; - avoid.offsetTop = +avoidTransform[2]; - } - } - - if(colorbar && cont.titleside) { - // argh, we only make it here if the title is on top or bottom, - // not right - x = gs.l + cont.titlex * gs.w; - y = gs.t + (1 - cont.titley) * gs.h + ((cont.titleside === 'top') ? - 3 + fontSize * 0.75 : - 3 - fontSize * 0.25); - options = {x: x, y: y, 'text-anchor': 'start'}; - avoid = {}; - - // convertToTspans rotates any 'y...' by 90 degrees... - // TODO: need a better solution than this hack - title = 'h' + title; - } - else if(axletter === 'x') { - xa = cont; - ya = xa._counteraxis || ((xa.anchor === 'free') ? - {_offset: gs.t + (1 - (xa.position || 0)) * gs.h, _length: 0} : - axisIds.getFromId(gd, xa.anchor)); - - x = xa._offset + xa._length / 2; - y = ya._offset + ((xa.side === 'top') ? - -10 - fontSize*(offsetBase + (xa.showticklabels ? 1 : 0)) : - ya._length + 10 + - fontSize*(offsetBase + (xa.showticklabels ? 1.5 : 0.5))); - - options = {x: x, y: y, 'text-anchor': 'middle'}; - if(!avoid.side) avoid.side = 'bottom'; - } - else if(axletter === 'y') { - ya = cont; - xa = ya._counteraxis || ((ya.anchor === 'free') ? - {_offset: gs.l + (ya.position || 0) * gs.w, _length: 0} : - axisIds.getFromId(gd, ya.anchor)); - - y = ya._offset + ya._length / 2; - x = xa._offset + ((ya.side === 'right') ? - xa._length + 10 + - fontSize*(offsetBase + (ya.showticklabels ? 1 : 0.5)) : - -10 - fontSize*(offsetBase + (ya.showticklabels ? 0.5 : 0))); - - options = {x: x, y: y, 'text-anchor': 'middle'}; - transform = {rotate: '-90', offset: 0}; - if(!avoid.side) avoid.side = 'left'; - } - else { - // plot title - name = 'Plot'; - fontSize = fullLayout.titlefont.size; - x = fullLayout.width / 2; - y = fullLayout._size.t / 2; - options = {x: x, y: y, 'text-anchor': 'middle'}; - avoid = {}; - } - - var opacity = 1, + opacity = 1, isplaceholder = false, txt = cont.title.trim(); if(txt === '') opacity = 0; @@ -145,23 +50,11 @@ Titles.draw = function(gd, title) { isplaceholder = true; } - var group; - if(colorbar) { - group = d3.select(gd) - .selectAll('.' + cont._id.substr(1) + ' .cbtitle'); - // this class-to-rotate thing with convertToTspans is - // getting hackier and hackier... delete groups with the - // wrong class - var otherClass = title.charAt(0) === 'h' ? - title.substr(1) : ('h' + title); - group.selectAll('.' + otherClass + ',.' + otherClass + '-math-group') - .remove(); - } - else { - group = fullLayout._infolayer.selectAll('.g-' + title) + if(!group) { + group = fullLayout._infolayer.selectAll('.g-' + titleClass) .data([0]); group.enter().append('g') - .classed('g-' + title, true); + .classed('g-' + titleClass, true); } var el = group.selectAll('text') @@ -173,7 +66,7 @@ Titles.draw = function(gd, title) { // so we need to clear out any old class and put the // correct one (only relevant for colorbars, at least // for now) - ie don't use .classed - .attr('class', title); + .attr('class', titleClass); function titleLayout(titleEl) { Lib.syncOrAsync([drawTitle,scootTitle], titleEl); @@ -181,7 +74,7 @@ Titles.draw = function(gd, title) { function drawTitle(titleEl) { titleEl.attr('transform', transform ? - 'rotate(' + [transform.rotate, options.x, options.y] + + 'rotate(' + [transform.rotate, attributes.x, attributes.y] + ') translate(0, ' + transform.offset + ')' : null); @@ -192,12 +85,12 @@ Titles.draw = function(gd, title) { opacity: opacity * Color.opacity(fontColor), 'font-weight': Plots.fontWeight }) - .attr(options) + .attr(attributes) .call(svgTextUtils.convertToTspans) - .attr(options); + .attr(attributes); titleEl.selectAll('tspan.line') - .attr(options); + .attr(attributes); return Plots.previousPromises(gd); } @@ -226,9 +119,9 @@ Titles.draw = function(gd, title) { right: fullLayout.width, bottom: fullLayout.height }, - maxshift = colorbar ? fullLayout.width: + maxshift = avoid.maxShift || ( (paperbb[avoid.side]-titlebb[avoid.side]) * - ((avoid.side === 'left' || avoid.side === 'top') ? -1 : 1); + ((avoid.side === 'left' || avoid.side === 'top') ? -1 : 1)); // Prevent the title going off the paper if(maxshift < 0) shift = maxshift; else { @@ -267,13 +160,13 @@ Titles.draw = function(gd, title) { el.attr({'data-unformatted': txt}) .call(titleLayout); - var placeholderText = 'Click to enter ' + name.replace(/\d+/, '') + ' title'; + var placeholderText = 'Click to enter ' + name + ' title'; function setPlaceholder() { opacity = 0; isplaceholder = true; txt = placeholderText; - fullLayout._infolayer.select('.' + title) + fullLayout._infolayer.select('.' + titleClass) .attr({'data-unformatted': txt}) .text(txt) .on('mouseover.opacity', function() { @@ -291,23 +184,17 @@ Titles.draw = function(gd, title) { el.call(svgTextUtils.makeEditable) .on('edit', function(text) { - if(colorbar) { - var trace = gd._fullData[cbnum]; - if(Plots.traceIs(trace, 'markerColorscale')) { - Plotly.restyle(gd, 'marker.colorbar.title', text, cbnum); - } - else Plotly.restyle(gd, 'colorbar.title', text, cbnum); - } - else Plotly.relayout(gd,prop,text); + if(traceIndex !== undefined) Plotly.restyle(gd, prop, text, traceIndex); + else Plotly.relayout(gd, prop, text); }) .on('cancel', function() { this.text(this.attr('data-unformatted')) .call(titleLayout); }) .on('input', function(d) { - this.text(d || ' ').attr(options) + this.text(d || ' ').attr(attributes) .selectAll('tspan.line') - .attr(options); + .attr(attributes); }); } else if(!txt || txt.match(/Click to enter .+ title/)) { diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index 9380c9ede4c..1daefee148e 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -2953,5 +2953,16 @@ function lsInner(gd) { } function drawMainTitle(gd) { - Titles.draw(gd, 'gtitle'); + var fullLayout = gd._fullLayout; + + Titles.draw(gd, 'gtitle', { + propContainer: fullLayout, + propName: 'title', + dfltName: 'Plot', + attributes: { + x: fullLayout.width / 2, + y: fullLayout._size.t / 2, + 'text-anchor': 'middle' + } + }); } diff --git a/src/plots/cartesian/axes.js b/src/plots/cartesian/axes.js index 5b49b52f8f6..5cd84621fc3 100644 --- a/src/plots/cartesian/axes.js +++ b/src/plots/cartesian/axes.js @@ -1625,7 +1625,7 @@ axes.doTicks = function(td, axid, skipTitle) { // (so it can move out of the way if needed) // TODO: separate out scoot so we don't need to do // a full redraw of the title (mostly relevant for MathJax) - if(!skipTitle) drawAxTitle(axid); + drawAxTitle(axid); return axid+' done'; } @@ -1638,7 +1638,71 @@ axes.doTicks = function(td, axid, skipTitle) { } function drawAxTitle(axid) { - Titles.draw(td, axid + 'title'); + if(skipTitle) return; + + // now this only applies to regular cartesian axes; colorbars and + // others ALWAYS call doTicks with skipTitle=true so they can + // configure their own titles. + var ax = axisIds.getFromId(td, axid), + avoidSelection = d3.select(td).selectAll('g.' + axid + 'tick'), + avoid = { + selection: avoidSelection, + side: ax.side + }, + axLetter = axid.charAt(0), + gs = td._fullLayout._size, + offsetBase = 1.5, + fontSize = ax.titlefont.size, + transform, + counterAxis, + x, + y; + if(avoidSelection.size()) { + var avoidTransform = d3.select(avoidSelection.node().parentNode) + .attr('transform') + .match(/translate\(([-\.\d]+),([-\.\d]+)\)/); + if(avoidTransform) { + avoid.offsetLeft = +avoidTransform[1]; + avoid.offsetTop = +avoidTransform[2]; + } + } + + if(axLetter === 'x') { + counterAxis = (ax.anchor === 'free') ? + {_offset: gs.t + (1 - (ax.position || 0)) * gs.h, _length: 0} : + axisIds.getFromId(td, ax.anchor); + + x = ax._offset + ax._length / 2; + y = counterAxis._offset + ((ax.side === 'top') ? + -10 - fontSize*(offsetBase + (ax.showticklabels ? 1 : 0)) : + counterAxis._length + 10 + + fontSize*(offsetBase + (ax.showticklabels ? 1.5 : 0.5))); + + if(!avoid.side) avoid.side = 'bottom'; + } + else { + counterAxis = (ax.anchor === 'free') ? + {_offset: gs.l + (ax.position || 0) * gs.w, _length: 0} : + axisIds.getFromId(td, ax.anchor); + + y = ax._offset + ax._length / 2; + x = counterAxis._offset + ((ax.side === 'right') ? + counterAxis._length + 10 + + fontSize*(offsetBase + (ax.showticklabels ? 1 : 0.5)) : + -10 - fontSize*(offsetBase + (ax.showticklabels ? 0.5 : 0))); + + transform = {rotate: '-90', offset: 0}; + if(!avoid.side) avoid.side = 'left'; + } + + Titles.draw(td, axid + 'title', { + propContainer: ax, + propName: ax._name + '.title', + dfltName: axLetter.toUpperCase() + ' axis', + avoid: avoid, + transform: transform, + attributes: {x: x, y: y, 'text-anchor': 'middle'} + }); } function traceHasBarsOrFill(trace, subplot) { From c75051d56a790c24e7a0137457484ed8b0701086 Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Sun, 3 Apr 2016 01:10:57 +0200 Subject: [PATCH 26/76] ternary axis titles --- src/plots/ternary/layout/axis_defaults.js | 12 ++-- src/plots/ternary/ternary.js | 68 ++++++++++++++++++++--- 2 files changed, 66 insertions(+), 14 deletions(-) diff --git a/src/plots/ternary/layout/axis_defaults.js b/src/plots/ternary/layout/axis_defaults.js index c5fd79b2efa..c861e6a3ea9 100644 --- a/src/plots/ternary/layout/axis_defaults.js +++ b/src/plots/ternary/layout/axis_defaults.js @@ -27,14 +27,14 @@ module.exports = function supplyLayoutDefaults(containerIn, containerOut, option containerOut.type = 'linear'; // no other types allowed for ternary - var axName = containerOut._name; - var dfltColor = coerce('color'); - var title = coerce('title', 'Click to enter component ' + - axName.charAt(0).toUpperCase() + ' title'); - containerOut._hovertitle = title.indexOf('Click to enter') === -1 ? - title : axName.charAt(0); + var axName = containerOut._name, + letterUpper = axName.charAt(0).toUpperCase(), + dfltTitle = 'Component ' + letterUpper; + + var title = coerce('title', dfltTitle); + containerOut._hovertitle = title === dfltTitle ? title : letterUpper; Lib.coerceFont(coerce, 'titlefont', { family: options.font.family, diff --git a/src/plots/ternary/ternary.js b/src/plots/ternary/ternary.js index 61ec72afe9d..f13f9932139 100644 --- a/src/plots/ternary/ternary.js +++ b/src/plots/ternary/ternary.js @@ -21,6 +21,7 @@ var extendFlat = require('../../lib/extend').extendFlat; var Axes = require('../cartesian/axes'); var filterVisible = require('../../lib/filter_visible'); var dragElement = require('../../components/dragElement'); +var Titles = require('../../components/titles'); var prepSelect = require('../cartesian/select'); var constants = require('../cartesian/constants'); var fx = require('../cartesian/graph_interact'); @@ -133,7 +134,7 @@ proto.makeFramework = function() { 'grids', 'frontplot', 'zoom', - 'aaxis', 'baxis', 'caxis','axlines' + 'aaxis', 'baxis', 'caxis', 'axlines' ]; var toplevel = _this.plotContainer.selectAll('g.toplevel') .data(plotLayers); @@ -322,10 +323,7 @@ proto.adjustLayout = function(ternaryLayout, graphSize) { _this.layers.caxis.attr('transform', cTransform); _this.layers.cgrid.attr('transform', cTransform); - // TODO: titles (3rd arg true below ignores them) - Axes.doTicks(_this.graphDiv, aaxis, true); - Axes.doTicks(_this.graphDiv, baxis, true); - Axes.doTicks(_this.graphDiv, caxis, true); + _this.drawAxes(true); // remove crispEdges - all the off-square angles in ternary plots // make these counterproductive. @@ -349,6 +347,62 @@ proto.adjustLayout = function(ternaryLayout, graphSize) { .style('stroke-width', (caxis.linewidth || 0) + 'px'); }; +proto.drawAxes = function(doTitles) { + var _this = this, + gd = _this.graphDiv, + titlesuffix = _this.id.substr(7) + 'title', + aaxis = _this.aaxis, + baxis = _this.baxis, + caxis = _this.caxis; + // 3rd arg true below skips titles, so we can configure them + // correctly later on. + Axes.doTicks(gd, aaxis, true); + Axes.doTicks(gd, baxis, true); + Axes.doTicks(gd, caxis, true); + + if(doTitles) { + var apad = Math.max(aaxis.showticklabels ? aaxis.tickfont.size / 2 : 0, + (caxis.showticklabels ? caxis.tickfont.size * 0.75 : 0) + + (caxis.ticks === 'outside' ? caxis.ticklen * 0.87 : 0)); + Titles.draw(gd, 'a' + titlesuffix, { + propContainer: aaxis, + propName: _this.id + '.aaxis.title', + dfltName: 'Component A', + attributes: { + x: _this.x0 + _this.w / 2, + y: _this.y0 - aaxis.titlefont.size / 3 - apad, + 'text-anchor': 'middle' + } + }); + + var btitleSize = baxis.titlefont.size, + bpad = (baxis.showticklabels ? baxis.tickfont.size : 0) + + (baxis.ticks === 'outside' ? baxis.ticklen : 0) + 3; + Titles.draw(gd, 'b' + titlesuffix, { + propContainer: baxis, + propName: _this.id + '.baxis.title', + dfltName: 'Component B', + attributes: { + x: _this.x0 - bpad, + y: _this.y0 + _this.h + btitleSize * 0.83 + bpad, + 'text-anchor': 'middle' + } + }); + + var ctitleSize = caxis.titlefont.size; + Titles.draw(gd, 'c' + titlesuffix, { + propContainer: caxis, + propName: _this.id + '.caxis.title', + dfltName: 'Component C', + attributes: { + x: _this.x0 + _this.w + bpad, + y: _this.y0 + _this.h + ctitleSize * 0.83 + bpad, + 'text-anchor': 'middle' + } + }); + } +}; + // hard coded paths for zoom corners // uses the same sizing as cartesian, length is MINZOOM/2, width is 3px var CLEN = constants.MINZOOM / 2 + 0.87; @@ -572,9 +626,7 @@ proto.init_interactions = function() { _this.baxis.range = [_this.sum - mins.a - mins.c, mins.b]; _this.caxis.range = [_this.sum - mins.a - mins.b, mins.c]; - Axes.doTicks(_this.graphDiv, _this.aaxis, true); - Axes.doTicks(_this.graphDiv, _this.baxis, true); - Axes.doTicks(_this.graphDiv, _this.caxis, true); + _this.drawAxes(false); _this.plotContainer.selectAll('.crisp').classed('crisp', false); } From 07e8502b5d39ed1a324e7131e21fab67a577ed0b Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Sun, 3 Apr 2016 02:00:11 +0200 Subject: [PATCH 27/76] oops, something weird happened in merging axes.js - fix it --- src/plots/cartesian/axes.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/plots/cartesian/axes.js b/src/plots/cartesian/axes.js index 4c39f7b3d11..8ece731ed8c 100644 --- a/src/plots/cartesian/axes.js +++ b/src/plots/cartesian/axes.js @@ -1373,10 +1373,11 @@ axes.doTicks = function(gd, axid, skipTitle) { pad = (ax.linewidth||1) / 2, labelStandoff = (ax.ticks==='outside' ? ax.ticklen : 1) + (ax.linewidth||0), + labelShift = 0, gridWidth = Plotly.Drawing.crispRound(gd, ax.gridwidth, 1), zeroLineWidth = Plotly.Drawing.crispRound(gd, ax.zerolinewidth, gridWidth), tickWidth = Plotly.Drawing.crispRound(gd, ax.tickwidth, 1), - sides, transfn, tickprefix, tickmid, + sides, transfn, tickpathfn, i; if(ax._counterangle && ax.ticks==='outside') { From c9ee322c7b91d61f30a0c2f73442fcb8a3c58ad2 Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Sun, 3 Apr 2016 02:10:46 +0200 Subject: [PATCH 28/76] _td -> _gd --- src/components/colorbar/draw.js | 2 +- src/plots/cartesian/axes.js | 8 ++++---- src/plots/cartesian/set_convert.js | 6 +++--- src/plots/geo/geo.js | 2 +- src/plots/gl3d/index.js | 2 +- src/plots/plots.js | 2 +- src/plots/ternary/ternary.js | 10 +++++----- src/traces/bar/hover.js | 2 +- 8 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/components/colorbar/draw.js b/src/components/colorbar/draw.js index 30b2191d862..6479c5f347e 100644 --- a/src/components/colorbar/draw.js +++ b/src/components/colorbar/draw.js @@ -185,7 +185,7 @@ module.exports = function draw(gd, id) { handleAxisPositionDefaults(cbAxisIn, cbAxisOut, coerce, axisOptions); cbAxisOut._id = 'y' + id; - cbAxisOut._td = gd; + cbAxisOut._gd = gd; // position can't go in through supplyDefaults // because that restricts it to [0,1] diff --git a/src/plots/cartesian/axes.js b/src/plots/cartesian/axes.js index 8ece731ed8c..8a453fb37d8 100644 --- a/src/plots/cartesian/axes.js +++ b/src/plots/cartesian/axes.js @@ -187,8 +187,8 @@ axes.doAutoRange = function(ax) { // doAutoRange will get called on fullLayout, // but we want to report its results back to layout - var axIn = ax._td.layout[ax._name]; - if(!axIn) ax._td.layout[ax._name] = axIn = {}; + var axIn = ax._gd.layout[ax._name]; + if(!axIn) ax._gd.layout[ax._name] = axIn = {}; if(axIn!==ax) { axIn.range = ax.range.slice(); axIn.autorange = ax.autorange; @@ -884,7 +884,7 @@ axes.tickText = function(ax, x, hover) { }; function tickTextObj(ax, x, text) { - var tf = ax.tickfont || ax._td._fullLayout.font; + var tf = ax.tickfont || ax._gd._fullLayout.font; return { x: x, @@ -1085,7 +1085,7 @@ function numFormat(v, ax, fmtoverride, hover) { if(dp) v = v.substr(0, dp + tickRound).replace(/\.?0+$/, ''); } // insert appropriate decimal point and thousands separator - v = numSeparate(v, ax._td._fullLayout.separators); + v = numSeparate(v, ax._gd._fullLayout.separators); } // add exponent diff --git a/src/plots/cartesian/set_convert.js b/src/plots/cartesian/set_convert.js index 57574b9fc8d..e35c039429e 100644 --- a/src/plots/cartesian/set_convert.js +++ b/src/plots/cartesian/set_convert.js @@ -65,7 +65,7 @@ module.exports = function setConvert(ax) { // set scaling to pixels ax.setScale = function() { - var gs = ax._td._fullLayout._size, + var gs = ax._gd._fullLayout._size, i; // TODO cleaner way to handle this case @@ -74,7 +74,7 @@ module.exports = function setConvert(ax) { // make sure we have a domain (pull it in from the axis // this one is overlaying if necessary) if(ax.overlaying) { - var ax2 = axisIds.getFromId(ax._td, ax.overlaying); + var ax2 = axisIds.getFromId(ax._gd, ax.overlaying); ax.domain = ax2.domain; } @@ -116,7 +116,7 @@ module.exports = function setConvert(ax) { Lib.notifier( 'Something went wrong with axis scaling', 'long'); - ax._td._replotting = false; + ax._gd._replotting = false; throw new Error('axis scaling'); } }; diff --git a/src/plots/geo/geo.js b/src/plots/geo/geo.js index d46bd7010e9..8161a965e23 100644 --- a/src/plots/geo/geo.js +++ b/src/plots/geo/geo.js @@ -488,7 +488,7 @@ function createMockAxis(fullLayout) { type: 'linear', showexponent: 'all', exponentformat: Axes.layoutAttributes.exponentformat.dflt, - _td: { _fullLayout: fullLayout } + _gd: { _fullLayout: fullLayout } }; Axes.setConvert(mockAxis); diff --git a/src/plots/gl3d/index.js b/src/plots/gl3d/index.js index 032b0714e30..d0286ee52c7 100644 --- a/src/plots/gl3d/index.js +++ b/src/plots/gl3d/index.js @@ -102,7 +102,7 @@ exports.initAxes = function(gd) { for(var j = 0; j < 3; ++j) { var axisName = axesNames[j]; var ax = sceneLayout[axisName]; - ax._td = gd; + ax._gd = gd; } } }; diff --git a/src/plots/plots.js b/src/plots/plots.js index 2831e0beb0c..7f9fcfdbc3e 100644 --- a/src/plots/plots.js +++ b/src/plots/plots.js @@ -509,7 +509,7 @@ plots.supplyDefaults = function(gd) { axList = Plotly.Axes.list(gd); for(i = 0; i < axList.length; i++) { ax = axList[i]; - ax._td = gd; + ax._gd = gd; ax.setScale(); } diff --git a/src/plots/ternary/ternary.js b/src/plots/ternary/ternary.js index f13f9932139..79102d09907 100644 --- a/src/plots/ternary/ternary.js +++ b/src/plots/ternary/ternary.js @@ -220,7 +220,7 @@ proto.adjustLayout = function(ternaryLayout, graphSize) { xDomainCenter + xDomainFinal / 2 ], _id: 'x', - _td: _this.graphDiv + _gd: _this.graphDiv }; setConvert(_this.xaxis); _this.xaxis.setScale(); @@ -233,7 +233,7 @@ proto.adjustLayout = function(ternaryLayout, graphSize) { yDomainCenter + yDomainFinal / 2 ], _id: 'y', - _td: _this.graphDiv + _gd: _this.graphDiv }; setConvert(_this.yaxis); _this.yaxis.setScale(); @@ -255,7 +255,7 @@ proto.adjustLayout = function(ternaryLayout, graphSize) { _axislayer: _this.layers.aaxis, _gridlayer: _this.layers.agrid, _pos: 0, //_this.xaxis.domain[0] * graphSize.w, - _td: _this.graphDiv, + _gd: _this.graphDiv, _id: 'y', _length: w, _gridpath: 'M0,0l' + h + ',-' + (w / 2) @@ -273,7 +273,7 @@ proto.adjustLayout = function(ternaryLayout, graphSize) { _gridlayer: _this.layers.bgrid, _counteraxis: _this.aaxis, _pos: 0, //(1 - yDomain0) * graphSize.h, - _td: _this.graphDiv, + _gd: _this.graphDiv, _id: 'x', _length: w, _gridpath: 'M0,0l-' + (w / 2) + ',-' + h @@ -293,7 +293,7 @@ proto.adjustLayout = function(ternaryLayout, graphSize) { _gridlayer: _this.layers.cgrid, _counteraxis: _this.baxis, _pos: 0, //_this.xaxis.domain[1] * graphSize.w, - _td: _this.graphDiv, + _gd: _this.graphDiv, _id: 'y', _length: w, _gridpath: 'M0,0l-' + h + ',' + (w / 2) diff --git a/src/traces/bar/hover.js b/src/traces/bar/hover.js index a667baaec02..673edd2e4ce 100644 --- a/src/traces/bar/hover.js +++ b/src/traces/bar/hover.js @@ -21,7 +21,7 @@ module.exports = function hoverPoints(pointData, xval, yval, hovermode) { xa = pointData.xa, ya = pointData.ya, barDelta = (hovermode==='closest') ? - t.barwidth/2 : t.dbar*(1-xa._td._fullLayout.bargap)/2, + t.barwidth/2 : t.dbar*(1-xa._gd._fullLayout.bargap)/2, barPos; if(hovermode!=='closest') barPos = function(di) { return di.p; }; From c88083c9a12a8dd9c0e1fed0cc03b8300e003699 Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Sun, 3 Apr 2016 22:33:39 +0200 Subject: [PATCH 29/76] fix dragElement requires --- src/plots/cartesian/graph_interact.js | 2 +- src/plots/ternary/ternary.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plots/cartesian/graph_interact.js b/src/plots/cartesian/graph_interact.js index 4f6b9374252..32e0969836e 100644 --- a/src/plots/cartesian/graph_interact.js +++ b/src/plots/cartesian/graph_interact.js @@ -19,7 +19,7 @@ var Events = require('../../lib/events'); var constants = require('./constants'); var dragBox = require('./dragbox'); -var dragElement = require('../../components/dragElement'); +var dragElement = require('../../components/dragelement'); var fx = module.exports = {}; diff --git a/src/plots/ternary/ternary.js b/src/plots/ternary/ternary.js index 79102d09907..9a386cf7355 100644 --- a/src/plots/ternary/ternary.js +++ b/src/plots/ternary/ternary.js @@ -20,7 +20,7 @@ var setConvert = require('../cartesian/set_convert'); var extendFlat = require('../../lib/extend').extendFlat; var Axes = require('../cartesian/axes'); var filterVisible = require('../../lib/filter_visible'); -var dragElement = require('../../components/dragElement'); +var dragElement = require('../../components/dragelement'); var Titles = require('../../components/titles'); var prepSelect = require('../cartesian/select'); var constants = require('../cartesian/constants'); From 0087ab8a661177fb9a6e63504dc719051d08bf17 Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Sun, 3 Apr 2016 22:44:36 +0200 Subject: [PATCH 30/76] bow to the :cow2: --- src/lib/coerce.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/coerce.js b/src/lib/coerce.js index d5489f8c219..c00c510295c 100644 --- a/src/lib/coerce.js +++ b/src/lib/coerce.js @@ -16,7 +16,7 @@ var nestedProperty = require('./nested_property'); var getColorscale = require('../components/colorscale/get_scale'); var colorscaleNames = Object.keys(require('../components/colorscale/scales')); -var idRegex = /^([2-9]|[1-9][0-9]+)$/ +var idRegex = /^([2-9]|[1-9][0-9]+)$/; exports.valObjects = { data_array: { From 9dd02eecb2e137247df7adae47cc8252b0f9df58 Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Sun, 3 Apr 2016 23:39:02 +0200 Subject: [PATCH 31/76] fix interaction between axis.color and layout.font.color --- src/plots/cartesian/axis_defaults.js | 5 ++++- src/plots/cartesian/tick_label_defaults.js | 6 +++++- src/plots/ternary/layout/axis_defaults.js | 7 +++++-- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/plots/cartesian/axis_defaults.js b/src/plots/cartesian/axis_defaults.js index 6fd557ad0a2..b911e28a284 100644 --- a/src/plots/cartesian/axis_defaults.js +++ b/src/plots/cartesian/axis_defaults.js @@ -74,12 +74,15 @@ module.exports = function handleAxisDefaults(containerIn, containerOut, coerce, setConvert(containerOut); var dfltColor = coerce('color'); + // if axis.color was provided, use it for fonts too; otherwise, + // inherit from global font color in case that was provided. + var dfltFontColor = (dfltColor === containerIn.color) ? dfltColor : font.color; coerce('title', defaultTitle); Lib.coerceFont(coerce, 'titlefont', { family: font.family, size: Math.round(font.size * 1.2), - color: dfltColor + color: dfltFontColor }); var validRange = ( diff --git a/src/plots/cartesian/tick_label_defaults.js b/src/plots/cartesian/tick_label_defaults.js index fdae2f0fbad..11608362711 100644 --- a/src/plots/cartesian/tick_label_defaults.js +++ b/src/plots/cartesian/tick_label_defaults.js @@ -27,10 +27,14 @@ module.exports = function handleTickLabelDefaults(containerIn, containerOut, coe var showTickLabels = coerce('showticklabels'); if(showTickLabels) { var font = options.font || {}; + // as with titlefont.color, inherit axis.color only if one was + // explicitly provided + var dfltFontColor = (containerOut.color === containerIn.color) ? + containerOut.color : font.color; Lib.coerceFont(coerce, 'tickfont', { family: font.family, size: font.size, - color: containerOut.color || font.color + color: dfltFontColor }); coerce('tickangle'); diff --git a/src/plots/ternary/layout/axis_defaults.js b/src/plots/ternary/layout/axis_defaults.js index c861e6a3ea9..e00b17641b5 100644 --- a/src/plots/ternary/layout/axis_defaults.js +++ b/src/plots/ternary/layout/axis_defaults.js @@ -28,6 +28,9 @@ module.exports = function supplyLayoutDefaults(containerIn, containerOut, option containerOut.type = 'linear'; // no other types allowed for ternary var dfltColor = coerce('color'); + // if axis.color was provided, use it for fonts too; otherwise, + // inherit from global font color in case that was provided. + var dfltFontColor = (dfltColor === containerIn.color) ? dfltColor : options.font.color; var axName = containerOut._name, letterUpper = axName.charAt(0).toUpperCase(), @@ -39,7 +42,7 @@ module.exports = function supplyLayoutDefaults(containerIn, containerOut, option Lib.coerceFont(coerce, 'titlefont', { family: options.font.family, size: Math.round(options.font.size * 1.2), - color: dfltColor + color: dfltFontColor }); // range is just set by 'min' - max is determined by the other axes mins @@ -57,7 +60,7 @@ module.exports = function supplyLayoutDefaults(containerIn, containerOut, option Lib.coerceFont(coerce, 'tickfont', { family: options.font.family, size: options.font.size, - color: containerOut.color + color: dfltFontColor }); coerce('tickangle'); coerce('tickformat'); From ee28402c274e1728f826d5090864f8da1621af19 Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Mon, 4 Apr 2016 10:33:11 +0200 Subject: [PATCH 32/76] linting / comment fixing --- src/components/dragelement/index.js | 2 +- src/components/dragelement/unhover.js | 2 +- src/components/errorbars/plot.js | 4 ++++ src/plots/cartesian/graph_interact.js | 18 ++++++++---------- src/plots/gl3d/layout/axis_defaults.js | 3 --- src/traces/scatter/line_shape_defaults.js | 2 +- src/traces/scatterternary/attributes.js | 2 +- 7 files changed, 16 insertions(+), 17 deletions(-) diff --git a/src/components/dragelement/index.js b/src/components/dragelement/index.js index 6c441beff3b..5213ccf7c14 100644 --- a/src/components/dragelement/index.js +++ b/src/components/dragelement/index.js @@ -42,7 +42,7 @@ dragElement.getCursor = require('./cursor'); var unhover = require('./unhover'); dragElement.unhover = unhover.wrapped; -dragElement.unhover_raw = unhover.raw; +dragElement.unhoverRaw = unhover.raw; dragElement.init = function init(options) { var gd = Lib.getPlotDiv(options.element) || {}, diff --git a/src/components/dragelement/unhover.js b/src/components/dragelement/unhover.js index 975c4c51596..90a09b4a4ba 100644 --- a/src/components/dragelement/unhover.js +++ b/src/components/dragelement/unhover.js @@ -28,7 +28,7 @@ unhover.wrapped = function(gd, evt, subplot) { // remove hover effects on mouse out, and emit unhover event -unhover.raw = function unhover_raw(gd, evt) { +unhover.raw = function unhoverRaw(gd, evt) { var fullLayout = gd._fullLayout; if(!evt) evt = {}; if(evt.target && diff --git a/src/components/errorbars/plot.js b/src/components/errorbars/plot.js index 49e20445f25..dd05c65e7df 100644 --- a/src/components/errorbars/plot.js +++ b/src/components/errorbars/plot.js @@ -22,6 +22,10 @@ module.exports = function plot(traces, plotinfo) { traces.each(function(d) { var trace = d[0].trace, + // || {} is in case the trace (specifically scatterternary) + // doesn't support error bars at all, but does go through + // the scatter.plot mechanics, which calls ErrorBars.plot + // internally xObj = trace.error_x || {}, yObj = trace.error_y || {}; diff --git a/src/plots/cartesian/graph_interact.js b/src/plots/cartesian/graph_interact.js index 32e0969836e..cfca5b4b7cf 100644 --- a/src/plots/cartesian/graph_interact.js +++ b/src/plots/cartesian/graph_interact.js @@ -313,22 +313,20 @@ function hover(gd, evt, subplot) { .map(function(pi) { return pi.id; })), xaArray = subplots.map(function(spId) { - if(spId.substr(0,7) === 'ternary') { - return gd._fullLayout[spId]._ternary.xaxis; - } + var ternary = gd._fullLayout[spId]._ternary; + if(ternary) return ternary.xaxis; return Plotly.Axes.getFromId(gd, spId, 'x'); }), yaArray = subplots.map(function(spId) { - if(spId.substr(0,7) === 'ternary') { - return gd._fullLayout[spId]._ternary.yaxis; - } + var ternary = gd._fullLayout[spId]._ternary; + if(ternary) return ternary.xaxis; return Plotly.Axes.getFromId(gd, spId, 'y'); }), hovermode = evt.hovermode || fullLayout.hovermode; if(['x','y','closest'].indexOf(hovermode)===-1 || !gd.calcdata || gd.querySelector('.zoombox') || gd._dragging) { - return dragElement.unhover_raw(gd, evt); + return dragElement.unhoverRaw(gd, evt); } // hoverData: the set of candidate points we've found to highlight @@ -400,7 +398,7 @@ function hover(gd, evt, subplot) { // in case hover was called from mouseout into hovertext, // it's possible you're not actually over the plot anymore if(xpx<0 || xpx>dbb.width || ypx<0 || ypx>dbb.height) { - return dragElement.unhover_raw(gd,evt); + return dragElement.unhoverRaw(gd,evt); } } else { @@ -419,7 +417,7 @@ function hover(gd, evt, subplot) { if(!isNumeric(xvalArray[0]) || !isNumeric(yvalArray[0])) { console.log('Plotly.Fx.hover failed', evt, gd); - return dragElement.unhover_raw(gd, evt); + return dragElement.unhoverRaw(gd, evt); } } @@ -518,7 +516,7 @@ function hover(gd, evt, subplot) { } // nothing left: remove all labels and quit - if(hoverData.length===0) return dragElement.unhover_raw(gd,evt); + if(hoverData.length===0) return dragElement.unhoverRaw(gd,evt); // if there's more than one horz bar trace, // rotate the labels so they don't overlap diff --git a/src/plots/gl3d/layout/axis_defaults.js b/src/plots/gl3d/layout/axis_defaults.js index 803878d4605..d01aa1c85c5 100644 --- a/src/plots/gl3d/layout/axis_defaults.js +++ b/src/plots/gl3d/layout/axis_defaults.js @@ -57,13 +57,10 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, options) { if(coerce('showspikes')) { coerce('spikesides'); coerce('spikethickness'); - // TODO: this makes the default spikes #444 rather than - // #000, is that OK? coerce('spikecolor', containerOut.color); } coerce('showaxeslabels'); - // TODO: relate backgroundcolor to common containerOut.color? if(coerce('showbackground')) coerce('backgroundcolor'); } }; diff --git a/src/traces/scatter/line_shape_defaults.js b/src/traces/scatter/line_shape_defaults.js index 07aaf13e82a..421144f3a2f 100644 --- a/src/traces/scatter/line_shape_defaults.js +++ b/src/traces/scatter/line_shape_defaults.js @@ -10,7 +10,7 @@ 'use strict'; -// common to 'scatter', 'scatter3d', 'scattergeo' and 'scattergl' +// common to 'scatter' and 'scatterternary' module.exports = function handleLineShapeDefaults(traceIn, traceOut, coerce) { var shape = coerce('line.shape'); if(shape === 'spline') coerce('line.smoothing'); diff --git a/src/traces/scatterternary/attributes.js b/src/traces/scatterternary/attributes.js index c5ffe691f30..6fb8711a577 100644 --- a/src/traces/scatterternary/attributes.js +++ b/src/traces/scatterternary/attributes.js @@ -35,7 +35,7 @@ module.exports = { 'If `a`, `b`, and `c` are all provided, they need not be', 'normalized, only the relative values matter. If only two', 'arrays are provided they must be normalized to match', - '`ternary(i).sum`.' + '`ternary.sum`.' ].join(' ') }, c: { From 515e121bbec4397278a3b1dbda23770eb50a0efb Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Mon, 4 Apr 2016 11:17:43 +0200 Subject: [PATCH 33/76] robustify the robustification of graph_interact ternary check --- src/plots/cartesian/graph_interact.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plots/cartesian/graph_interact.js b/src/plots/cartesian/graph_interact.js index cfca5b4b7cf..de3e76d955e 100644 --- a/src/plots/cartesian/graph_interact.js +++ b/src/plots/cartesian/graph_interact.js @@ -313,12 +313,12 @@ function hover(gd, evt, subplot) { .map(function(pi) { return pi.id; })), xaArray = subplots.map(function(spId) { - var ternary = gd._fullLayout[spId]._ternary; + var ternary = (gd._fullLayout[spId] || {})._ternary; if(ternary) return ternary.xaxis; return Plotly.Axes.getFromId(gd, spId, 'x'); }), yaArray = subplots.map(function(spId) { - var ternary = gd._fullLayout[spId]._ternary; + var ternary = (gd._fullLayout[spId] || {})._ternary; if(ternary) return ternary.xaxis; return Plotly.Axes.getFromId(gd, spId, 'y'); }), From cdc6195d840274b8fd90a073a71c21aeb8ff251e Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Mon, 4 Apr 2016 11:28:51 +0200 Subject: [PATCH 34/76] put back the fullLayout calculation instead of ._size for rangeslider title position --- src/plots/cartesian/axes.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/plots/cartesian/axes.js b/src/plots/cartesian/axes.js index 8a453fb37d8..0eddb4f6a9a 100644 --- a/src/plots/cartesian/axes.js +++ b/src/plots/cartesian/axes.js @@ -1684,7 +1684,8 @@ axes.doTicks = function(gd, axid, skipTitle) { fontSize*(offsetBase + (ax.showticklabels ? 1.5 : 0.5))); if(ax.rangeslider && ax.rangeslider.visible && ax._boundingBox) { - y += gs.h * ax.rangeslider.thickness + ax._boundingBox.height; + y += (fullLayout.height - fullLayout.margin.b - fullLayout.margin.t) * + ax.rangeslider.thickness + ax._boundingBox.height; } if(!avoid.side) avoid.side = 'bottom'; From 793afb3ab5237536d370e9601cec70469b132d88 Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Mon, 4 Apr 2016 11:30:18 +0200 Subject: [PATCH 35/76] fix wrong name in legend.draw --- src/components/legend/draw.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/legend/draw.js b/src/components/legend/draw.js index 79790a9d0b1..ac514443dd5 100644 --- a/src/components/legend/draw.js +++ b/src/components/legend/draw.js @@ -312,7 +312,7 @@ module.exports = function draw(gd) { yf = dragElement.align(y0+dy+lh, -lh, gs.t+gs.h, gs.t, opts.yanchor); - var csr = dragElement.cursor(xf, yf, + var csr = dragElement.getCursor(xf, yf, opts.xanchor, opts.yanchor); setCursor(legend, csr); }, From c582d7f437bb35dcad702726587d05c84907d36b Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Mon, 4 Apr 2016 11:47:41 +0200 Subject: [PATCH 36/76] updated size_margins test image for axis.color gridline default color change --- test/image/baselines/size_margins.png | Bin 19454 -> 19329 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/test/image/baselines/size_margins.png b/test/image/baselines/size_margins.png index afe9bfebab21016c5e599a4e4c40953b4c1ec6a8..6c3f930a74644ed199afc2fc19dba295644f81dc 100644 GIT binary patch literal 19329 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 uHUKUD`-T6V()C>b4VwSc+hTfQ Date: Mon, 4 Apr 2016 18:44:16 +0200 Subject: [PATCH 37/76] remove commented code in gl3daxes_test --- test/jasmine/tests/gl3daxes_test.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/test/jasmine/tests/gl3daxes_test.js b/test/jasmine/tests/gl3daxes_test.js index cbf77f36e85..4c8778b14c8 100644 --- a/test/jasmine/tests/gl3daxes_test.js +++ b/test/jasmine/tests/gl3daxes_test.js @@ -65,16 +65,13 @@ describe('Test Gl3dAxes', function() { for(var i = 0; i < keys.length; i++) { var k = keys[i]; expect(validObject[k]).toBe(testObject[k]); - // if(validObject[k] !== testObject[k]) return false; } return true; } supplyLayoutDefaults(layoutIn, layoutOut, options); ['xaxis', 'yaxis', 'zaxis'].forEach(function(axis) { - // expect( checkKeys(expected[axis], layoutOut[axis]); - // ).toBe(true); }); }); }); From a0e0cbdcaec794f91ba2ba4bc20dbd79926abbab Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Mon, 4 Apr 2016 18:49:34 +0200 Subject: [PATCH 38/76] comment on axis.color blending to grid color --- src/plots/cartesian/layout_attributes.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/plots/cartesian/layout_attributes.js b/src/plots/cartesian/layout_attributes.js index 64bd84583bd..dfac8d41644 100644 --- a/src/plots/cartesian/layout_attributes.js +++ b/src/plots/cartesian/layout_attributes.js @@ -21,6 +21,7 @@ module.exports = { description: [ 'Sets default for all colors associated with this axis', 'all at once: line, font, tick, and grid colors.', + 'Grid color is lightened by blending this with the plot background', 'Individual pieces can override this.' ].join(' ') }, From 4eb83fbb5f2ecfca210709cbb9d55d4d9ddb92ed Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Mon, 4 Apr 2016 22:08:04 +0200 Subject: [PATCH 39/76] Color.lightColor -> tinycolor.mix --- src/components/color/attributes.js | 6 +++--- src/components/color/index.js | 18 ------------------ src/plots/cartesian/axis_defaults.js | 5 +++-- src/plots/gl3d/layout/axis_defaults.js | 6 +++--- src/plots/ternary/layout/axis_defaults.js | 6 +++--- 5 files changed, 12 insertions(+), 29 deletions(-) diff --git a/src/components/color/attributes.js b/src/components/color/attributes.js index 8e9a0b0d2b4..fe9b763e80f 100644 --- a/src/components/color/attributes.js +++ b/src/components/color/attributes.js @@ -31,6 +31,6 @@ exports.background = '#fff'; // with axis.color and Color.interp we aren't using lightLine // itself anymore, instead interpolating between axis.color -// and the background color. lightFraction gives back exactly -// lightLine if the other colors are defaults. -exports.lightFraction = (0xe - 0x4) / (0xf - 0x4); +// and the background color using tinycolor.mix. lightFraction +// gives back exactly lightLine if the other colors are defaults. +exports.lightFraction = 100 * (0xe - 0x4) / (0xf - 0x4); diff --git a/src/components/color/index.js b/src/components/color/index.js index 48682faf9af..655c514652a 100644 --- a/src/components/color/index.js +++ b/src/components/color/index.js @@ -19,7 +19,6 @@ color.defaults = colorAttrs.defaults; color.defaultLine = colorAttrs.defaultLine; color.lightLine = colorAttrs.lightLine; color.background = colorAttrs.background; -var lightFraction = colorAttrs.lightFraction; color.tinyRGB = function(tc) { var c = tc.toRgb(); @@ -58,23 +57,6 @@ color.combine = function(front, back) { return tinycolor(fcflat).toRgbString(); }; -// find a color interpolating between two endpoints, -// in rgba space. -color.lightColor = function(front, back, backFraction) { - if(backFraction===undefined) backFraction = lightFraction; - - var c1 = tinycolor(front).toRgb(), - c2 = tinycolor(back || color.background).toRgb(), - frontfraction = 1 - backFraction, - cInterp = { - r: frontfraction * c1.r + backFraction * c2.r, - g: frontfraction * c1.g + backFraction * c2.g, - b: frontfraction * c1.b + backFraction * c2.b, - a: frontfraction * c1.a + backFraction * c2.a - }; - return tinycolor(cInterp).toRgbString(); -}; - color.stroke = function(s, c) { var tc = tinycolor(c); s.style({'stroke': color.tinyRGB(tc), 'stroke-opacity': tc.getAlpha()}); diff --git a/src/plots/cartesian/axis_defaults.js b/src/plots/cartesian/axis_defaults.js index b911e28a284..f7aa309e20b 100644 --- a/src/plots/cartesian/axis_defaults.js +++ b/src/plots/cartesian/axis_defaults.js @@ -10,10 +10,11 @@ 'use strict'; var isNumeric = require('fast-isnumeric'); +var colorMix = require('tinycolor2').mix; var Lib = require('../../lib'); var Plots = require('../plots'); -var lightColor = require('../../components/color').lightColor; +var lightFraction = require('../../components/color/attributes').lightFraction; var layoutAttributes = require('./layout_attributes'); var handleTickValueDefaults = require('./tick_value_defaults'); @@ -116,7 +117,7 @@ module.exports = function handleAxisDefaults(containerIn, containerOut, coerce, if(showLine || containerOut.ticks) coerce('mirror'); - var gridColor = coerce2('gridcolor', lightColor(dfltColor, options.bgColor)), + var gridColor = coerce2('gridcolor', colorMix(dfltColor, options.bgColor, lightFraction).toRgbString()), gridWidth = coerce2('gridwidth'), showGridLines = coerce('showgrid', options.showGrid || !!gridColor || !!gridWidth); diff --git a/src/plots/gl3d/layout/axis_defaults.js b/src/plots/gl3d/layout/axis_defaults.js index d01aa1c85c5..35c6195a1d8 100644 --- a/src/plots/gl3d/layout/axis_defaults.js +++ b/src/plots/gl3d/layout/axis_defaults.js @@ -8,9 +8,9 @@ 'use strict'; +var colorMix = require('tinycolor2').mix; var Lib = require('../../../lib'); -var Color = require('../../../components/color'); var layoutAttributes = require('./axis_attributes'); var handleAxisDefaults = require('../../cartesian/axis_defaults'); @@ -20,7 +20,7 @@ var noop = function() {}; // TODO: hard-coded lightness fraction based on gridline default colors // that differ from other subplot types. -var gridLightness = (204 - 0x44) / (255 - 0x44); +var gridLightness = 100 * (204 - 0x44) / (255 - 0x44); module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, options) { var containerIn, containerOut; @@ -49,7 +49,7 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, options) { bgColor: options.bgColorCombined }); - coerce('gridcolor', Color.lightColor(containerOut.color, options.bgColorCombined, gridLightness)); + coerce('gridcolor', colorMix(containerOut.color, options.bgColorCombined, gridLightness).toRgbString()); coerce('title', axName[0]); // shouldn't this be on-par with 2D? containerOut.setScale = noop; diff --git a/src/plots/ternary/layout/axis_defaults.js b/src/plots/ternary/layout/axis_defaults.js index e00b17641b5..43ec3bf068f 100644 --- a/src/plots/ternary/layout/axis_defaults.js +++ b/src/plots/ternary/layout/axis_defaults.js @@ -8,9 +8,9 @@ 'use strict'; +var colorMix = require('tinycolor2').mix; var Lib = require('../../../lib'); -var lightColor = require('../../../components/color').lightColor; var layoutAttributes = require('./axis_attributes'); var handleTickMarkDefaults = require('../../cartesian/tick_mark_defaults'); @@ -77,9 +77,9 @@ module.exports = function supplyLayoutDefaults(containerIn, containerOut, option delete containerOut.linewidth; } - // default grid color is darker here (backFraction 0.6, vs default 0.909) + // default grid color is darker here (60%, vs cartesian default ~91%) // because the grid is not square so the eye needs heavier cues to follow - var gridColor = coerce2('gridcolor', lightColor(dfltColor, options.bgColor, 0.6)), + var gridColor = coerce2('gridcolor', colorMix(dfltColor, options.bgColor, 60).toRgbString()), gridWidth = coerce2('gridwidth'), showGridLines = coerce('showgrid', !!gridColor || !!gridWidth); From e08dce9912ce12832bc27be247a1dbddf6d4a214 Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Mon, 4 Apr 2016 22:22:40 +0200 Subject: [PATCH 40/76] update scatterternary.text description --- src/traces/scatterternary/attributes.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/traces/scatterternary/attributes.js b/src/traces/scatterternary/attributes.js index 6fb8711a577..6d904e95477 100644 --- a/src/traces/scatterternary/attributes.js +++ b/src/traces/scatterternary/attributes.js @@ -68,7 +68,7 @@ module.exports = { 'If a single string, the same string appears over', 'all the data points.', 'If an array of string, the items are mapped in order to the', - 'this trace\'s (a,b,c) coordinates.' + 'the data points in (a,b,c).' ].join(' ') }), line: { From a5d5969513a4d393744ffd186fc05477b88f57b0 Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Mon, 4 Apr 2016 22:28:05 +0200 Subject: [PATCH 41/76] perf scatterternary.calc --- src/traces/scatterternary/calc.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/traces/scatterternary/calc.js b/src/traces/scatterternary/calc.js index 89b46f65fd4..1ebd7b2b674 100644 --- a/src/traces/scatterternary/calc.js +++ b/src/traces/scatterternary/calc.js @@ -51,9 +51,9 @@ module.exports = function calc(gd, trace) { b = trace.b[i]; c = trace.c[i]; if(isNumeric(a) && isNumeric(b) && isNumeric(c)) { - a = Number(a); - b = Number(b); - c = Number(c); + a = +a; + b = +b; + c = +c; norm = displaySum / (a + b + c); if(norm !== 1) { a *= norm; From 408e3b0094e8b575421a335f8844de4d98365a36 Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Mon, 4 Apr 2016 23:25:16 +0200 Subject: [PATCH 42/76] oops, bug in my 3d mods, evidenced (but not created by) tinycolor.mix --- src/plots/gl3d/layout/axis_defaults.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plots/gl3d/layout/axis_defaults.js b/src/plots/gl3d/layout/axis_defaults.js index 35c6195a1d8..37ee79dcd23 100644 --- a/src/plots/gl3d/layout/axis_defaults.js +++ b/src/plots/gl3d/layout/axis_defaults.js @@ -46,10 +46,10 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, options) { letter: axName[0], data: options.data, showGrid: true, - bgColor: options.bgColorCombined + bgColor: options.bgColor }); - coerce('gridcolor', colorMix(containerOut.color, options.bgColorCombined, gridLightness).toRgbString()); + coerce('gridcolor', colorMix(containerOut.color, options.bgColor, gridLightness).toRgbString()); coerce('title', axName[0]); // shouldn't this be on-par with 2D? containerOut.setScale = noop; From dae00afe87afd7dd50781e45ec26fd2e4be98697 Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Mon, 4 Apr 2016 23:26:06 +0200 Subject: [PATCH 43/76] scatterternary: don't treat [] array equivalent to undefined --- src/traces/scatterternary/calc.js | 2 +- src/traces/scatterternary/defaults.js | 25 +++++++++++++++---------- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/src/traces/scatterternary/calc.js b/src/traces/scatterternary/calc.js index 1ebd7b2b674..ae439af07a6 100644 --- a/src/traces/scatterternary/calc.js +++ b/src/traces/scatterternary/calc.js @@ -31,7 +31,7 @@ module.exports = function calc(gd, trace) { // fill in one missing component for(i = 0; i < dataArrays.length; i++) { dataArray = dataArrays[i]; - if(trace[dataArray] && trace[dataArray].length) continue; + if(trace[dataArray]) continue; fillArray1 = trace[arraysToFill[dataArray][0]]; fillArray2 = trace[arraysToFill[dataArray][1]]; diff --git a/src/traces/scatterternary/defaults.js b/src/traces/scatterternary/defaults.js index aa173d62077..f81b7976be5 100644 --- a/src/traces/scatterternary/defaults.js +++ b/src/traces/scatterternary/defaults.js @@ -27,22 +27,27 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout return Lib.coerce(traceIn, traceOut, attributes, attr, dflt); } - var a = coerce('a') || [], - b = coerce('b') || [], - c = coerce('c') || [], + var a = coerce('a'), + b = coerce('b'), + c = coerce('c'), len; - // allow any one array to be missing, len is the minimum - // of any that have data - if(a.length) { + // allow any one array to be missing, len is the minimum length of those + // present. Note that after coerce data_array's are either Arrays (which + // are truthy even if empty) or undefined. As in scatter, an empty array + // is different from undefined, because it can signify that this data is + // not known yet but expected in the future + if(a) { len = a.length; - if(b.length) { + if(b) { len = Math.min(len, b.length); - if(c.length) len = Math.min(len, c.length); + if(c) len = Math.min(len, c.length); } - else len = Math.min(len, c.length); + else if(c) len = Math.min(len, c.length); + } + else if(b && c) { + len = Math.min(b.length, c.length); } - else len = Math.min(b.length, c.length); if(!len) { traceOut.visible = false; From a15e9b6fc6393961f17a479b3986ddee0a66a6b2 Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Mon, 4 Apr 2016 23:49:15 +0200 Subject: [PATCH 44/76] lint axes.doTicks --- src/plots/cartesian/axes.js | 153 ++++++++++++++++++------------------ 1 file changed, 76 insertions(+), 77 deletions(-) diff --git a/src/plots/cartesian/axes.js b/src/plots/cartesian/axes.js index 0eddb4f6a9a..b7059abe40c 100644 --- a/src/plots/cartesian/axes.js +++ b/src/plots/cartesian/axes.js @@ -1322,11 +1322,11 @@ axes.doTicks = function(gd, axid, skipTitle) { xa = plotinfo.x(), ya = plotinfo.y(); plotinfo.plot.attr('viewBox', - '0 0 '+xa._length+' '+ya._length); + '0 0 '+xa._length + ' ' + ya._length); plotinfo.xaxislayer - .selectAll('.'+xa._id+'tick').remove(); + .selectAll('.' + xa._id + 'tick').remove(); plotinfo.yaxislayer - .selectAll('.'+ya._id+'tick').remove(); + .selectAll('.' + ya._id + 'tick').remove(); plotinfo.gridlayer .selectAll('path').remove(); plotinfo.zerolinelayer @@ -1338,7 +1338,7 @@ axes.doTicks = function(gd, axid, skipTitle) { return Plotly.Lib.syncOrAsync(axes.list(gd, '', true).map(function(ax) { return function() { if(!ax._id) return; - var axDone = axes.doTicks(gd,ax._id); + var axDone = axes.doTicks(gd, ax._id); if(axid === 'redraw') ax._r = ax.range.slice(); return axDone; }; @@ -1349,10 +1349,10 @@ axes.doTicks = function(gd, axid, skipTitle) { // make sure we only have allowed options for exponents // (others can make confusing errors) if(!ax.tickformat) { - if(['none','e','E','power','SI','B'].indexOf(ax.exponentformat)===-1) { + if(['none', 'e', 'E', 'power', 'SI', 'B'].indexOf(ax.exponentformat) === -1) { ax.exponentformat = 'e'; } - if(['all','first','last','none'].indexOf(ax.showexponent)===-1) { + if(['all', 'first', 'last', 'none'].indexOf(ax.showexponent) === -1) { ax.showexponent = 'all'; } } @@ -1367,12 +1367,12 @@ axes.doTicks = function(gd, axid, skipTitle) { counterLetter = axes.counterLetter(axid), vals = axes.calcTicks(ax), datafn = function(d) { return d.text + d.x + ax.mirror; }, - tcls = axid+'tick', - gcls = axid+'grid', - zcls = axid+'zl', - pad = (ax.linewidth||1) / 2, + tcls = axid + 'tick', + gcls = axid + 'grid', + zcls = axid + 'zl', + pad = (ax.linewidth || 1) / 2, labelStandoff = - (ax.ticks==='outside' ? ax.ticklen : 1) + (ax.linewidth||0), + (ax.ticks === 'outside' ? ax.ticklen : 1) + (ax.linewidth || 0), labelShift = 0, gridWidth = Plotly.Drawing.crispRound(gd, ax.gridwidth, 1), zeroLineWidth = Plotly.Drawing.crispRound(gd, ax.zerolinewidth, gridWidth), @@ -1380,7 +1380,7 @@ axes.doTicks = function(gd, axid, skipTitle) { sides, transfn, tickpathfn, i; - if(ax._counterangle && ax.ticks==='outside') { + if(ax._counterangle && ax.ticks === 'outside') { var caRad = ax._counterangle * Math.PI / 180; labelStandoff = ax.ticklen * Math.cos(caRad) + (ax.linewidth || 0); labelShift = ax.ticklen * Math.sin(caRad); @@ -1392,8 +1392,6 @@ axes.doTicks = function(gd, axid, skipTitle) { transfn = function(d) { return 'translate(' + ax.l2p(d.x) + ',0)'; }; - // dumb templating with string concat - // would be better to use an actual template tickpathfn = function(shift, len) { if(ax._counterangle) { var caRad = ax._counterangle * Math.PI / 180; @@ -1432,12 +1430,12 @@ axes.doTicks = function(gd, axid, skipTitle) { // The key case here is removing zero lines when the axis bound is zero. function clipEnds(d) { var p = ax.l2p(d.x); - return (p>1 && p 1 && p < ax._length - 1); } var valsClipped = vals.filter(clipEnds); - function drawTicks(container,tickpath) { - var ticks = container.selectAll('path.'+tcls) + function drawTicks(container, tickpath) { + var ticks = container.selectAll('path.' + tcls) .data(ax.ticks==='inside' ? valsClipped : vals, datafn); if(tickpath && ax.ticks) { ticks.enter().append('path').classed(tcls, 1).classed('ticks', 1) @@ -1462,30 +1460,30 @@ axes.doTicks = function(gd, axid, skipTitle) { } var labelx, labely, labelanchor, labelpos0, flipit; - if(axletter==='x') { - flipit = axside==='bottom' ? 1 : -1; + if(axletter === 'x') { + flipit = (axside === 'bottom') ? 1 : -1; labelx = function(d) { return d.dx + labelShift * flipit; }; - labelpos0 = position + (labelStandoff+pad)*flipit; + labelpos0 = position + (labelStandoff + pad) * flipit; labely = function(d) { - return d.dy+labelpos0+d.fontSize * - (axside==='bottom' ? 1 : -0.5); + return d.dy + labelpos0 + d.fontSize * + ((axside === 'bottom') ? 1 : -0.5); }; labelanchor = function(angle) { - if(!isNumeric(angle) || angle===0 || angle===180) { + if(!isNumeric(angle) || angle === 0 || angle === 180) { return 'middle'; } - return angle*flipit<0 ? 'end' : 'start'; + return (angle * flipit < 0) ? 'end' : 'start'; }; } else { - flipit = axside==='right' ? 1 : -1; - labely = function(d) { return d.dy+d.fontSize/2 - labelShift * flipit; }; + flipit = (axside === 'right') ? 1 : -1; + labely = function(d) { return d.dy + d.fontSize / 2 - labelShift * flipit; }; labelx = function(d) { return d.dx + position + (labelStandoff + pad + - (Math.abs(ax.tickangle)===90 ? d.fontSize/2 : 0)) * flipit; + ((Math.abs(ax.tickangle) === 90) ? d.fontSize / 2 : 0)) * flipit; }; labelanchor = function(angle) { - if(isNumeric(angle) && Math.abs(angle)===90) { + if(isNumeric(angle) && Math.abs(angle) === 90) { return 'middle'; } return axside==='right' ? 'start' : 'end'; @@ -1494,7 +1492,7 @@ axes.doTicks = function(gd, axid, skipTitle) { var maxFontSize = 0, autoangle = 0, labelsReady = []; - tickLabels.enter().append('g').classed(tcls,1) + tickLabels.enter().append('g').classed(tcls, 1) .append('text') // only so tex has predictable alignment that we can // alter later @@ -1536,9 +1534,9 @@ axes.doTicks = function(gd, axid, skipTitle) { var thisLabel = d3.select(this), mathjaxGroup = thisLabel.select('.text-math-group'), transform = transfn(d) + - ((isNumeric(angle) && +angle!==0) ? - (' rotate('+angle+','+labelx(d)+','+ - (labely(d)-d.fontSize/2)+')') : + ((isNumeric(angle) && +angle !== 0) ? + (' rotate(' + angle + ',' + labelx(d) + ',' + + (labely(d) - d.fontSize / 2) + ')') : ''); if(mathjaxGroup.empty()) { var txt = thisLabel.select('text').attr({ @@ -1575,19 +1573,20 @@ axes.doTicks = function(gd, axid, skipTitle) { } function fixLabelOverlaps() { - positionLabels(tickLabels,ax.tickangle); + positionLabels(tickLabels, ax.tickangle); // check for auto-angling if x labels overlap // don't auto-angle at all for log axes with // base and digit format if(axletter==='x' && !isNumeric(ax.tickangle) && - (ax.type!=='log' || String(ax.dtick).charAt(0)!=='D')) { + (ax.type !== 'log' || String(ax.dtick).charAt(0) !== 'D')) { var lbbArray = []; tickLabels.each(function(d) { var s = d3.select(this), thisLabel = s.select('.text-math-group'), x = ax.l2p(d.x); - if(thisLabel.empty()) { thisLabel = s.select('text'); } + if(thisLabel.empty()) thisLabel = s.select('text'); + var bb = Plotly.Drawing.bBox(thisLabel.node()); lbbArray.push({ @@ -1595,15 +1594,15 @@ axes.doTicks = function(gd, axid, skipTitle) { top: 0, bottom: 10, height: 10, - left: x-bb.width/2, + left: x - bb.width / 2, // impose a 2px gap - right: x+bb.width/2 + 2, + right: x + bb.width / 2 + 2, width: bb.width + 2 }); }); - for(i=0; i Date: Mon, 4 Apr 2016 23:57:19 +0200 Subject: [PATCH 45/76] 3d test image has better default gridline colors now, and fix 3d defaults test --- test/image/baselines/gl3d_cufflinks.png | Bin 88161 -> 87490 bytes test/jasmine/tests/gl3daxes_test.js | 3 ++- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/test/image/baselines/gl3d_cufflinks.png b/test/image/baselines/gl3d_cufflinks.png index 7be1febeebdb7d094643fda9779fe0bc148ed302..809e2296303fa195f82e30341c63b9e154fce0a6 100644 GIT binary patch literal 87490 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

q1V!4oJZht`jrMW7y1aNu=W(Ha!|NJ# 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

-kh3H8D^JB zpzKbeO+~n3cS^yxj2-Qi3?IFsK*?rcoB3S>P5=y&TeUxO+>s(7Zh1U6^W{VHSN56o zXCx<)-oLgYY^{P9iVBS;$F^+6`;<&jfiRi@bRm1t1YZ_$x3n&Eg5Kq4r} zi|*HW$x4V=QvOdik&cS?Qw~OY#V5Ij3_LBaf~~1sTo0j-_F7A#3Q&=n{`zbbSD&O8 zl#2HUazv%g9F60S!+SJmIP(7vDijg$vdu9&)orVmvT)1;#UA`Yk^==4G*jf4X*G7z z)XFo!{)N|Bi7^599Jao0mhQsWh-JvatXDE$dvOk`{|KjaOeJ-}gebXVPSYzpim&eP>BCLMIZkzZR;TU z1EjWgXOFkX<3IYL!7T-T8B##yE61gFVaK1f_<5w1w?`+$f2kA#y1^K`7g(-mhVEf00LVA554ZPWyy{t=va;gG!R}jul+pCf zvz)XNwANaAkon)p6ckxTF+zf^$<5PKQd=WYpo2yp&q3A8dKv#U-J5He6=DOjQN~-J@T^_rRN`0JQ~=D($lPK?(=) z!DcP3yx&OchWqbUuIcfg(`B#k(n+bgtD&t zFpGD!4(J8D%?0g7FHF(qiZ*jV&ytdeL}k~5_FAWT-Ko2fm+F=GOi#blZCvaHTF0fK zKIX~QLviLq{;gUkq!!q<&2sV7z{vUM zW*ZI!s)%eSfSeH9apHqfpQ}qH>fI!?6Ubo()b#Y@QnD1v#_O7)_n0OGV;Omt$ilk1 zrN-j*kyi1lbm6P6|Am_Gw0g8A>^3vaL|Q#0rN;mB4V>KvPSFKDJ3Bu!)6>7yOH2A7 z&T+CNyjOp67=+ckRAgqhUJz@6!K$&zmBvJC&t~%H)AJ)S=0672lr3&bch!zRB3p_~ za}3u&vTS@v4w!}Ix~(MVKcngANkv*(^K_A?nq8=zVWw!69@CgPahJhc@hQou-NRk~+S<=E(|WoS?SbTL{YXkJdg?nh)z|1=KCgnU{+< zeL@?64Z*`dhx7R6C{In2>Ud)8B9S)i<~@%1XZ1WZMrFo8h9N);{@-?UB1KlbZ^;NyBtStF4qJ>!Tz-<&CJe@Y2za>fI^ecGOyA?{fz! zH_L0~4I`aJM*rHj%7n=J7t{yk!Po@}&58$`IbDNu$p=j5;S!jedHJM)Wgs}f4rn4>N+z}IiD*p$n{w;QpI7t)*^&%$p5S1+T+>I zvUrk;&KPuO>e2bAnw|Mn8LdGo+GbQ*Ez^fSCWuD0DeEndZcwGtq^f2{F?RbPswk2o z@d}B?E0{9udbA!P5xUh9+9c?xVz4K=`}^;Af9Iaxx%d9=_dM?T9cH*V0hU*DL|D9; zh(uh7!;lsjiBnmY0oz(eN27>Gy%*Br>5EZQg=>tQArtyT+20!$@+>S4Eq)Yh)h8(O zPC3i8?5_QEHTDy6_kSF5kv*Dfz1>%L;;eRlCVx8l;>5l>iNq>yj1ds^+{_srp({>cCeeRZc=$|xP1?YceRaKhi?TAaaQZN@?w13RW`sm|1x_+Q+dAlF; z%2%gKEFr6<47B*<>`**X-s8oVnA&_x!l(q=f4>T+_4$u4Rl4S&lJWuUVsIhK;BvX4 zHkB$)cnO97lITVwj#;Qg0RlmLR#O08aU z8deLML@}@j=}lXox2T0w&!^Ia_%Lz8%ua}YI)_1gG4T=WsYN{F5t*dU%&Z;yve0E- z?hBfM1*T-}4@r$*>b!Y(bX0`GIC+Nd*e`T1h;jsuepLX6GOC+aw8z&XJnm|oIAOWi zh-p2@J$Oq*Q8Wz5xch zk#>Qo&M8-diN6ZKyADJf#@FMP^y`(=!6$N{!&N}r3&Mp4#gA2O2_fCo#Z)3F8|_K? zJ>A>P(7VU_vfr8osZQ?l2Ttht!VUQvpHxScaq$@@!%rBVHLnRg&e^31!sq6Bv)1AL z18;bqG;;+Xfk<=JVlO_%wyn&_m74pI?-rw*TIIKNRo@}rU&Y4+7dU#c${Bgt z>l|+9M+E#dMN#yIN1T$1B7t<@+)X5H?Jk+p`PMSfVDA|uUB!7hGuas=)||b|^>F1d zSNG2D33Ycu>mLS86jSzVvE9TsvrX7)#p=xM;JD2%zphSlK$m*wTd{&`%|o4Qvoa@bv_Eu?~@mKk(W(5LVoPEOTh znXjqlHoY{|Z%(_mGMs;s{oItbxaN<|yK=wBjkqlGr_3&7x@u$-)(O zAto*P-k=#REaG;7N#szZQv^Oe0&ql2l;hPaB+*-*lvO~=~ zE;gTcQ$G02e+X7>9!eWIr^v=ExcG4!1gpBH#-ts0keQjZolC_G4kRIMZmv&GPI3^Jh+lQryZ;UV&@9S(gjgfjA)uS+Oh^$!^h^;!#zY7;Bbzl3YjeYCB z$===KhHYiMscIMNEw8G4%&se4M?VjFRk?Mq9w_{(+EylSxl)(Kg-udP$(EdOK}l$P zJH&>vP+ocYkH)RhQ#%zX)b$S4>hJ;zvzmMhgq@lVRH{vv`|ZUGHeKtLp2D{X|%Nad1^VZ z{}Ih`iabYXzeM~AxM!h)ZCT5nBg0bI-BOcnbTt1&8!L~D`<{?LZGgDedY!>w z+%=5(|2Gi21*mks^B^P)YiIWL#@^xL3r!!F()rL!DUJsY%mn<65u5OJ1!Z7^k%t{ws!;U@C1KFMd8o#$X}-DQd}3&O`tZi% zH6L(eulMGrjhbxpLwD&7IEkGrZz$M+WGq{U7of|GKYS``!=RUa*iQJ`+mt`bm{{sZ zA(0SDl9lVjO&A}A;G5e=kDo=(OCx3FmxaQ?Xl59ng?0EJX$8Poz$d`7FAObF^UwHn kqU0vo;K4fgv~qcPGJ}pdq*hlHl$T7zpleA-EF=76`#1gF6g?1b2sl2@;&a z9o|ju^E>B$>U+Mb_f)-qQ8moot5?g_SNG~QQ5xzBIFBhFBOxK-C@IRmMnXbqMM6S8 z0igq*EbHAZBO%ctDalH|^)lXV!}v@!mCEhc%&M4~7>4wSPmzJ2Kj~MOP%jf@at+g} zduNbZwdeAA&g{xk!`Z&1eaFGxZzLhIg%%-3dI&OxOEo47NPsbsIgyFUC4*dve}aGc zU{S-prKpVkq{5(~RQ;HU=xQ40-gSFAQ2qYk`B%G%qIi@x5c10)d^)uM{0aWdpuqsm zdNVK%`OioHe1Qx_)~k=;|NP7)7Ks36n3YbV0K!KJL!y82Uw^{V{r>$<08F(ee|A(*Cc!FjVacpcUdEIH@ol+9W9$$kC#}06V=2n_=4&Ap4&M@KNMsgFxJJUeTN9wbuV5H5)!k3}QF~ z#olyuv(!eKy%v0vAkZqW`K{~U`ema7RQ?od@S@=xGtqww^db@8G|aQ5!VCwS+~|zM zHn<*#Rs&dg`~O_{k!%nFY_B0$@T*xtS+3wbHrT1{)&KQtAZ{g!$D3!hmj9BE5Rh-b znzQ_=P7Igtv?5V$MPNL`|9b&f!aSdq+woDsD@?=AjXoks`sQpa{%w-NV8Biv5k+uM z0hWKOtM_4VKkcfEynoDQh>VuQ`D!bCBmWmG1CM{(sxGpc+=STRq~`4nH9UpdKXIps z`FGTyFuVv$XJ~!o=Bv;5KdV2%M3(pR%t5x-AFTA5#(%LuNeMhl;z1Gn?`MD0|9!US z*hioDU(Y^%cy-&% z%}G0+|1moq+T#y^U6KTzjPBX>{g1sgn9#`J*-eb)*{1bJKL*ugM(aJO0jFMbsMY@W z)8jAxI{O4dj3W78IzzSq78kQ;)m@l==b%buyU|HkTj8Djzfccg)wAL1djHR3RwKL1 zDB!Z?JW$pDRd&GF{(oc_fiFFpRu`ax?{$L?cAmgFlWNBFG5>|6il_iUJb|lqAK0q? zdp|J`A->-NomX0ojg3Mby0PT1Z9Afn^NRgnKm<)o*L)ul5Jnq6v-0-Vb^ga69|ym% zv0*i^adL8^pH_)xdaxHA=&vX^M+82(fC@R}S5!>&K{G}(IEpSU%?X=nR9^<74%kjk zP9V#D^(5SpMe+|Y{(|AbzJFA8#hN%1?`}o2FAtGw+iKJPTgP-dAiCha-Q5rvj7I*Q zuaD2W%Br$5EKxDByfS-S`)IUJ#((V%1i^zUIFmXi^f7}68LQvJQP@(Ic|;$NE-g_% zeaFt&ibcv5_8QaF&Fx8ZOS&Zuk^VoaMEXI384leREnkf{6$c|X#kBLrhB5@k6~(V@ zfOjW|q49_ge0_Yg9y=_b_;m7JbVqWPkLK@1WECt1G*Ix%2W7)*O!rcvrAl0wFuzjk?)qAgzyXW2p@1!gBB3_?jof~ElW}vSN1_6 zL6~S7rJW`>qk0laWy1=r@KeT)3*RKk3 za+d#&i0a44RW!oGYe@{Pu%?84;W+nA3cxk0AN^HNT@R@pP1uWlf`r$PPDk|$qJ`wV zro5YFm(;wcH$^vn{yI^TEReg3{5>M;7F5+;GI;$*{O&C>5I5{k;DR4TaUSf}OYq^# z&shE@0Z^6btMS|3?-WKdz{f=dbWjxn;|WQHRRWYI1-eQaVO$EJS0-BQ$i_!BE#aal}c6@nK~vB@UkR$hO~ z0{+Fzz4QF9=Z^sVl=}xiF_5Js9G{@rp?=ofp!`|^wr5Yx{>qeDRhA?!A$~4CzA>FCy8cwN`^Q2^s*CoWn7 zB5rUA4>li=Ifey@E`{GRWZP?Uc}*p3Vz1cEXpfsf99oZV(IrQs0CZ9H&;$Q zHyOX(hCDu%0a^aTyx7&Ug!~1vtiS3JpS?lrz zP<1Q79{yo#Dg=tk@su`Ab_wMo$0;+ipPW+aV+WOH>g-0+-8z~%ly31+wiy3XhAm(E zp}Yi8vCksR*q6c1F1fsT7~iTL@Rw`_!aZE3Gh}vm)#ha;4t0M*ls>>+8r)ux zwt_{VV*X3=Wd6+n73%wx=EH)v`!a9rrm;v{CUshso?1L^sGe@gsrt6~5xptVU)E@! zEH!S(7t{G8iYULTs_NY%y<5fEqF?3_MqlzvZ2HiplRoR8%gd~c_B@0RIiTTn)IXhK zVqntCd3mmkvki1I?{|;nyeN?qE1A(8UYfS-S$9U@P)aKkzjyRn!N$w6+Dm1IqzXE8 z4-1zG6`v~PvNJ{%ZqLOvP8X9K&tRF1}*`zvb_0kXB z9hst*T(1+rUMZeu70Havh67yRX+qi+zLeb{{IO?J7#7l>Jo2C5&%+GX_gNH|`+oGI zUf`2xo#T-u>iI-`I2~Ra{2Q&8Zt_F^GDZIj0fMV`0?Hc%pz+lWrEeLmkW{Uo1Iak= zmMEp0m&@m4H`GA0ACU-Q8=-%?d|P%+-99d~x6?%AdPV$~Rf9i(^4uf3Ydd(U2l32(v@#xanLhjL!$Fz*;l`ttt8ZJ4=_WW_vb zaAV|f2+g)$h`7Ba`=B2wM+Tls8plu!IkfyKdvuhGlScb;xo+ylCqW?0>Zu@7tsOgY z^55Er>~~Bzxd;A36A;n>A^`cH8tWSy-l;AsSu5M$_eCKn+~`I$Vize(Pw4mCKEe9! zEMIog{8ju6jZ@(#G8sBWz%vHOk4QPBn{^e`cPeU42`l~Gx6<(_y?SE-M4gu(Bph_B$ z*oWf>$D{Fo;iK5SyHgckRLfw%4i?~zueJ#L1k_jEUrHK`D9&XweS?foiiKakpar({ zlQsa~Pr#nkKRaU6u1v71dqW|&5aa|ZFUF+TAVt+@S)~@8Kz60T^Q1mLJ0pmA z!NTjfDA_%j4stSdqio^Y^EHPaUk$ZyjN zud_;dRsru*`|7PkR<^%&3daN}V|uk0d7voL_w(fdpzB}qS2O!-TQPQU^`$)dTwCJSKBB=k;V7cMWPSJZI5I1T5QG-(J2Jg{t-s)o_%okf`^Qn zI{bABnl2i{(4?7CNKSgB?=%feWSN-VC$Fq5+5a`&ZFHc((1fO$c09968jD>(g}==Z zdO-BZfP|*K|E9@TLJ{ArD~vR4o4jEVG7dm~vM)Y$=LL@FQTSjj%U!G3=N-U*vOy_n z_}&3=`&NQWUY>+gKc$ZON9I8c`GnbOZ-a%A{eYZpRZb3#LD8xh-j;=tktX%>H_ z&E3sqwTqRLwaa(w9`w`COe2;vE&Dyuq*(U_ejU?JRFAUh9;#xJ$Q_(iyv&AgowdPo$xzt3!@`mR+itd~|+ z;=XW1%_wseBfvJs6C=AjjcbT#RjBj!dZrM4GV%W)sxAZQ?T)iiQW&?#I2v z$cwKL{UU5^bz>cw2K^aBtC_~*ncf2;)PpIrn=)9w-~xk-*)$Pumzx0roI1Lr^t$ep zD8j8RLjKibAnPPc`Jy?@`<@p@6we)mks1fb)A|)YgNk_{SY9wz{=}*$h|$xv#RaPGR(`% zOT?Lx0Io)5-hu(5G{S26*oYTieY-G@d(S{kMpkVl`u&gDys@T~pav<4b)^|*)x4Ab zSR>C)6j(Quw7#K1CNQw0i(rCWIkL^+I)$E^$!?Mf;9)IOHt|72*Sm>pJQi0p_csHG zL_&DB2*G(cawllN*?q?!^Pv5DA9Y0Hyfv|j5H5&PV-yOgwKw~FT;$8e?`p=8fo<)# zXJRaos+Jr?XRq&zA2Y@t8rWtu_0qQ*AF;pr3=bY;GBKM@Ub|?%Dgrr)GcjMLJ{?iK z!pBo+E~NmJZ9seabQW@uBcD%xzNmueLJg#GwFRFq*~~DvQd3`l-{g`H;r$Ti64BMw z1*kVnm_T=L=dJwoduyeQ#K8d>@n0dbUZMCXFLEdJ^tfZ31eLwkOGU`HOr==lWTEA( zWa}pe`0y!<&{|2mQGVa;9uhQ&yjS98Tue;$&F!0Sj#vv0H8`yozg40-l^)(caUf9f zrrX>51BPA#$O#-qk;s8&M$_BiMqunTDICRS_+Q48^*S{OcI&v};XFnje zPimiBK_jh!dIBgQs<&U;=?p`M$dhkbD*{!Kw@c{{M=1i(l25xuV-vWRlEUe-B0kxV zhv^J!O87m|Il{M=kj^N)m88+_zRs(v;@&G^5|!K@b<%40w1)JoY0pmeEoo-L%8XuOawh-eetoK%v^yJ7p~2 zBMHhCfQ@uZ;G;M2w38OmE`fhv z0CLTLS9tl=m9~J$aoe8;*7S$|8K=XO)6du}ucD6Q$Hvp2$p#Bz;YG6T9NK3^Mz-Zv zxc#x24)!GLP|ia1yGQgjP*_WdzE^?7i!Y*@A^I{apcf_??r*Yofd<~pvNe#rf8~NF zR(r!)?C~NScLuw=VfJOkQzO64N&htS1Xy9gwPqsMP+ir2(^i#k@D2L>Ua>sN{QI(g zNCnd+^O6&picJR&A#CfXKZ$(HnP79q>k(JP*%IDgge9jOd>dexTy1#?$A9!qTzq#- z`{J+#2XKzv?u*peFdd`xNkip9a60RwIVQaG7o!x1w|@;mIiIjUgU-$*jPbWAHQd5* z_E}KE@yGi24R0TjNrS*N+OyY?(J8^mEtd&Nf12JA=tb&Ym`)|pIWiNC-i(3uB@JlR z6{1GA96g@P3069{k7hm@Ld87r_(}VS$3_a^LtAz|&t~7xpgw`)glgq1JmHNuH>Sle z&9y!>J4ofQ>{B<}$OH=dJ2SR(jKS8VI)OKOtr%Yok5AA8n zC3^-#O?#QaXc}P@Y%R5DM8dEB?sns6-#O-<4jR@ddQDNX+S3Z#tkZmGi6`D7Vka&D zYLyJ%?*xNe^A`^dhP&h3&?7@*ils^WwYgNpH0{ARP}S4U2?`8h3_o{wcl~VI874`& zS$uH7Y?&Sj5X1vX+0a43ov;~|0A%?JbzZAui9GubtX0qEUPvA7DIV`2g{2GuuEBR@ zU*iWMvp(Q?{L^#L{mSph3G@UKoguS!1T=&hFDOlfv_5^zw4zt>c+5W|PGIc4ob_yp z%02h{*nL4Iz3Mej0BKQfYpov8J}~&mIVEu%Qda2>h<%8qAT!abZe$d({q0DVyZpM_ zAJAvGb_7LJVMzLM4=MQL6gtOa8X?(6ijsg{qN#Js-}_jrZ}b>iovVK;Qp)=urq;t| z(3gebjb=B)Kg1o#WqM>3qQC~zsp;PM@Y4pSb@iSGH)FEvfhP2{{e%a_ zYhHSFmoxwJNV+M~(uxK-C4?Vcw&N}jvGRG8KgK*zXQe{rygq)Fqt+IYcHWuQ(E(Jf zMM$q)sTR>7+60_RtqATward0_qqHL0E3%Clee#cYV|D-05hEeBk(Rs5k~u zpZ9E$(>8;=GQ2j08T3FFC*`wG2gRcmy|gH^29Hdk=iL`hNnq%OVbbctv@qN(ORjeq zc)BCjh1o7c!dJ+^E-;WbZ%n7k7{}lLwpTnwDD+)%bPxv3qSEit%$UNMxG&l@Vw$wW z9|1S0*$itSx$G%*RPq~7N;zQN-X##jTXiL98FX`nb_X2OpU<8CST((oc8yi{I8Gjc z+Izy(2#ntpmr?QrNZC)T{?Ygp^?cM?LmXPh-~V;n#a)>s#7~MnfYLfg*6XLfSV;(q zsK3-Ng$W<2n31o74FbK);^QwQ<}+lyun6I&Ttvse`#Bzu8L}AL&trc;^d3?%*{!=g z7sT<>z7ZM}k3vkJzsS~F0{+vqP~)$b;mfnYK6tmAh!_^%^2&IR5HEXA{9Wb;C&4v0rzW z4Mo57kPVx8q^zAck*Y>lh#4akeKh(?1%i*JNJ3*NvuXhE-fOTXe})iTuQ<=QI;OkM z>1c@6-C$Go-a-A-mRI-fv|TOZXR5gD;ce}EM@z&0QH|IfHOYk!M#ll1F1dQFfi&}Y zbIY5rukRsL(~EZs9}$_d5^9j>oxy&E(jteXb2Yv}8QOc$_I74BZtk4o9k?M;#x`*$ zO?lU3pp#Bj@1Dzb70)x{u?)BZ$qO2PTgLLE$WNkQq~1Jkjg+xNLV_Jds^szNwIN~G0R^Q%?KM&9_A#0<~ z9PQ6a?MGVYyY}eMI(QFlnGm>x{Q`Avg0AbWCg`G37Oo)Fu0JY_Ki0G+8yN-?oK*sN z2q$<{aWc;UU%E}wS&MUC)2wL(06HH~73bJy zGHbyftCjjAJBGJylosUnX#Zr(&Of8JfAQrS?!l(h>C(&)vM~Nw!!aZ+c*x;He*Ga5 zBXJNtm$PXhnKF-6_jSz0`~`&kSl`S06sXC-C#7o}vPW@i)l*CSK#45tnjA@rd9c=( z+*|&{wJ>wAM2Ozx4a~4*kTFBSr4$ zUEIPZRf^dui05mZEy-&T9_PRG0Oz2 zWb~QHDO!f#Y;Yg;%EZpkc!@{c%V)k1ozta|E?d|4u?adHmd|)oVql)jZM`+Yn{KXI z;FDn*vBxDLbS+88Lc*vkQhqpi$AL%8b?xhXs=nUY>czFER{R@gBvAygf_TXYrPA+V zUNrm@mP_(xyJYn^W&|W9Ckq4sFiJZzv~IQN-64%tok3$OR`npiI1zJ&#wFmo(@Cf>>)FlU@WcluPTgNKnZ41T8xelshm!c}vO#PiG? zuHmjm!=ZT|O52s%VRPvdY~`A9w6QRH|KV1zH74%K$@Q=2cwj+H$;>v!7)Nrquh^x9^n<^d zkhxOx1S*N^_1kduC9jwUQ|0GpBfL}OSm8VMN;D9Mf7JVkH3T1W;13*l3FXV!V_va? zGpQB@`U2h4!g~vv0``;8Fcn7yEB1X%|LmEXE>s{rBPO|ZJ!vshs#5kiye?v~^*vy; z^|c6{+8OR&OFD62w_5sl|N zPLbqM3?>`u&uYw_&c10|)Bc&n8}qe>Z#Lm$ZHzl-Q5J7H>_J%BwwYVe50er@UYRye z<~arlVQeKo+{D7366X2BAW~U=r%N&|NY%P%j7`d6!rQ;)4l%XE%%LI~S6;6sX$b9{ zh^C!MG9s$7RGrh@uYY`hN?@E_{B?np5LW4`9!{=6qCyE4HzBrioX!WdQEGo#g3rQVc+bN({LSGc4;Oy>13mIZD?phn6; zU+_QsV2N>b(hl!ZSee^#`uf1hI4NUkbn|DsE^=v1><8eNMqW1!~PolUs zq~+UATXLU+@&Y?ehVeI6^ob9xl)QucDCQSTx4U2Q8L>sgyC)U0U3`mjPw~F0s~ekf zSEhkD(ozfcN`f$q6f^e#_9g`bj?iaG?Ivtv5a!Rm9}kP+`S|*lGd7j(%qpUMX{@)C zs?eHpw9R^o7?Z3)t_R&#>h$AB?Ed~j4J}ioM0l?Dc4?1vpl}`p{>W2eCr+~~FSwA- zsIR{kohQDcqqnQ4WokX%$i)?-I(=GzS{1l5f*2e(_{*UN z&EUEMNMxzE&^;5pkJ`VS=D^<=~JjOrK@W3tsJyd_49xWEs3Tre2Cv+HX!; zv@B;{4X}eMm3^$D%wmp&gk=_bJ@dgslv)!4Mo3*xrlspYTnA|TNvMCUDPLfzRI@iy z(A|`sFf@_%y8RAxT%Z9N*0}Rw=_F0pc{r2SeJ@ANYhI#^f7~dBJnDGv=k4r5S=M4NSF)4&l{NLerHCXCzoaiS zGY`CEFj{|gnvfj}p0@RfY}EakQ{9VIv%6|$`eI=v662zQAgxB7-Q6goiDiyuKr)kw zOK+1B^FZ1}6wBlJnPS0&|7}j>O5cuS;a}rYmn%|!$hD+Dz_w| z7ec_X$jkS;q6SXWqFGnU$N z?q{eef@bn#@;be33`c9O)%_q!yV6MF1N0`Jeeb-Bu8vxv5EEN(MQyIsR7Hp93!_rlAHZRc*#O1>+xA=BBxr(RQ7 zRE`E}K`IOeGTw&lsy^1l0v*=@qhQL*TOOB4j%8H6vqL(Ih&q+oCpOZnb5{YnR1VpJ zV}HWK&PR`jgFpTBpJsbve-MCAb3erQaQ@feRxJ)@h%F=>Ai_1)8Q>!?wjx~u!q>p{ zeU)Cd-5=^aK7m2EWIq0Gvfq@)Fl$*-;MhQ=WNLC8n+T?hSHWEwHD`gaI$gunYsY}> zu1af1Tck5h%sd(rE;(OaNtiS&Ufu+H=chkoa4Y1_zv=3{m(W!xlEO_MTa<@ztah+?!w;On0o^jj!XZ%UccU5%Jpe%rs{SM|D8v-P(Fm-{~7 zmNRaWNjnJUxb{j|qWB(NdOma{E&L-d@m2z4;Unb@G!|*@)?H1fofVj+GWejGEwn)j zfj}8tkhdUCMi^BNj?iSrlD-f=(!ksn+dHpOMK8=wwE2R*ux7uqwrc8SiX<=Bg~{>` z{|D(Gc!J5D3}Q0Om{f_2wIyWpu@dTCds7?Eykm!0BX=z4ngV>*O1mcY!P`_z?49}{ zYtp3X(z+AZeg90)^?B=r<^0!)XzY7AQMGksoWW0J*Il323$LZ8Gl1qJqs?swn2wSA zsX=FV1i&Dbt){co#YvuJ2}hyWPbHzJ?nc0ylUDk}tAAB0j0ZiM$?T1f#Poz^_4`;! zZkK;;>z7Hb^Fe=Q?)35~n!@Xb5i%w|!}2*5RE3$6Obs-{2uvhNJ)~d`VdtqWG(idh z{t|Xx$%0NEJ`JA`ndJd#f7Aro33#d;=sB}uRC6C2;C@DBlnJf;EFHW3IIT=>JWtkl zfU|;UzmHfW;E&NNrDC0*CH;VEV)&~7$nOPb?9AGG8|EIpagiV2ag2y?{+UhL;Ki)l z2hPk~n#wD}%Hr&{N7sSV{~TehEG21;`f!0~L9x3Htl0a_{4v$9zQl~n{X7Q_1Hf|x@%kEM)mmTSUiCr3=wTs1R=$AweEYX!Z zWK`;%;;G2kLOKUPpj|%50S!;>0vFHgk`EwmLIZWjub=bB$}L|Hu=jB>wyLR(9&PVv z1njRD8s=MfNRurnN4)11x~k_|eKaRGBLWL>xRPPz#7=_N7vBQSk_K@rJ03rs!nzVu zH}Cz$=MHstxllDMRHCB#s3)>R^$8J1@*~g5Bd)H@+$;;9$%K|IxihwCMb2!KO)xjk zD$*FPCK~-gGHAl)+}qP=hI!ea;?t+MrSsB%DAmZ#fbY#w70LT5IiYxH33q|Ukz z{tWQjL7k26oLs5-%_EbmReuB`@FAmY*5g_Gym4m6l8D)yNASf4_X)0c)wQ=vCd+8Y zGT&E6C4=d4T4Hj%8L*w^4T+a3QuFCK=2A@?$tgwmkqa@g-3Iy9Y3<~?4Ch|n zNI9r3d~LnOE~fIJ6RQ^D15)(!)3CDf#Hs#KI;HpSvgrmxB}}`U-ert!>n@mx75xc| zS6#mhC|5H?rv+n8Ka&t7VW|x?SRhj$ev#iLbS_pLQY#XvAr4gx(zJL1^4cex z^s-TK-@uK%HapT1B7lKmax#vn*sr1kFa5?X9=9{`@Ccs!tY)x0(zL=l-Ef>|QFA>D zPtN5ML^9^d`a_MSG zpPj~&A~{SGnA*L!vmO#l#gR>RRy4cbX5F)E8T=JdUJ%8=+BJ*fgUdJW2!fM~hW$+~ z&p+|}-t@c-7VP*!WM+l)?#GOQ-kDx6I#MBkvsUql$p={`q|KK>|U z-`^$3CViP4K-PYq|DM|3HuXs_%NnlIRXw_`g=b=ln)xd>Xm~Rdo?b5f^e%>L+ejQ!>4E-TEm;7;<( z3Oi;SvWsmofL%k_P1M_+wx5;!K0MPuvs2`0lMG=y82dsL^;30Jl)G}vDtxV2Yw0K? z81plj767zA?alaSVsPDpH%6z|zAFJhFQ!MFD3sp2N9Wb6S4)@kDaxwC)!r~ucUfps z66Y+?+Z>UiezqMfn0wP={4_MTw#(N`jkzi50#XIJkZnR!XHvFuw4J4E5jXnB; z$_4_Lgj$FE93zA@`H6yVf>C$7)Lovp#=JkvTitL$@#yapj~xkA|xNSbnon2lV}5`$<4(%i?_k@Ci5Lp|v!f;@aNgc#)O;Ah>x zqN_U7)~#LMX8mX+adckCb@po}X!+D!uQ;YwD^c0d($b=Qlu-XF6XLGfD-HGCof!@t z=FVhpZR=>E+`n9R`hXzm8T(RBBuqZ3@9yDI9my*{?eG+vz=}Gyc4$YQGL1@c4#nJ7 zhH1G_qIYN~sHpASa?FSoql&IAX7h2*P8w;Pyq`UB4*p<+@;EScl7DogQoTNx8r$fy zX|HB(==5519oNiXQ~*CsptgsG1&&ar*%;h{nBVh8N$31zfbKXMB?%O+TsHQ3czAs8 zs8#b1`wZnnjU3$RX8Ax>_|%}98QOCC$Rt9V-KC!=3;gpH3y_p z6()k|EB93AEq1E7g8C7o88D?awvj5aHo#+7^EcaHSXG9>-z3w(I2 zug;vmQH8H$Y-p-SK}y9OJ^c3O(j=mZH2}8xBe(519`V!uVT$U61IIeaboPUa@l5>M zN`{3Owd+T(XTV;z@Qw;q>!z5}E0hRQ~GCN|C}Mc^~qvM@+3l zJQp`AqvscIqLx{Tc&5MO3$}FopD$rh3OP)URcQHJ_R%OI>71Oh2HVa1KO`Kdc}ZQ@ zn=ej6-iA_@C2jr;hW@8h+E<2cN}nJLt;h^d#ZM#>{oe7tA=~k2$Kr0qzq*p_%O2)0I~%oH{!(Q^8Ygrb z8g`EL=bm^saGW$o-Tsm~Fi6VqdbtpoarTKoA8>tFS#ABRJ7T9IBz-DAP;Jh9Vd7QD zcuF5nNaU2frJk84SWPtUWq=k6)pXS)Rcq4Pkzy6)o)rPi1+)JvUrfLSZ>#43wOLF2 z((ses@2s1y`6pZATjDkEu#Dh4Wmx6YJ`|_ap`C-Dfb!2YZ{LJ5qf*YlLRYhc zPycfkG9DlCrJF++(S^jdWCM*poZEjyx#=2rDH1|H~O=KY+ZA|0dVYAM6jdQ;0|HApZ zeBvMz&Y)YIP5Ef>bC^CzTYB@$C%FBLr z?mU6i=3fJ-I6!e`7gB9AsL!>m)1_`IT#z+jy z+nMm8mD)yb4KwnL00Sp}*myWyA51KDZj8+}jPyksk-~*lP0>3! z?tjy8PqY6}>kcyFkjQ!+A~b>1znz+@D%z{7i*r#x@I98+Qvj)N=s1){w^T&0l&|O; z_kcmR`pR2VFI%!_3^J%$4X>2UfTEz1uRP3$`e^4VFGs+dT^}&<#*ckc9&&znLrTpv zz03Glp0_5$9~bE#y!g*wFRnk80cHXi!ebE1BHIMU4vYn@7ZRdl+f(+0^lfr5F9#!E zvN@m)ziDt=uRw*p=PH`Vz$aiDhfcDUS|!RHp8eJexw{`Iz8zF+b?Cdv<~XBOan#hT zHch@0!hF}FHlrVVhgY=I2;3Hj>FkYIH_2%45m;979FThN0ka7%lxxpejSyNQ7i-)U zM!iH0_qh9w8#;euD6A_4R5h=aL>S57Bq?N7`r(;D@t=3a$gh4A1%vjf%k0M68wYOq z^IC_V6vvn<^Qn9AejG?j_Qv~GA}(SMHWO3{joDCsfskNo-etdIPtPkcj7U6}3Y$rX zQDHUkkTS=DGoJsjV`MgUu1uYoW$|S5i3lmbGG~<3O|g$cTp46Cv`SJKb6d)KB`2nIQwi1{wxyFbNtoW~}P+>L8o56@P`F>KY)N?`b{q1x050-%*8Fs*|qayDU; z0S?4LQ2+7{Ub@k??G5evym_#>LMy0(ul~& zypSuS6v65f>=j$@?AOqcQy;#R*s05DBZ-Gk*}(P z7#o(u6D>Fd3i#!+937I6f@}Te3W2z)A2mB>s4~I`s6P)5JFksy$BoSONg6AW^jV;O zzccayxSl3a7E1hFLNAH(J>k`2wPadPsC}~oeN?4>xZ}?P$wv!RB6xsM)VEw>)hx9w z>hJgnmexPd#O}pCpWLwcP>N~~BhWUZ<1DCx@igh{Y_1&8RvnAB{>KrjqQB$C_ zoc~b8<<~sx>)3@j{9id-OSci>H!zu*eZppsRPHx z23gM$!BMoyXJ==H`jZUNnlO3Vh`JAz0=w-1!K_tX%CxdIwdczK${F1;_O3+r@?;(2~q-kD8yEq$K{7XKar(FWW%@kn4hFG*?oWKa!KM@hY0u zwBLC@|D!e8oP%Ny^Rb6Ppz3Cr&bG16#sp)jOc@FRm-&L9GRytPnY`9$@9J3GVhNWU zXM4XFRN6Znnq|9SQ2WMm`Kg^V9!&UHjj4@9^-e@T%w&r{g_h0CP;6KHqI71oZ;0Jg zW~~nAkag}a_5jJaQA4lW2F&=aZGkIYppoIAr+s97#B18}eshBTW_6`UP)V%UN>!z+ zR+;FLh$GV~)mIU+=~@Qt$Do4?%vE?|;ElA=uiu2r#k2C?4PJx6$7a+(FK=Et-9Nh3 z!-#AihDQkF@3_RdII&};%-+s*Y@>F(+8TM_TBq1}+G|5>+weF) zzu+51BJPgYtE~n|gk?~ZjFHk$`a|FGLD(5OK41G-G%Jf@NZU@GY7rTyuQmTjS8z|f z>7TT`a9?b6M*NbRVr_N#T>L|rquy-SvNU!`l1gVkK~8KF;+Z&8i6yfDzyfe#Yr#Kd zsOndi=4_pXSn?bRg)>!FU&wNE$ZpV_Y=UZO zu66(X^M$C>bcB&I>5mqzOV8Bv(%RPY!Ril30DL-zG|>1G8egv7CY~qd#V>^Vi5~@_ zbNx!f!+^gEYnYWLMs0{DmH|4R!l-s$74J1Y3HAKgwu7ptn-7tN0`L5E>)u(FJ`~~H zDwbXG*ot4GzpYVv?*-gwR(HcAUsx6XUR!a0lcYst5{d9Zr^+gd5k@?tD4j^cd@hq3 zm%iii>9ceZiv@-8mns2%9+~)CME|eTC+Z5x#0E5J8D6#jOq;In|WJ49}9(qhzh_yDmC?+k-+fa zyMUZP&nGnkjq72|b5?rfOoznOZ>hD9>04YlB2S*tFPE0uLw;wnHV@PY&|PxY__c!f z_TK+7xo^{MfJ)JcinXj94RFuQJB?>sjYZm>1zSNRX&;$&~wt%E?2~ui@)mf|92cO%%Q5QQxRnxfNh}G=^{9qyFZ6X&hbYNexIWvUZ+$Z!U>s z*e+fLAaW?cLF9s%#402551r4U(rKJu_ubz0ksLMBFVN5*E0ZQcx|HtEuQ{0Y!q;Iv zGWMTO`d<>kWZspw(j%%fXC~fJ8Cx*TjvZ=~=65RFXMDkqa(Ywp<8|`Vp;Qum>rs4^ zK84A`zR7~WFs^ZFpu*5TzKys+rF6(hP1(sTA9JR{OJJ(e=>1_hqKRK!#Wrky2^M%< zQN7l`4SE>SF-te{h?pvy=>)E|#0-C%HkI{yBiuEM$s{N0p!-h3-;rhp%xKpcX`-XU z8XfBM6-#M0Z)cLowBZ#kdZ>(yx{?|Bl9j%(w!-WbqI50&r_9t7t1ja~6-+UuzS&2u zrJ26Eu^sVz=y$&4>vBpm_9|GdSKof-{i$EZz$3|*<8)v?lp~J^&%dVh7e-kW%(|kE z>+xk^PdpbPV{$h6`3b`)SsbMvAGS5|t*cNec@}oHk+t&y!Qyn~5SP#}^c3g|w^mTL zjj)W;2PcUML|h4p$erVdWI4=$DvMr8{PU zb6D5s=}yjI8y{sb_;ThZX`9t{Nhwz4HuK@9A)wLRAq67>l<>BLB7^+v{@mvjuqdL1 z{Ek2*>yeyCJJS`wk4nIZA-v|sLf?w#NRHV4R0jiBCmg$J&?ol-saa_-S_1bccnslB z`{aA_1+kan5(Pgb0FCX}-zneg4*BZ%;#D2$a*>`X+l9)w^>8QdrXfc4o6v^T)DZo* zc-{SC2V?bS9J)NTX&ibKvnG?<;9zQ@(N6Sbu<4ZrSM1UE0N5IC^MPHqM}GSWgDDm~ zwQS!1d=40B&lE9fv+{TSsO+Y-W97eR{OU)jy&d&cl(O6UqWi+iW=9hl%R#NgTY2v; z%uo4Uu}3a0J5O>naLc1sxUw>ROw}sdS$;R+o^Zh{hP&9UaK(S`Xd5M$^y!ABZ+Q(M z4zrxP{O_H=*2gk)z*AA@U-(U&7=b(4se2B0s(Uz+GOC6pSIdq5Tui1vM`MCZYz$5e z;XFnY@fXH_frI0AS%))r&h~y&-!p}`1nl$o_$~(`Q1~a^U7_AT`JHUN zMxkvO=D6uKEj3ovO`zGpC_Za510035l>x8nNKD>Q?j-$<(Vex{ol%Ent8$Z|QxUq7 zHRBki%db74s-j3f|}Do|BwHNrmJvk`i=G~N_R+u zG)Rn)Mi2=JL3-2%NOyNhw{#;&H-kwtx=R7+7~M#>#Qpla_x=NW_Pl%do^w8R@Bl&z zV*4a)NA4;+gQO|w&^7E!Mn=IuC)F9(4KtOwP?z_WQbUKm-|26j5Ei-a%sf65QwvH# zHVFI$J;Dot@B6&m-Kg)yw;O)fTco6U)^l&_3v#lGV{9BdO@5lWNm(lZOMn zI#N3g9TasmlxU?8VU(U7-EQ#t1&g+cw)7X(Us1q>Qc4a*JbpUf;B2twN#W`$Cp5SV zZ!I>L`7FZ`ejF;;I_iY>?aJ9hy2MoXfBk8?ZKU4%nY=G|zP@@1_!JzEKgiBdjw@us zI|GXS2rh-UrSkC$1|=bKiOq$YxdFF_MMb75b+fm5wnRGO@q3V z*_0^3|GQ0q;Sk<7qjJPdlYqp?h~s=7kkFvita@q~xx*~!@d$HPU!w;ptEJptjgEHiyVJzPK(3Tb z-DsT3e`b5bavbI4@`KLM38TIgHf=13>)&r zHGJqs1q1yAyYr#K+OTkC-a1Jr*06J{TMA<4<~62rwOinh!duYqqtA8xboTGM7Um}~ z!M^KioQH`;Vr;l4F?&CB-ziiREp5Un)63+Wf_{28#q^*33uEms@>0N zDvbkYON&N~P@W=?N1;xJkTL}a9^&<#xn}E43|Wd;fszZ8tjJ0G#6z~~dm8be)wHX5brZ7tTP=ot4dPJ${yA+o1 z#=qgJ!0*Q_P-yadRkUs(Xw=_l=UBMs&u{Z=%(Jf&6rrz4WZgcz*iDvp`-qDydqzy< znS2~7T+JL&S)2KK`{HGmp{*$wH204TT<>+VXHlpchSuWQle^ub1qwHmOmvujbj1j` zJX|Il-9AOlc{fRPr#Y6=&2O)R74X5|ul^HAfuqH>7nO$H=HBBII?hl}+EW~rZ*-dF zvDb`;+!ue6AisRyTW{)sLs{pfFpvr3eCON=oz~Y1t26xi$9uHo)^|zf*k+0qB_lipgc+c}U1k znGU74E-$m?%A>f4FZJE+UkM8jgPL@}ebsh^I1{j$t*%*hT^I}C@>jOzbOdIiNUP!y zYiK{yzdVqSf8Ek>M6Ce;I4D@hS%p>r5sO#ejVp>x<9)!hA5nFPek8$l1KOe7aSrDi z?qqjCAeu80zDD1V7#xB(-RbgMpk2ei;~b5GGU@`hmr25=q&?|Ptx{e6LLGxv<7bjcME zDnK0HDd8LF7zWCCk({#o7HC=V-n8giy0^AF4_3Zvdq1&4XpkL6iUE4~D&zU|yYJoe zYh-F0^7va0Qy050zp_b<(Rad~cc~IA+;y`M)IsXg#;sXHl7sjExQ5Z%VQ)bgV&LVe zoX7o}<`70id)rF^JRHdvjRleqQt)BiiW+oS*fkvAKE}d62HhUg#mp+ zo;&u*Sr^e10rbvSPm$f{Xn0!?;cpUuTt;93>uWH2hwT<;e!6}2K|XuNX3UjW7&weG zP%GKL2&2}s(99KGfYY8ZugIufM4U3~4G^u$EVuwvp2nxTNHdd&<2}~9laP?@4#|F6 z0I@R)PSw2X@+y@IziOfgSn^V$c$7GJ_ZroCg}^QT{t9kG)^ecU&G1OqvOEm8#2HWOj+Hz@#` z|IIgL+;Z8mJ4V_ghCaKybHm_RuIQgNx|`${#D#+HuZ}tkBGQh>f8x50X`%FM7ghOg(gg0J2QpWWX1RZ%`?L>iljv4Ov zu^iq1GYxPTGX0qR3-BBCc8dZqa)~@yII*-#p?3%!fk4ye{8?J}OLB)Fc#&}d`U94?Zq9>yimpn>ZuIc*D&^NOp+-z#JavtrDbG;7mRrI4j zf{W-9j|9}Z>@YDLf~B_hh5dv^!&xm(Q^u>G7kL{toiu{|erZKR(;3d^K;?-GRgP#X2mtGK z=jaWa&R)+`I&8W>3JdJSd?xo^N&7#`vVTFjEmNCLw>aUBc#QS8n~tKo0xglgeDQ<# zUjR1hEr1}U!ft3oRDC~Q#^_sV~8>#ZCn>!hV255fF>G!$eFsIrW$ zxmGm?VE|1uE=L31Udb3c{+#nQNiv1%B_Zm|Ig-MV6A?zlb^nmNvM5Wv4n;5TWd{$) zrUz`x@b5bm4VH1SuVuKdq@)5c4b)+d$srOrDSP`yf~uSGPpq6eP%5@j*MZpLF2CUH zEq&J*+wQ(4)^SeyRhhu+Rs#r1X1&c8Fw#kDp~h!p^El?WX3d{ImI2xouXREsPhp6R zI5a*#ApL`^>xs9HuC|%21zW07*Y(NqQv0p!MoDpPD~=!4X0}c6@sOhp-M{Y?`OT`X z_rffNh&&CK8}|LvWs zHuV^rIu!0S4FwEEVp;s)QQJA_im4*v&YqES+7w0125X$8JQ=i0yl!gJ2;C}TzCJrT z3e@xWrA`#AZD{E1i?I*OR-Qi8`xXq2xn84v33zc#{K__2Z5Ng&i9 z)Gb8_$NX3JXr4r4I?K)LH8V4MqjrND^riZdiO#y}w$Kf91Ax$WSrPGEZ91MKYDe!5 zoXo3uy8HTXWRKRyvTJ#H6H};;2G~K5&2gn2FcT(s5mklG6t;_Xv0K!H@14Fe<(iYh zT-4w~NkF0<-BB+heOrIX`jouF!ISdo{#?4Symn5y-JzT0_|Xh^fbdY2yJDt3+?~~gFy}Bi$kN4~$ay?#;B%1Row(TYfgcFjE?75>rZ^M)pkKs38{qC;)3ZnDO1{N-01iEyJVKc$-z zjI~Ur#r4@CZzTeTc@c+UJzAiv;Z&f2Dj~o22=1bTl+Y|0n%~@?Mg)?zF&KM3N zfJ!M#b!&(GcLA9X;{XaXKpSnuLe$3|9T+2))mBJ#2%n$df)aPNC*N>S z#{=7kpIJjzm?y4SpThGkBSRaSM;wHxVZ`i0np2EVN=zJ14Vb4c1mjS{Pg@q5Ugv%VOG($DdC`fEuMYxBJ|2_J>z>zqI^>=Qjm=3SJ zjy&!Q;PRdUAdNmVvSdh;lWE0IQ(TDNsalZ|bKaNm*(L9zR^_b_))}bt8Skvvn~ekr zo2`mSsf^Ewt-RmnmF5k3*9jy&Npj1KEPu7PZ7RGSx#<73fj!^miC23= zh;5CdnxPBfRW9Os1JbRTk_oly%EBa7ZMS;^S4#bh!VYs1cbwQ-p}F)57<`;niEb4l zooNa_!h@R`1{a$}Tv+`cQ>>Nqv5ZX#lhgFgns79&@H}iKrZ3Y)X4k7%k9ZZjHT1m9 zq)-pKMYn~@gV?YVCT|A3>6I7tH>?R-V$mVl1R>Y5OMDc;oEO7U^~lMm4IY4w74&df z=nx9jgVEuXP9O)YZkF$jGBDB0a=(RyER8UYE9Gm;q)jczG9#R|oOeTe7Z>_xd+`(SI(}t!S1&W%TEO#ibSJJ%jZ2d>8vmt2EH-*1dNJP6ng3DKPr(SdzwM>UPLIoXw*ai z&p~f$IkvIFOiL`YuoyUPEjpgeWj664uhF$~1WPh)X791<$5xl!+1F58cZs-EVl$Gd z#*a_sL2Xod`$Q+n_hmg=G5etrCn5z}=>-abLE&6&AiOFg7gD@Rqk^keaCTtb@KKPp zotFS6!dN^WBXNjh;t2)i!e0I!A@FzzJ(Ls3u0w!Z*Z1`2*q6q3{nJ0gD)mOW>R?j2rn{Fxv*`;T+y7WN3+HtYejmwt~D%-dY9e$!6o!>-{ z6-)~WZ8%0lr%)EU(O+g5_D{tfVA@tC=vKTu*$i%cV7CgMS;$ljH64%Gn>5vA3L`&} z>Cy}3eKKO_jb#>HwPiUiE4vbbyf~gLyEryE^76QCYVct>3@eQ35yT1xKZC?xqu$sf zv)N?y?2!k>&ToM?ZXB47lt1xJg2ypcJOrINWCMD7v~~k?C}-6yIX`kiKwG3){G=Hf z87m$4Zoxq>w+ zXJ_QWEN97jmV<4h=5%~oYjWLH0uw`MMO~K#gK;jriF{!NCT~!g&JUr?nZLpChVMc zTOoR?b=|a!n60g;(ONE+4&waT)>)Hz=0{Hwo@*x<+@;?eaq~X&-_=3;RH=&wUOtY7 z(Rw>yXSiIy@!^ZO;DjR5ZfpUH{rs{h)m=2AZfY+Ph?OY#!GjAhYohT5bd=&^W+@_= zyt4?G^f03B@QDzF%^tJ73|XG{xbR=T^e#GQHwvO&nY=Ao$N1XP=U{2K_z}8*UKo+v zml?g-FP*t`*+5;@buML36&&cc=H0fdvlFz!gjb8}vumcO?wpozr*aRFGPHc0c*0#( z_`(f&ik>6@GtV_-+gHg)L_hbYZ5c;zL z4InUxo;4o45+hnh9K<-*N%S zyS<_vlz&|P`;YMW_sWqxj;;v)z&5c!c4Qm@MBb%!9CDWq)?L@3@F7K z@B8}vq!-5VBZ%HHZlfWZ`jw@fl~Z1a+GBM5KtCAq^wTBc>CECAfqt0$nst}p#Zj=a z_xT}-NO?ngn*SuvDO^w%B3K9e*CG1<`i^cNdXyn%>*6O3&R_vqE1jy~UoK4xJViwn z2x!1Qe)`uA4bA-!o%EacD+cIZ03Z3FD?LE2YgxO*w>o{}VrhiG1C$ra;b_HF=(M#5 z3{J;+ZmEgd!1O`*C%FcxJMltrXdjv7CK>! z9vwRyyIWNsW6Wz5b2^aqCV$#@b=;s#nIvKu+z@g9Q_-i8Q&#r1!_-9all=DKoCTFS zzVugZ^0!5Uw$t{o0fv*&;Wmp2iBs{Hgh+e;?FENJpC1&+{ZuHP1du(4Tw-s%*fjcq zMc?enfRL9NpUfjD`|#OrG3uxAlMFpwYNaagv1lSqMqM>?T^T2)PScI)x8Jo0)WYJ2 zScR4uc9$*<$t;dc<{=}U!?^Wq@p`g8-+U1CWqM4GB@iPe*}(ioODh*Zp{vQ?{CeE9 zKwFM%gP+y3WBEiM!DMlC6f)8B>4~iEuu{pwX4v$xfiuy}u~xic5?^>6{7wo*9+Y6< z38`ZYwDt=_MO!r=&ZbE`13##qT`n9+2QX+X6!`E>u24|E_!H#`Jb>0y=dHE?>*q%;i!y3cRd=y+ z$=aE|Azi@10~?aT;%De`NeW0K>=&}na@%Y9HMr40geLLssqxUT>&PG*nzdp1%JL&k z|F3S~Xxq1;ldmZtb*C8&mJVtuCj(VU0&kvn^o-+uf9x$B(~xmv+U1l~bSCn~d(189 zj29mWw@+?s;E>~E=}5R%7zI_CczBTcdEF;+%p9F=3yMT3(&tO5qQjo^;PnU&|N0_( zWZm2*@T^hLXIpeR^yX}vGQ?tcqR;_|#Ekh2E$M_e&dD$}?{#CeAMNfA*2cKTN51zu zrucs?fUY#DDb})(GYyrvJt?ao-P?80)p0swqkg7dDSF<%w9|>Nyp!j`s*#Q0v+iWf zzckDJ7O*6q74K_cq}vU9MDbZ%J}O{5Qy`x_z_xpUfG6>j{5UEqa{XFJnu_@yKju#ZAw-3kk&3!!)lGs5ukXSSL(FNPgncf0 zK_IEY(9D|=C9RpDpQ7~y^XPfQpW&w(nXF=T zOgPujzriQfhn+_}S(%yo!YC{)b_Jvuj?R2D`+3NQ($eRnqoeeZQ4E+jL6bL_I?q`; zN-lca%1rSNVo7P&${E%cO6-g38cNm$;2!|fL^`#Xm)1M?D*)kFcgF>4yBAR*ucWdE zDCj2i#~Y5;2d~D!)8nGgXIoR1*E-Nd~xh_-w1JS)ZV$GP+}oJ3*fd@@|d&k%t_Gy)v-}@54ij7dtudY6Xa#Y#N(y(@7 zxf~DxwEV|BMApA|J@4@Aj*FusX4UH^>?B)*!;%82fsH4cfU*TgM=!* zWu01du8X9sOQB1y0MMq4Zv?zib}1j^VyPu5!ET{|gzdnsjO1JjgF9pPPW@y;L&phb z{c?Rp8@o4!4xrL?JnKKfY_HX?g@|rdQt_9BOyduNu(L;*CC9$aGcHT;MV=6sM z*9O=aMfLkOa`=6Sw%{izLtEPPfw>@uJFjTZ;0}H-i|Zna{6(&JtV($U6aRMO z{e1IPKLig_j0UWa0EOIyDq?|J{mAtldt=$~U^IMcpgR0&+uw?TJ43Du_0x?s~2?;!2VmVt% z@_lC~&BQvRo$S98Rz2jL=D;cDr0vhn+fP_L{)TwVT)qGgbrj$!ajlxdgA?M!lN81l z1IM8>C#LbxOIU;=ZAM`mIO6UW-#{GqNU5=`nWggWs$bFTcgb{no<%J76fGJ>h^C zQk4K5VQMB@O`do`GbdyDY`DIo0P7GK!*n@Q`!=14ir&8hr|%?6S-|+ z`MJKSpNvm+$euE`lHqKnbb}JE%bw3vwrn9kB5O@gZ*?0o8QbdUTV~q;ls|c6NZ>Gl zUyy>MyppG`l2uGKRFyU6>-*(>ylzntavR-$>KQQye0T#R5IyRs2FJ_+P|@eNeeDDL z{K{{8eX_Zcz6&Rb0S#}BO&)BVB{Rhkh>q+XU-Q8_1s^x<(BRxX09V_Hv&6h+J&dHq z6}B?{xBQ{q7h^=?%0^uZ?wA7gN6|m&&iOBgsXIjRgTo4u4Hmd^C1wQyN z{7v=k;FElk>kWQJ#8uRJvmFv5`#l{s_0jpF(MOuaIcRJ>4?FOKJI^{w>?!@Otz~Do z6+9s`?}`LaL3-TKitBTA=C>wX^Wt2Nz0$8cAAIc#V{D@oXxtrTWR~{G_XN-P2#&s$ zpirBffAf!WBVfUSS=xUz6#Jc-^Km^$alCXK2PI~=E;Lcp8HB zGXpJbbjdW@r;~C#S>lWpHo1Gsfi0({qfkTViC?MP#yxU+5;*`$6mb~kiaL1BHn2$8 zMGvm3K;e+H-zJy%^6&sW&AHh599J-1(@OD^Uia4KC0V{Vlk|wNg_$$FzpVRygb!1s;&TlDKBWa=X{}M+?9k|tj6IuAZuwb0Eqwpg`*E+$DaWKRH zcZ&V-&_c)tX*V{pr}e?Pt{HCl+^@2F=YTcpQi}+Z4#ob#r7=hvob*(l8}zCZgU2sc z)sO|wJ^v6Ko>M+6X+M>4hYOwOPrOKeKt&Y^2 z+kF~QN<{}v`h%FAQ&!H{y|t(lr6P4%c+`jE*W7;kSQX6|_TMz{;BbnTFD#2B2GU1l zmA$MrY88RoCnN;Axx1TAXgZC7*52vZ?+2*fnYeWrad!=y;A$4k#9R*1zl2*Y82>_G zl0H%naWAH33`L{c8@r1U8jKu#u_RT(UluzEilI{m|NU1pmIYb6GK2my?~eW|>N${y zO$Cb@?v*?|w3UT(eT`CG%6aIn;clypA)t`OPA_;z9BmjjT#zKW=LX3ehI0>;93EE- zCH3)f4`>*l#!*D=MXnLaxu{Nbz_!!L8eQHqHNjtnTz?y>eMOJ>n{{i?{U$mXp8+9v z8+T2wY2p%J18u+$iGov1UC`*C&C^%Z(rFKM?BzK6m0{CIDRaZwk?-bz>WEIqc9Uy; zFKoj8wS4KW96Uq*2_LZH1;6(r_cZf#HJ#^4VnUx7-oP%akc_zdEE~4PY+*ESt6CkX zX`4tG%9}(yF`PH>^Ortu07z`I-t_E={&{PQ(G8Y;EL|Jm!>KkKr64-YnI#iAz$q$g zn5&-d!Iq~M41U6bSPs0|&YDw^Pz6Do511R*!oaG*g0CGW#VMZ2lYFm^*in#%BgTir zxvF~0^Y2bJKXk65jRa3mI~wB_%2(PI@*bFI$AwL5XYizh!5e+1r!U9{NjATjGNZD< zs>c4D4piw$#!zbzg-vA5R=oES-qkR{dBW}4$1%Ga2YYY&Jc9n0?!Moqs&X-w>U-^n z3NJ5!m_jz%r3u+rc3WjFer0MpxKeiIFWpV{0mng@P2chUYX3~oauNGTD2^P!$WyE? zJe-a>)ta*H4(A98Co)LUBlVw&-W?GNFl9_5saR1%tFzMkF|Zy*WG0>BD}xavG`TfE zkx^dT$~!;&utQE2{2>(j}I&oOPx8nBmG89S!Jzh|B41Nq=z;=eJIUFi&<{(MQdt zwYYPv;uIn3Hn5Rjfp$$=`(<9-3zh=2^hAA{xlOiN+3)%k0iUMDu=l+V)m#vHCe?|1 z6&ZITJ!yk)RbJCMUpVOmB?%6cfLPk-L(&HhJJ73z#+b|Nn{J_wjY_S@TYcs$>7>g>V{$pziQ}EqoLbv7CDt+W_y&|SrC6Z7Vn<+JyET?e2<^Kc zj-+w12^1h&;vj^!6B`2oLG4#Ajko^(K+J5vunBmye3-tO!{D$wj*?XuLT3=Sk!EU0 z=2Tu#N&uC0FqL+HzJQK_!p=W}0h`=IP1KKeIhFg)*tXzKgQhq7z!41}0PyIW{#~*8 zVR;zQpJY_cH*E;;xHmT}#${QaW^Z)t`_}H}(O^jxTn{S+V0>(MCCkUa)*A0Q z8Scld-`xfUxtkNF4ILyBx8yP&{D$(7=dZ>Fm`j}bl;>6C2L)Yziel>+5{BNJ!-FjM zo-!zZTngv10)2Ba&d)kNE#aD(Q}8{+AMR4&G<9HT5}VkUcI|!2KTWzHw(q3L7WC1- z4}+Vf>1$SJVvu{wS7W0gnw%G`{IT)8+@3*FZkt!p4;ZKHYly&=(0G0!ga(vm|6oCF!g$E;$lPJYJd9V^s$S)dmi68hEKpB z?JXf1=;VGQu}yn3(iI85xE&hmzZ0Qvi#fO`I*?p12>XZ98~|CW*#=hB!pibW=~ATX zdnTnOM_nOU6%~%Op*wlwu)7xHmFoMNaOvT+#pdV(%!FY^Y-LnbKRKL7N2k}3ve=r2 za`=#!Q@#GD+!+Hu*z;R>x48%nlr7@LS`uE!Tj=!64A!s*qthhp*8*|wQtfyCtLx(N z08?hMA_679srY)_9M;TuvT0C`sme-#Z0tmq1ro2Jr6)@&1#S^S+$-Vwp_C`dCaJ~D znPR0RG>=D*tFqSLTuk2oX7|(L6H3aE-Zi{<8*iVQ${-uaURHuJF-+g4aEZ#_6@@nE zamx$+7DEK(VvjEXD;b1N-5|!HP%STX8ps2M{+<2R?V)_VA~jt08PiGc-;x$@`2usv ztXA=fCzC?I(H*+H=|&{B-{kLWHy>y;R6*a9f9nyXL@?Q1FczM0WGxTr|vqpZ#z4^2RF{1WCs30DzK z_deOG=0H{XoNIa6AO|)ykNs~IeY2}vl`|%e>}m(-UKHD=3SAiGSNHHBkYdGj<64NL z(8bNtOWLm(iKSmgnS4GSc{t77#Nh@3F6m_7UM@eupI#f_Hd#)wI69XKD+q80xn1&>p30(bmpO|KNRc#To*%RV;|K=4c{=yVe7}BJTBc zIum9z=pc?ftYS8-JT>RF4LRADs5XB=vA+{7Z7n4kLnB3%0tZO*-&u}ayi>-2oJ6HZ zuzbH3=Su8X*uR{JvxDWI^ML|ViJIdH&wV-rZ>miOCm6%fm)yznqQ0%J*5>#E8g)Ux zXK(nyiIB;i&&;UN2O=^|&*v%=xWjF*DR@WPRtOzfYFNF~d)I}tr#`n59>|xkngTS0KM2nROudFz=XHcAa zNpMn*{yO%!@4|By7 zkfRAoyC8Ny7eIvo6uXtqJArNl{=dtGZeO`);a4SSf3zET@YQ{wN-T@{Y)=6`oY$;< z|8dWSolJG(6eWqzkOm@_B~V~uyh5uOg=s}b*CzlBUPn=2wRK-743tz^Ioqlvc@0H! zvN;j}YT9_sMpAT8EqdU!F_YWn!Al5D4+9sm5XD2>97c_rxP)DlV?5DedU%x-S|GH` zKyhZ`3PSz9&o?ALDNGL0S;B%nXlqW2n25rrzdsvQdIA&o;Ph$qtAkfi><*Yj7^bw1 zl&`wtUC@yy_oR}RIQ$Tj1A>LJWjqv_EG%ueyr9vuaIsb~Fep(_g8(Y&W{SzD`HOub zZO{S~b&iYIy^^BjuzGbEYA$x;Wqs4Y*(3N#x|-%>^tTAsU5T>G?msyo6OMgem3h7M zky)Bxo;AuU4lsB)%fP)a++%~LPq^kZe}8q%W2bL{mpqT^QE07tRT>?AT*5Uxu%e;T zedK9`K~uQSHfUmmk`=MGK34f28@O?9*imRMu2LnFd!ZRP0J%U#pEnQ zNa9)HqfTaGgJ-w~SBTI&VP3iBNb`D_%r1O%H3sDg!EKhM5~*j|ZGB=bF)W>!Bv@d8w| znsm;`>iI25IeX5yB?XJ0Ej9`lNo_Q2C@m^)$+siUhn`V#VE(N8opo?JAhfmSdlZ7U zda-#WB=&9BsW-T+td++zDEwGN4VBB>!30m4F>T*Lc-2)A&)F6P$jDb^5~6|uIJB@f zK-1KEwcUI}hw8KO83CZ=K3}8aRuBKEx5d;d(fWXKJ@p3^+BQ=ixzZOy2`p0ro%4%O zY~8#96I{Abi@)=A_}5Q)Ka=rrc1a!RwgAGahQ`aRK)o6T3^rRYjyPb~v>!T*&fI_t zMzlwzqK>Jc5f7m=jx!;RQozO)&494nr^WFY5(6OaM>LKCEVZsL;}FKK)9Orb?a^dG z4Qr3h{%aqZj}I$!P0DjdPi8%l`|!PK zaVOL|%3`N1vBpsQGAC4dw`3`6?q`ML1RmQrXe%-1heRe?X67s36i@YJd|FivSfo%S zR{G^jIka=*py@S|_=e1u9q6#@bOM9)ny03C9^Y-TcS)WvNO?7hJ;Q)ymV*j>VwP3bbF#$AK@k$S?n!9J3t&J5NtqL$F2z4gMFV<|3E*P3joA$#QYd*3t z{}4Flz}DaUPMn8NGU-v!@La4nO>Bg@EixPwb@`e6bM7BkOsJeK?7f#G)d*Q$5l7++ z&PD+rvU(eeFa4?Qn9Dc6jthVdFOk>Gt)vAtuo(*VDs=^F?r$dc_At)h`JUSUEkcso z0Mc8S5Ec%1+P2-HF)59PPQQVS)6w!INg1BfFntDT(rrO^nC0>Bb_vuzrH%n0o$Wdy zIBDNTHHBMW$xSC1yyIpnfSSbfO1LkNkRzkKfR;!)og~|E<0W3vAg}>;XeEh0#Yhm{aJn=NAI4;dB49j0q52M5AZ4G%*&EA5C_vNTUbA8|QiFu4JD>PL}nF0OD zO=V_bKqVBC$Y81x0gDq^@qRzwF;GbeKs3FJ6WwC_fgB$==-bFPNdU8QY%+>1FN8e7 z>Ls;ZC_MbsOFhM?zGf9oBFe9}ogDbculMpq%LSK#X*8*`robkTlitaMbNc5R z&w;m21bFKh^E)_NS8*HH`rE&w!{jH|EjX2af_7`*DiI&iQ02uoD5hvXXD^jMTv}wC zgLmlTEgDTyNs|pN2c%UoUd~ZVxyi5@GF^~CyX%@y<1&Owm~nr|V+1*S#1zu4KiAbV zbuPr>B}tbUucU9v!Wk~>&Z5E!yi12YvfGeT5eR#jA|Z7MZk9&HJwOaneHe8LuD123?3g^JlJ5j1-%S8;pZgrSG&m} ztN@V!(hhJ!6&nck^@>to$w>Q!g9Dd4YIwM4YxeZopF_La0&IWX8T{5=={zRojF*9N zQk2#y$qG(=PjUl%{xcv@HH9A!ZLWrjcwcg8w|XhjZD9ME)zxqZ9p1}tOK|~mDqZXJ zmTise!DX5k>*RCP+Z#ffK%uz(CgUN4gk+PF%9qCRpgX;ec>OY^0~dg(O{&lErNzTEM3FQ7v1tz?kp%n0qIx6B=m zQTjMHKjXrdOR19#vi<_5)je~H?3!MILSMI2d?q@7y*GlksZdDTu$tU0RdwEMrEhKG zyz?!m;c_u+^yfiC&OK;mR}E9W2f#ZQsRmB+GQxa@WV`L1T|ANNz6 z;iAuGW73MV>eyY~#5=j()r-aibYQYh8D^kxeGyDnWOWwok4rAIqtVUJ5yUAc+0YHF zx@*zA9<{Qg(PrSbGQ`RG)P79jb}$#th%z>%$U=3}^%LKqzgpMeSm~X9p#42yT}i%O z_yyBb_qtzbzRmm*#9mnx1rEWd%MKia+67o^sFax_Uj@#~J1a6yI2-0L3RjqO3PI)QrQ z1{PjJIVGY)h*s$FAGipaW|xYS^Hvq=w9hfeq`oeB2`(^*+&f^j-lZ4w2pw#}gyV{s z>*#ekLdU%V>`J$aoFW<#uLS2PMW{tLZ$NeHzwVz>l1a-{DlmH`D?i^Us;{F7vW6Tl zD9%CH&ZkKXJb4Q^uc`$Z+p9Dn0ieS$-QA=W{I;E5Pw$mm(A*#Yzc(BGg`y1JE{e+F zm}-q>m$}W329EsjB7PlGcwxXh_TxXH$m7%3If5i=Pb?SpIsab^P^KdruK}H-2=zQJ z!u(tje1`~U&&>82f<7F6syr`*A8bzNU01o$Rv&zuEg}`tpxG9HJfQ5^2_HW!O2C0X z>O5!bG8lTn)^hGHKKta~Sxzn)$G)xSeSYdv^v1#g_v$IF;!WhIw-*|j8S^L9q|I{FR1xx(%(B{kuViD9vQ$@Ry#sU4~1@N<=$%PW?DoL*CsMJya z*qqkegh!LV<}YmxDYS!fbOR2bm70 zI|O@^<)=u_w@xuk&nL{6>c1HWUG82z!k4jVKPd`?G#ZDTDoSt}wbzPXFFyemMVm{= zXoXSdB{CUxlm}bNe4h$H>M;uUY)4M$6$xOoxRQQX%;6?6c&UEul^6@gz;#@hMU9&u z%g(%rM#7-(4fkiu*L$+91zOam^%K4wp-Y{`tC6@N{nu|e_9eX&agHzjt?(!myWQr! z_KSCJzrj($|IG3j;k`XS4qd*NGGb|3G}gE8nN|BVbw2H)ilU z?_06<^A+AgNgmVW&N}*1Iy~6;auhG-9I=Jvcg?4JTR5qHT_vt@w_nh_|uBZqIZ%KcPvZ(nX(mmBU-Gfxzy|%P!A2% z7Y^1!gGj<(k;b;(Er1^qABVSq;O(zt)A+?mqVwCajPDI*#txyCj}^}s{0#Pwyjq{# zYLnmP^&ai*V!MvvRO9K03Bn2|aH$G9QYPxhjeh)kIP_O8v#mw%?>1vhPs^?`(QJ__ zz3zkMyX5U1CC@AJdwf?D=?s1$ojTa9Xs9G^0Qyc=Pq%rB+waUDY=Q$N{Z2Z`iO;1@ zxua;V1C?0{QRd%Hbxkw$w@DLZc7_(#gZZXoffb?G zoSWnS;OVyH>doFTw{NcIal*_iF235K5qRo#c%FMLTRrzZYI_}_?k;-pHTu)+&NZ&R z8YQkP>G43o54WESoQ9-hy0>op1*jpU)4^_;n%!sPWwHY@W}vFXcH;{2-&9m?>cr%9 zGnvzQ@|>m3kjocSnA?)|6^k$HL*9>?Mxywm;V(Mh{W!Rz^$O#3c_RM=iu+bGmo%00 zdjoqESUI%0Ob-`cxvh!sulID3(9b&34x@S3y0@`37dNMHqQTaq+IacyZ1~Uk-y__e zMb@r z4NpsJ^kb#;77Z(ck(D7(M}wTfanV|};0pV@n$H6%&zAfYxTm*ANPTj?IUuj*^PXls zdH;pw9c&)47lHk3S;fNJ^oUv2oxuj@;=1OUi{e7y&UfB9#g-V!xpeT5mNaHGqP)+I_CE;PwK|w zh!yWYJxh*dg&8ScVWf_9M zP@$n=xD_6sdQw+=wzwY|EHuFG%N5k5E)Tal`DzO{uWRPN=qXqYIGP17TG&VB3V?Es zsi3Tth{LL9k-Vwkjy<=~XCB?tPbh;vB$QfdPoowJJaK^V6s4xR_EDweC!2|XaqrtI zx>`7b_y*mtnjh-!a#@Y0cDJ44*f$P{9|z!7+=$=?ONbF)uA+6FIlNl>@a#yUHfs5N zkF#|F?03%RwtI9ZnSX4zTV@|b+5OX_|LeEGY?YUvS0<^%nORs`4%xUG{2w<$I~O_; zKPEdn*$r+KTJBCqj}qP4yFqOOvdnJt1FQ@Ky7}RB`7j<&cZTH7O7xSu@|N5UDgjlh5!4{N$?8eX9V&rHzA*JO85q3M`+Cn~Qma|&9l3(T2fM{X;4^Q72&E^C4 z9lJ(oi`rC&8bv9h_NZ!UYtInW7DUb1o0clAqNr6>TkIgQtM=X_5qlF#Y~K8z_kG?k z`Qn_M9QSo!<2SC0))b}8((K7BJ$SBHjD&tDjrB9yr`gLnAXI{WWpy`Y^VS+C(ri0*C8$Ct9A^)$k7q0_F2CH% z;SKj4SW+V|V}kHk#fTP%!Pw0_hHSUe;z|FmEQ}s4QY#7LSiC78$nY2Pww8V6xKUpO zBp@1HrN5b)Ss1gsE5P_0>1*2yL*EP)H;*f4=1cVr)b+@MBpt7}M-Z-QH8}wX6~X7* z_KIs%9_eBys2HHjsEFT_y>^hxF#W!eYY6h1a_Eu^Kn2(5KhCB-=K5$p9VDob6ceZ8*c?_c>Ot6#-7q-wWvb}6^d3im=x0j?Uh`~cQw zd#2PFzP8>ih~e_ zmvtZhJ~+Lvcq!`abjMi}_a-??=J3T2!yw_YaWhG=vDR8`5C7!1W8>JfmbBZfx64>W zJH=B#?Jl)X@IUpt_rIsM_mli&ZK5&LbW%h`LZe^HK&qmL=sitZ6mC z3scSh!X;y|IlKz4@ekV*(iqBhj*@~?ZrMI7dfYC8u6yldSu*V=Q3{OC2%Y>07x(q+ zov||dU>)*Cikmy2&>KFpYUMKJpLN-L9AuqHC^>cNoxCy?4**@#yS4zG$FE$-cAY$! zXKg4r;lTu+sSem#t8k+_F`z}aM~*M7{wJV8pkzaw<&25oY+aC2IdlAe20~^SdlXf5 z1C{%wp4NfY(O?|opr6|?>z!v<4t^1zpr^~ZB`PLSoNy-1Fd+y4T=-7-;Wup z9REFTH7W=57=jBUt?F3#*xFI1Tp`bB{QNVQFD$u!SHO^QpS;qkYOrgbj z`KyX#z5HTZb~T}oy;s2t_I1miApD`^Z`?^0IKM20Rrf0T?73_8H3ZFX)iC5Nv{La@ zU!H?RVE*Pl?is;o=5wSMwqtb=IsISZzovF%!n)K}xE^Aj4f%X`7@lqPH=3_)=no25 z%dG4Tx?Hocyw51>Tqn7D)cVp6urBahz~6cH>aXayCf3}D;=VnSv}JcKu2sUwr1wTqeHu8cqbA|k@-dRN$ zo@~kDBr1>AhJ)L=&c|$<+2Tt9;NXPm_Vn0CZ7ExM4CL3#lfsm%2)6{2A*uU$p2t49;=HIa=$^C&DNTwS^%KwhYrh9 z>d~1~Ph?$oQrVzdS~JZjrO=%-SyU;Qqb43KlI%9$)J?`70P>%&)frUvke`K8+(7UB zmvxJT8?A{!TIIdrF9IVLHuIy^zk)(;*}D=(GoNdxFFrSk)qCN*|@;5wOZ##3FpcSpQ&p5u9ZejBv^;}MVR5FX^7?GI4tDcyI+mfE5 zsAvS#%@JL|f&RuECpYru4Iw;&6WK8wF|M~>@qtyTQ738d-N>jQr|IT`qqx0a*}3>g6TYj{qE#ta}ncbKP%)b?d~HvEUDYS zNo}91_{qRF?Jx>tWDyuCdPt7BSJtN{=6va~PG9q5=Dp1RR~g zZP2hAk`)*-VU@2N9|M`8st=^-{G3zEqBmVrrxKzkS6TI>K-T}trC#qL8G;V%&WBjI z%}(i|ktt^V&?_a1`Ej|w%pzcq3RTPhB9e%*o%9F?RvQIvdk`bC%;EnjLtqw+V1)i!!p%1fRU1uS*=Iu`jDVyGJ1bf7gZK9Ld-1pBLYyL?ro8w%NGAC z0uYa$l5h9?y{YO%Uq^yI#Q(*yM-5+W$@Tr!k77T$qY5>)2z~Epo^Z)a=2~|9&V|C- z=^!$3uzBdttyg!h%&8!vnKut9mmBhOs-G74eQ!xqs?+yZJ=$StDV~;LLL^5|-NyIi zawu6GZBJOG%%6LtMNQz*wJzmVmOi_eX_$bWn%6o)OMl)e^#^a?)u^c@w^*53w8~em zc~=lu1kb_K(@%ZKfBJF-=mo-olo7>EuN}4NMO%F$ZrPSgC~+m{<@`Jt8BI@{Y@5hV zv$}{IVXrI=-GO$kWH78}qz8-yZ8XkTbL^#>U=nNl@+J?`_CQqSIX=#02xsB=_hhi9 z`XVw<3U=Ji^&d%YESYC6WCNpclZJmHT@gJDqq|Js&2Q**5M zf?ENaktz9W^0lwYj0BP&&$0lJUnUcn7Ot|3BPgptSA7qqo{v&Cae!Jy>)JR;r;@-~wHcKrVeix+vMW7~>Tf5~eGN0nX z;GhmB1IDuBr0|V$iozIb9H+>5Lw4H`m*Up5P;)P)WO3B|)4I5A|3q$gkyKQ~C$6ek zk<(Y{nPdr{Z^B8MVk+Gwzh?h3wW2SM?9l`{v0L`4njb|_7G_`c#jjkT9!|*&|7NWr z?$f&q8et#PpgLFiQzaJlY1oBWJ>A-JyW1v)ZsBWP-i>W9?OkJNQc?(%b3q4Neoks% z7H?|!u-O_GH3Dn{5eGE|XbOlws$vz) z!_Prpq`u;%i&>T6 zmq(L`72C`!JK69(Z;U+x+M2G2{mSe>>}a(iw6Tn_Q_3I$RUq(cw!_cD}W2cPwvj1n_i4X|@1$)P^2G7kY%>j%f2g#8Qb zw-LY%N@*YGAcX)_WN^wyx=IO8p+VUv*SL`AEo z9v@nCH=a+i0n~M5zVtk58_6EMxcY~$yKQ;DBm13ofm(8@L`Ke)c=0bu&tg=idlyPm z^1D<5dBd0Hba!~JFheb+siAb8l+;Lt*2V5#q##!Yd)wik19GG2Nv7Esw&_RhX0atF zfA`wR;%-2Gi9bpHL<6&R>t|#q^-CPl)It=aUajPnTK!?h(h&<<4Pb8b35NkTr~LW1 zT1r=o4};L?c$V5IVs6%&?n{iH_Oh7Yn(dUF7dyZ3nV@Rq*iV~Wt>kL;xz95{dcHe4 zcV>tPA=^WV))JHSfpFu7kfL{-`59J-_WfKB|oJ|*-PBzyWYvrU0; zlDqTg>42LTi>gz&wx{|Hlozh_#Hamhd3lS5mFxiP`UmO#vr&OO;^Vn@OQkr(R@Nn(3;9 z?Ge%zUI{*lx;&D0Ck;D(cleiyyZ+(rE@AU+GYKszGzj(7)^?5x>y4`7#@F*N_igA~ zogvn8CJa2Hwe~z_=~btM$1cFCQK{zOX3W0~@Xi7ik5_r~QU!^T{~p}gMRvP?D&OT1 z9`mUaS)1f~wW$OqA>EtZUIaBcYwn#T@tmWH|9J*b3o5S|J2zEJMm!q(`t_&Tu`$m< zjg=z>Tj-HM^CF!GZFq>?+GicvrR_Ci=5vTeF&p9KU=W#Tw&8M)Q@ zuFGa`7A*4BOe&`sQeJbue|xx3r z6yTuAvtxSi?}Q(f(Lc)fJ((o_9UElc2VV~AMCzIkzUgH;M)L%R#eHf=(N1x#w4}|l zKPmYu{d?|-+7^j0HK)uLVJ~WiBRd^XSNB*bQnK!E4uAAq`O^ou$PVRKKxPIQf#4Tl zV$A(Dju%~Wsc)XFjlVC@AUVYRT;bRkfcQc8gM=Pdt?xq z@`Azw><9mMhu$)2JbVLqp-|j7jlG*>)WJynB~~u1)V4;O#6CH54ses3yYkiwJ}TPB zq0W}<19z;mIS+&ue{={ug*P_FX}{({Xz&|bez||PzT`p%U&}ri>7C7-fyFDjAuzt3 zmlGRLWPrgw=ecCw{iR%}rHUZ7p7F*uC&(5QlO>mdLovt0O1DNXFa!%??1Og}dHgq$ zvC~4=C$z-YP;ulxFnF^+?#L{(lpI@1ZZ1ESgnsoS;Ma8V0B5Bh;YlSeZ0My1Ae_@{ z%+-Ij`_Xh)S-TlVjfri{+z4(3#fM4?qTz{er*nyI0xmLxm%Z+%xUmAJA%mnxWU)8< zz>b)%V7KH~GchIjWJAi8z2BZu8X9DmjpP>eKl6Uqkb7W?MAE0o(S6v`i^omDTPUoJ z9_{^!R?9R5MX`MjpvWYS*d~7!dzby_MEB@%BHM!{@zT4&V^I@+1&t=-Ud}8{MoLZ9 zpaO`r{Q&`BZWiwhbbTKZNs%<}?(!P~rBY*b z^ONMk^C)efJUo4hchpa~$^_*UM>aXFpZO)1-R|zoSV3X_00;P(3ypa#B@hwW6T8M- zzGWEIHlodC7R%d5j`pYp5bxq*1yo;8U;5115~VwARLagD+O5$$x2l>i2#b|C)Yr+g z7mYwUE*vT;9+o>%Sf9C5MfxB2BSLI!&Yknq{j{)GgxDQbo(6Y4*wO|7lLXi+)b<=? z^&Ilzq``;J(BM;9c`6d$%3%M-%k}(+%xOdWsK(dlSDHtf!K4U=jjm=PTpUS_zVHt; zilqI@e}?Ts{FWwVQc4ry5Ht4frv6jX-HNwiQ3k2-K|21kW}0?_48>+)KoC+#pq-Yu=Vu=5(b+q6ZJ zo|@rvWYN<5+OBGrd*d;6lNQxY*M}zxT=6iydDb+c`4MIS{%Ua$JuPxo!80{7+jtP) z?9ja5RQ$iqW~NX%1Q*z;nbmqR%(^;^XRzFi^0(U_K5f7B<_W;(;@h9b&swW5@%!|;c{f&NShN|{>cX!MF z``v)b;o4O}#M)+5Gic1brR-Yn6kfPnYh`-_=OW}YN&Bcr4kFm{?Caxi7*^BvyBwM6 zF+Jdao8Hqbq(PW1aGy^e<8{X8#{CRMr>9&+0ovqN;I2Hzhli_+AAb=9f8G^nGB{V! z^{4d(rDz?7VS-elSI#VhzRq98%9E`W!R%jGc<*#D#OOMax%;fFJlrk{2rG{bPnHTj zW>9>)qWc4~jK62w8q9s#X8IX|#nK!`^|$J=-5dC{LH8``v9AwBy}}sf5KwgQW(l2+ zvRqh?)e4^ejIi^OgZQXUywpH>k~cyYw)$TIWwvx?0?N&;9r~9Dw-4KTWp@PkwwDtL ze^eP}$ruzDARZyETz>ljbA+yU##}|HZ~2TjB&TZamtV=QMGV8mS6Y+RY=_D&M%tlR z28E1xSieuhX}in+eTxK%Pi)rtcY3h8VVcqTk|O+P`-rSXkQ|A~ZOQ_N6;55FdbGZB zzAwq2lIq4yXrchs5%?Invr3h{>Wq!XK6v|VWP410H5*)rC>21_qsZ(~K5?_}JGpFg zqq#OVMWV+6^>Y0}kw*D$fgkc$jUw7;yD0E}?P)O&?|lrNSC@hXBon(`2#KgO-9Hla zl_cfF`rw;9MqBrAL~wPfV~DVZ;ICcM50c;c5Uz!reqw41m?Ks8j#0tE7OTPSIS;L> z(LveSRZcj#Nx{*$v~T`sy`tcbNFz!h?6sXCDbx{~aL_BPHW5YYof6AmtoCJp*7<$c zynT|a0Zs1#(Ou#W=L*VwSNFA3(<^1rYSxtOA0cB4R?Zzj;_|;*fJT2~=&x<%!t zzxu3N>Z=Pd=a7#F_GO}F@gw#Xu<}$IzgQA&32O4r^YQL6fc*I;e~7atEH`Z|uQ4Ak z`fBY2-|~s#uaf1qjoLE=EO>n8s$JvOI=}r{7wf)IRuJ`@ZFbNS=W&+*m0r$51Bq+J ztz-ogopcODOtVt9C9aDFLU@rz%Q+^bgbPeGzsMDx9_jR5>1?(kzNqn1#KEM@&%-*< z$g&U=rqoi#0QM~v49>>~A}Rh1atiAXlZ>kg7M?X&gXEvU zka7}xisyuITEb2=bvN`<$>fiKKXHVtUz^p@o6K7%kUM>Atb1GaV2A#DT-@iWVSn%G z-TU#LUOqO?bCh6fatI)UVRZ;K%K(>mn78gwS@1fNy3MLG?e!28D$?|Oo@KL4uKB~m z@R^4~WCoBhS8B9xNNK^H&zlPxv!*4!x_+9p;nHt?(IoXc=of&u(ByHf$H24xw~EL0 zwU*j|LU{i3C_LUzB9*HD=es&r`wyN`enkKCK1~oc%3yu`PUVl%9GMWKOy{5dNs>{d3G6xW4_LnHTWvBN}l_8QP`Gz9v z7>Sij!e}l5-*6M(wz(9^R8=btvfgoo`(~8~#w%JeC?0(4AFv8;;jNW9Oh$m^ed1sL zfcYOo*6{w5OIuKw?9z8-*?(+@H)WT(qYG-?jT5%aR_~vWepsdv7JqDkVqF>s{?EhiB+~&WXMtp4CiZ{!G ze|XOnN{NYm3A$L%ji0$ZRxB5Y#qgukD@$hoX{5}gUabzw4~j^;fUK)k>Pw4IM6|M8 z2%K}Zc)$(3x#`O6Uid`j;!eK>`t=1Tk|l(&0c$l9Qc42y(zq?@pI6a?J(*cxAzs9f z?^jVw)Fc=3t>A0CJOGDp_numa-mBhkkN;pp34ql;ePYSu1Op&`Pi@g4vYPVRz(|P) z1;AiLu%hBxcDge?U==Xy?cXX4td{b0<`%Ol47fK2`SaEHttd3=(u1zZmrry;TrD}W zjYua2>SN@785#s0{UZwb#s{R&r)!_EmJ2=9ZZa3Jh>S<-O5CzcA7Ll2aU~h5zm}^r zB*GFkRDQG3^}knX$r%742e zf4e*X_r>cddR^+q04}J-y?J>TgXzpalQJHGdw%yAWZnH2_dJzC_bfc_>yxU@ZwI}Yw+hA4&v3{>r4{r9Q@&k55y>Rh(?)_LN! zPIJ(~wtbc(661|Resd+LlcCca+h*6#QuG)^0*_C!JB@jX4+b5pvQBfT|Rh0?bG zoWQ#p|07)=YIFc&u?79+yUVo(h2SMUGQ%$qw9jPqwv(D!lIju9z9_3YSuTFu*O~SB zLu`2^t#V}kLyZ)tX-X=!|y|S9YOp+JCC%BQPpNR(>C7q1O8mMKOEO ztL@K-Ud^(_5oHyXphV#(Em4v8930DqEsgS+uFet|{0Asy|4Au4ff@xZ)2iJOUFy1F zr_-F{4uqK%7d2K&YoD&n8!|zVAMQr`3`J3{j~Yxr*RT(X;mBi*Fjo<=G}1tw!S} z9LO&Q!R_hVwpqg;CeWBiV{Q zj`FGjzcNg~KjG!5MKZv>~q__>r z=S^LktawHH3!mN3>10uj1)XR4^x^frFj-jMApMa|6t%Q5w>co(Aue59q6pO!db_zgZK)bvJRz% z)P^(SqB>fYLE|?W2LKgel7N!yk`H_y%*ewXzNv>ys0NN&O8(})Nv^{%W}dgrH9zM-!S-olVi{hc2G+AW zV>}-;K?zK=OaG?GvM;w80%9Q5o$hbxwMn2Ox+mAD96APVE!J}mX#TwCy1M1IrLexZ z_3PkW_OQeAmNY_4NI~GkX*~VzOs!?At8c>Z%(6z0X6G zrCy}DZb8jHhT&PRSK4Us?;L~;mZE+No!CB3G;OTZ03Yv)6z*&sO_9u}xlI(TjtD{R^d^~kxijGBWxy?o-cxzrpxQaSG_U{$vu2&Sp33tutv zl+&4Pw(*?(-Dra86g#K)!UnK) z#0q4h(U;rHX`|X$%1~+x5c$g9x6S77J#*Z=OqwqFWApEsn*%W4wV%0(b>_XSYNp{@ z;l{n_Nh@A5m|C|m8ZCD}BM@zZF@L+7RHQldrY^mtak?klX+@%XwZ}zAdV$I`=0U9B zYPkeX(u#fkk8<3s{*08=P_1a-emY?&mv!dx&u%lX$CMLJP6$!3+23*XQyo`+r<~y7@$q5eL`ML)bng+qP zKq;#OF0ss0^erg+!7ow@3cJaafyA!EkuapnFtfaT)g!YY8952U_QuON#dG;CfAQ`W zp2vQxAtnyJWUq6`hP$i*XHx3~o@d@Y;LE^G$s!?E!>w8&Pk$ey;@zIBgnVRu)4?gV zpQezid{`eX-#cxW$$|G;_N-hyNL~QUX0M@Pt7o?-1eDPr%mityw~sWEqAW6Fb-j&u zn)*1p6Z_&?uy&|u)e zd&2fw<~t8ArJL7D2G!cAhHNA6$gw)ZJK9VKzoVvTv|v9w4s}iGJ+~0`a%esS&k@|NL#Z^1RsxX7NIw_=YEM_z8#m?>4A~LZU6^!t8 zvWB(c=M+7kXC*H>qOxDWbMF1o8&(asljZwM*MhR{{GKmP7Z>MY3C#BbFJMZ^5JD#7 zoDg#)!xCKbiSoo&q@F~oGi`)9QVal?TWsm}T0L@q39OBLMg^H+cbg}wvYM?;$rj)c z3MO{5*k6>Us&k<|gH28P-nK4oEx;N%5;q+co&L4Fw|)buoikNg9+mz6Dr7s{McLzS zZ<+nVhu}84j;6gauqhT@J|pE1B+Vu(KqW}`R_O=6#MGR_e;UI(pZOEBKrKd-xpkGd zz1w%g*R))GwSCnoS9ey^cdI#?E-iZ&4ZfOszR-UEDf)gZ_zsnGu0H&f!Jw_^!JQjq z+jA+!p*2izq9!C(c2}(=SNBJnXT$QW$hj>@^FXEbd7(Xx4CVEWygXAwBP|8O7jr_9 zn0f9}=+vDUg7S(+m3AHtx(T!7{DDR1?oa3D8vEZa?$_0C-VRKS_5H}&xm!L$TimKY zo#%Jz+gmLYh8R06>M5>{l&hNnuLxV2NPW0K9-jQo_~cMohw$p~aqGI;;hD3VoPV~F z508Jg%8I%q%=)`_0P$tZ9T+pa`v2Z{TCd{>#v<HdSXJ zUIcl>Y16z{qbE<8vOmc!NqKRR!lO7o@a+93liZkxdX8k+^%Pb^&GFh@4(5xAuWYxs z3CFuMVM6;wL9pUXa}rpyJ|!Bt^k&}5=Uw05PIVVn8W?R0sy(;s#7@*J_E}!gnN)}o z@d|3lTQ<$r{P(>QQTz|?ZqvAv0TwHzg?C@kDr8e8`3y&oe~kwL>PS&KDkLWcRqXe> znP^!;X34y%>mn?b->0@0BP6HzEcSXW0%Ev=#&l|*0B(-7up218-S@bM$w7AU_Y)!o zPmQ_-_y?hj9CBkQkI>a=DCxExS9wfBQ3-P zj}}8qRn1?b_4JGXs8Mo`pV}Bbq>)ziNP27Qp!%@8$?^&00 z_jwK+=9?`BYgEJqRU8uDySzRhNZkHmALnJeCm!=j-3e3VDj8E^|1MnpOH!uAsSk6m z0ew*(d7_za&xz}v^ts7O15d3xsqt$r)nvs1!la)u-cuy0A1g}5B>6A zA{)RkbC3Blqx%mGjABHrdsHxS3q<(dWaVi2(>qJy&cyfi`{G&m7UnzU)E&cHB85&E@FNK&Z9+BeF!vXGQyO6N6cob{C_1^lu;D$@3Y zLE8e1=VR+YH0!H0`o3I1>Jm}n;hfW15xZ|iKPosDEsYsi!WLm6Z6a(co$0_ z|7Lscm&dNM0MQAkvvXhO(c@)&t+TiH-`%EjZk`wIsq_WSFP#C~nE?l{=v#5*^re_) z0dLBA5NF__*tky&*+#0%?pB&1?yIIqg;v1gCi6HT%VQ+saxFG1wNf3EeAf$^+&C*5 zQ^KyCJj$A^X}4awt5MJUQUIlAvDL5LLc2o0=?b-(iskd(&2EqtpE(GXWP0=I?&rQ8 zI=PJE8MCOZf56P-sBNPM>+MlDhi!E7HfdnDR1FP$Nb36@dO^uTK+|kDnpj_Z?71%Q zH#G7_jgO%zB|#v>@L~%SCF!0Df8D?Rr!0KXBG-;yj%Sqno|W;uaBT<{)Oc1e1yIiR4zK58xFr!{j`7aPFHftLIUkN^uTa42EIa>n1@l5$yJgU=L{Ho(A=99r$Pom4+j=ciQ2C0O-4EVJGY}qHYcIN zW(8z4u6T)>5-K@-Llw^w5i_vM-7JYWP>%Y7ey`F%itP2>sfy92bCiX1ibYUI3}W47 z-se~2k^g60;hgyAk}hn&+U;o+STrr6zcr^`iyO9q=~&4?#i&+}(-pni51o;el5w`S z7M>Lo+sumo()C1UA>Sify;B7PynaW6Wl$3ugG6IlhZZh>s!QANix=|RZv9OUe00&s z-4cH{2)&@L&v=`8ZUAS3A~&HgTN%{#;exMV-gvc}_BvaK)R52+-F6h)$r>6w`x zQxqpZ1K+fl_5#ZVZoNRfa?#cF^+iz*$CzP7&Q(YnH5?1*5JxYeC%FhYE1oOy%x|9swyeCXpOiVtqD<m9zuEuWl3M+tEmmg3_Ou}r?Dn_9xdK*2q zkzvITC>1keLB2-t)2O|nEy4FKWkmJTHeaNXnC;=o?U-D*d!`cwo9OX^$qFqs$zl;uDD zB`j^fcc3ox#)&_-n=`x!@v$KuL_g<_CZ4@H88nevxaJI5$`)1d@AoE!80!Pj9^P9S z!7vc^3XTu>k;Jb-Jk6Ru)ADP#$obwwDeZCLoD>0IgcahBH<(O+wt(d`uVjEIM|dzd z3Fp%sWkE!7*8u{h7COQ0yjK+e_9JIU3;SEOhMFAyj20-b+SkZ7T8g>or@3J!8g)Du zR&GE?qSnI|_C3Qz7Ec;r1#JoC$tudL^7Y^V8}9xK`q`XR78OA)PjyeADN zuSnDaO^B`AT+nI1dj8O*UlLx|JIXJIy!AENqj7I3MS~547R;t==58m4JeBS8iUD{2 z+u}ukK`XKeC*bUfaj&T`0M=`oD{NE~WofHa5g~+*nfd9ZY<2ExJ%lqY4IqU+x3t}l zkc*{964!y&zI3~}e#Q}a0;fP%3-OnR4#j@>&GcrKo9H8cMeQx5T2T8*fYuhtpLX{! z3JDsU4bX12ub*uO*H8pppLWjd5)FhGA`6(6RM>EP+Sk#3LM&G?r~<^zdn&ZO+#TrS&%i%Hv-wn+PJ~-1yO%{MWYS8QoPg57Us;K4*fGYE45h@^R`1y)6<{FB-d{ZvFQ=H8jIV*uQo6)`@GmxFvnEWy;*f^++d~op|s2LfpXg#R9*H_j`+^ zD1oI(MP=ct7e2$sSvFM}Ip2m7m=*b+K3FdmLKQ)dvev|Dg57O=K7>jzYyS$ZA@%%s z6P&R46%kgly81lI*&Lshln|@V=6Ihv`KM#Q9^2Q_oT5V-Z!UPw^B4E1LVRw7Lu)O0 zuIGIWb_>H@#vC190-tZ=wmQlBmflp@P#g+o_9+{yLN(rp^nmF~3T;%vq2=NLztn!s zy|R$W@xFplKKy5QxUjFEM`ShS#0%{j^C^Fg>;GIg_55G~hO2CX-vUkcWN3;nI=1X! zz}VjvYe&*p<&vJ7S~dOb$Eh)%tg>I<*I(_Mm)e(cqAjiGo!=PMP7z|KO7W~muXsia zVQYad;&2cNc=_pDu_5d0T}=CrlU>(O~HOs;1@CP=!b_|>$qB4f5YlSu~(9D7fQ zYNm*}?Z_r7phu@gr~?TRSB}hIx*1|9aN=WS-GZArflK7q{BOrR=Uumet0>noAaN#?n7(94B~+kf}8#W+*Z z#5(FtE2OIHNzx+85q_`x$3l!QHL-ct5p^l^_eO4atZ_qk1`>qnmjet9X{rJ>4T_w1 zH*aa)d6Gb{eJ?-?SRpVvw&lIC^yU;r(qyo{^Q9ZyTdG;kp?KVx-&xoz^NN|wdH678 zW;`G!zTIipTuTG?Jh3p~otTn{+^lMbF9(}(Il}BZTmC@5tZS_OFubBG=FR)Eq`9ek zL{wIkYAWC(33_+lHX;?wp-avNjj%Egi40ASDu4Q`$f^i+LgnVsax}us4e&dS8N>2t z;z-&)>adiP`Fv5 zicUOC~nGGWuX7~ zAEr8|7!=od8lBag7k^x&cn&_#xISHrYx}>tyBI^uQrYP$E!LWA&*6n24vbXPW{W8c zISUr!w5_4wm1ZxNo0XH~Lm~ z!7Y7{?lwFxLj6JNfe+(hL;SDubcdIv#qQEy+3wZnXif`5dm@^P%kit~`U&z6&4SGw zi<%1xkI3>QTGe~w(5N8a@tK~HY&pm&T(zFFnu4R20=0vEw4LnF*FK>X8wX8F>!u98{ymayb(<{5c=8;^7)(!m@ISw;~xy=a-jk39h>E#<1^*SV8>fkyWxt!1wh{HnOG9IgD;NWxqn-D9lEwf>bC@czaC1D6@cH(WdI^hLtlL91=DA~8t#WcAhn146TIJp=FL?0gDoy2 zFz~(Y7((+(N=iOAld#xLQ|ASbt^DY06(=QI=G9rAIbOPcXun&X7_T^Y zr(!~isik!0lVqWO)yEQ+#2iNKV zao2@_OgMfTnZ%SiGn8#6I|BP~D|b0AQJLMvehaKa?A2=R&9b}xZA~8YHkDB)_c{Lv zrr^>Sq?mP{W6PSfD3~%QT6!56Qh`v1Hv;rTt(gR+b5`ca%?&zN$B;_ec%q>}d&5MI zcPg^K(}Q>|jbDR*)0-&K`U%(=w!b2dN}nEBQ`1gTKhZp=8iN4DT!7t+OH|aHyWn-t zSC6)1jj9G=BK{5~|1qf-35^>ygR>)47sFbC?LlkD3Pft1C?2Hhjw@s$Wyr^d)ao$2 zmzSNnh7aE&%q>OH!gmn6h9Ogd&1yU40WC@F3zfHM<7;P%_;Ad>_s>qWdg$LCFY1dZ zv0#ai1@x1k+?UA^Z?$-4-( zt*PS7V>&Qv2!yTm%rRRudop?gXEA2aC-EftF+&OU=Y|8g>aOQ;kx z-sAuhraurJkE4?tNXY1dO7`74cFo^aly`*%o1Ybi4sn!k4fXn2Wi)iIkf6(@0tkED z;+BDI=YOTG!e9qSQSL&vM$gcN@1$$W_F)d-0#lCie(2peVW_hK8D20EsfZsTKB-`S ztM{?0v0m*~_Wb{RdWmaVVix!PnNlxMkc$4CCjFr=GgR-NbtHC}#QV1GnsGoNp`r|w z75yH1A9kLM8~n>gP69Y4sGCP?Q|$N}=Tswa*XT#tIs+fx7>!bJ_)qh4JvbhZa73ar z1Mz9D<)^vfLI2}sVFGbuUFuG=h%82J@b#~7k*2?$cKSP|_V)f0Pr(EpNU$*p0@Kn9 z`YHFF&kM<|Q|rlOvZt3?DWwm6u`;{$fSI;MRz}8I81aPj@E0Y+lu;G?MSTyiS4)!L zD{#1z2Q~VZ&gup^IkwVHTJ2kXPwxv!0dybN_{Qvpwx3u>Qvwsj=slNv`zQylS3HK( zf^VL<*k_vU*DmFHkz?Enx6ThC9I<@3`nqM2UHw+$c`N-jN1Wn@wiW}jKTYT!`JXH5t#c>DG z)6L)t$%vmqNgySo4LbT3H3_!ai5s5e7GsO=YGmlqS?7OMH*%;0dZSesL~}|?I0y*U zjij2;JCtQA6!4spo{76dYUCzX(7Sk=fMOa1rF68qPECd|5McJ>2p2N*!5_1NDXlHu zr=v>3oUE-#Jge-FwRF;uw&3`*v1BjU5CJ*!wSs92e!SOHnbgvG=^+2{#xt&yO?y*t zi96z63`EDqD+#2Qbfg0hUP;uNLGJcKcE-_pCH6e%v{^?H|Me0;$g%zWQ8?lEB&0l# z;Vd`n?04hZ@%2v~IMQ>RA&f)z`r_Yq!xrJUy6$yQF889JPwn->QS_|GXAa`YW(UXh z%4ZQpz0#H2*9I5HwND?H?$UVX9In41E(4J$iF)PvqcSp&SRE)Q+%%9Z_=Ys*_Gk3y z4x)wl2HHwcDL#b)-?rEL>qDh2jUdZ5Eq$XJIU|)P&d6rBK&X)f&E`(h5&;2L=a|-E zekT#X=Z{Dcic8D(`|+ZvDcgy8B64S3dP z_{Nz(kUM8HszH;MOj#OO?n^>}yK~nl(p(N!E%|sV<9LrKNXsOE9}V5c-iP0f8``!q&1cxrcG75cy{ zQ9V)wA?4seS55S(k4K;zo7M!>t@NrYoyWM`;&ho2ihYRi;X$u*xL)D2%|j{S=ZZDN zCaGV_--JzrgG?X0^BeCpEplBb&m5~ljS|v>r=p5`H7efAiz;jTvL{y9RNvU0DK;1Gh#y7?b&XF--~8k8Oq^)Uv)1%EIe{_pyBth1V zA;`%=sYR=@ypW$ks6lzxKUX^p&=412FQ0nPDEEm1$o)S3h}+&8YSH*Dr|()h|A5$W z%$+Eo0=!`eOEvL{`Vn&k z>-A=V^k{#oNJ>s7{qRZQs5|FwiudL_=_9z{iTC=8Rz!Ae%JGDwU1_lhRup3^x>n9l zcDS>CcJ!o09$B$yN>*x(qP|>f2nMAmAO9zcP*H_fAjsOJPqbk^esfU579AgLA`=7Y z8otBLVc8FyvBVtaH$*p@0C*?MmM@zD3+Y|`syWR5|c(kz*2*+FANsQN`yFYO7nfs9l zc2HoIQdsKDeqNqAm;Es+W?~O#d0C{pp!JGV_kGfuI0u? z+9i(zpDB&Zl^2bq(xh>+bhe`-MMZR@gA&>PX^H8IFhzrzAhE%B8>_jszWP2Tj=(V~ zv3{{LEpJ~*ERXG(*haHpY10Wuo`JB#P>0q${b$ps&xLK}hM7n3Fs%)*(FZXtk)fA# zo#;Z!A6*vu&|?#oRXh~V^q_Z{VC>AysI@SS28Cy=6_#;U9{0_0Y-mIOhe)Mx77vSA zOc9q8IHJ%r`Wg3Ms6@WfW|O5gCMDL^KrN355o~&)>PJrq7(H_*fEYV2%(&z|J5`ja zKg>>9rh3P|14f74czGqHYXf_d8Vt(EVaqe%%al7 zI}w9J%`%NuipzZCEancFgO{v}EdgxiGbE4A?c^RS{7nASGj#M$TX}H(8RlF3aOyIq z&F`t%W&?62a{SGyXIf*VU}BpYAAE}i-55#QT7XibGVH9!=R`C~H=B1}mj$5bnO%N% z_?Y_SU#*9^W1Z3Pda?Vl7USznne$yV_V;d0{s)v`#TC(m5_9}_ z9pydN)D{<%PRtKlzeCG-M;1ARd;Bmu-=~BYjFi41u34~`pX+86Wh^$C3Z;GVINfAb z{a8(#lhk|yndJx$WXO|&=tFzPQ88E<=mW;Z`7!)of+nmj*gaK^p(?S9AFPwR!#3mF z=Uyy3mCAcZ@!r{ho?;l65k$2dk^bNx9l*R**}pwQh-Uh|pNL>nel1HJ$`YGn=a^78e6|G~mL z>!(!J@WDn8`bUz^MjFtf1v7q8*+DA%qR$ zVhmzMVqY?Xk7jXns1W~9XuOdDqi*&OEBVLXll3AL8z(@IXJJjsEJyGFSm)9$^0sx^ zNz`(sty*gA_E^3JJHf@?tiT2fkgH4Efw#V=&*7roY_M2QPn$L}7TvPr`~AH2MCk^{ zg+GEesGkb>)o9vA#?V()mb>)I=FAhnmwcepSi64B(-{xCI@3#ajS}DmQ+EDLiKOdR z%_C~QY>AvbKL8@8u{7K)iFb-Givc$WL4+_@c7S@3yLg#e(`U>|Bim{e4cJmgyk)4fIL-&oA_m z#(fMHWRaY@n=auvC=jq+4-r+#xOiT5}trLknIoeqiXpqM93=aUH)AwbJ z$chbo@+$tU60R|l73Z8rJdCA*Qy^?`%Z9z`rHI|hj~^^ug#(^IeyZyCQv1X%P#=Y* zs%t1wQ=oZ4fk>3`wFed&JVPH!lt9*IF*Wu9dj8cAP1>*+10aW2#2Pf&rm2G8>p0vA zMb5x}e%AFzSa@-atm}UpcmP1>N9Ey3aQlHbY7rX?ScGANil8o8+aBWqmh10|moY$^ zYEVZuJbqU2R&x+jAX0uD)@*VKbqTCjjF7QxQ!Ppl9(sz;pH0XOihVv?xb=1A6N^WL z7|R8b*fp&l`9TUTX1uuZ#AMw4ErVq)mc3~tc7-J#QU0q3P zxMaPmr027_4!*$|ISv1oyH7f};ic&UL~z->u@M@4H1cW{JVfaJ-HRes$Xf!xl*f5K zr*PcnNciHR$-9;FcasJp=}j}<33vTXtb4OTH^1kJ*|C+-Tt!pMf{I|tVNbXpWw%O)dYkWkj$E8KOt3f5lZXGA-e5|o_ zjhg>m-%xTcfoZfUc64q$pnPkeVMDeB6)7*_UTBCJIqY6&#?tu|!Ubj-VoY>k-c#jzFrQfIWhzWtkkLGAqjl<-cSkxlHO*n1AQgB%0Jhuo7 zCzx3uy1L$e6AjEZb-Y?>u<(#SF#w2?O+Hv0{K2To|Aip~iS+Ueu;?V0AY%u zb(jhgsD%v*@Z5+Xsw(0!O=;`8l%r?A^EGzG3v50;#DV%MJ@K2ge)QQ(UfU|}QJ6J| zF>BJa;m@uRaE@TM@UXRRNKZ7_O$OPM&pryhM1IV^>Z^&J3_wWR4mC#B*&EytzU-$@ z+Dzn=7rTMpfUEDWZA!rROJ{+OlQKT-7vHaL!d(6G%L;H!`Q5{N z(m6$$Ef<(a)Hb-Pi^siBR8~9KNv5K^`Z}{0-R(^6Y3KJzHTqldE7|Wg*#{)M+e414 zsp5j_yU!w;IC4~EV{HXPT!g(SR5rKcl@e=Kk#%gNk{u&y%p8bY0&+OL6FBkExW0c2 zkVJPFu9-TT=kY{q;`|wB^Fp`Jtof-HAM)=j)~%wh{IGROqSxQVoPbOpNbGd zbzhL8@WyD0xFu0T3ORISizci`I*pOWtcP^LK2Kys-SgTgFG^Yx!ffyW2-y?zKxE)P zL0(%C`Cj6`HFhlQBd`|u|8k1-+Hn}$PXhi)u@f7ER`RXvw0JVP`2%j-tR+D#acjS< zY)bKePmG|_PbPLj)Qn+Ne4Gg4g^R8Ri$V%qOu6gh&vY0+ecz>QqX^T{d}gFM1)}rT z)O3?N=ZbTMaNT8e#OObPGF)AAZt*@qRO~AJKfxXhHj-U5D^T>Pkq&aAx+-0>6}Zfi zi-8If2+T3S3>mxm54bmBmQ+OHeLmTo=371{^7 zyRS=;EGVrA(-62U!AtCEQ}!ELA(yaS3kEvy`hg~OL_;II3S=s`dNPuKRICwQdNI37 zAhvYp8w&Bk6+mdgUUwg0hndx7?$>N{gExzF&%+ww{rawH1K~1C?;+gJ?sQ*>8~=KH z2r8I)>5nm<<4^IwqvV@3y*L{ud$oi01G%txw!N=gAD`DU`iddFD5pQS>ClumglDa` zcE^Ub=XF@xPu)xh2G#tzSp>Iz7^Y^%aE}38{*~0!3NvA{FRodUN=N>q(jUhHS2s_2 zS_kR#D$?-D9$EuN5*6&L^OHMtvJU@l_>Z7^MP5*S?U8)nIKuAe`}(kDsz=lR zT)OL0(owW}k;%z_Fb9I);8Lj}VOSu=m{!&Tm2k?|c|vPy$U;!L8P<|x;eg(H=yv?g6nX(7{f6Nw3fOESoX3XNu&xKMON z09)Qj^>ox)d_2yh{B(75H6TA5K#LS_A^@S~Laj!#Y)V?3$^zkP1#~*vA>&(%9!;)P z{*7EJNIZHW&u<^`0*Tdzu6z-_+4wFhgqKP?9VwCBMX|v2nNhrqtas?Hjq9t{gpqKC z{PEd%H0E57VCHe?@4TmE>8tk%udBJ($SCb&4-BPNKgnm?f+?UqBdB-}gBh&5G0T9< zAry;1z{cIaw|_^K$zCT6O);jeOSOo$t%H)L-Y28x+#krgBsO$M|F=E%i8Zp|XyyD? zsnR@_WE2Wv)+*g3_Z^c+dyDL~6(obJ4}0w|KbOS%98Mbgqbl|c{DeOVaE!1T)xG%D z#wdzEx)l-SlcC@Y;pbt+vfW3F^ss1?Ji_y)V{ep zc3VY{P8ZjvEsiY4W7>!Q1;I1Z1^%Yb*C-=qXy?fV^Ud(+Xceq;{)jxQI~r^}xD^BY zZ{>9-7OS-C1QG1OVXP9*ar>QrH<6xDDF3jm&3CL)vW9)72!9&)Jr>$BLqy^}NDA3Z zs&jnx=WTc+E@E}PD2mqid+_3`RqQ&tUYHP8^!K{Vm$MD;bbMiGGR(g`wZzbui%!8Y zj@#k8CDeQRKYIf{@@8XQR77N*3!Z{iOVUHPu?W5`GcvgEVf8IS#H;D8wX5GNDe1^eD5;1^=v|6g6sU z^jL}+bvI2r@9psYQ1mE;;1@9f8g!kz;(`?*2w@e!LAM?&jTdJ3o&Sg85vf8df%p%9 zI*hQ?>C4*GtgTET83I?-R(0>IgPY#_oBoO&z{(zQ`L<>HYxtqKhCl>Ub7&_PTvL%& zjUmHAO}Rmz8A}h@MVhp{JS;&3El_$B{&x&IItm7?xCml2{})C?22KqL)$Q^#OCAF0vdI8 zxiaV3GoIy~AA<*lZ*8<5hXH2zlU1J+zq3s?{OwNcJ8WmIRY$l!SS~VGzs(kgHSFaD zxbB!@e&R0{J-3lOpS@3*Lw$-*h`;g(-3BN))`&LS&{5A}Hm4B?d%Y6_LH@6VDDWj7 zZaNZOM*8H5sHhTmPeSyI?Ai7q_H*f;yEYYTWEaH2^Y=UY8q-O&_n+&GpYk`S0#}BL z+47t@kf}H_(%D)AP0u?C#QNL=>l&>avLL^BbppdxOJCxb`NHn)8w17 zCNY`WvZEeFnh9p36vcUy^Mga1LSw4vmKV817f!A&8t6{(v!6v^ptavN|yBmvIwwCms{dP8q&y+6(`Fg*CdYq$nJb-XbVLs?qhExNFX`k(X=>UWX2K7|_Y4@N{K-9hjMvFpn#pUnPwh!J zLafgZ&Km?(2I9|KaNFx%raYsf(|Z?3Pi65`7#f#H1?jHtDCw?fC~An3tKiyqbQlwz zP+L`BKG1@$TPa+Q;I-02U4lH3%O^!=4P|HZ$}ZC#-cMG@v(S>R`pA$A<`oCp_P=qw z0TlW{hkAbhsDq~dKUPhZ9Uf|Z3Cms1y7w-V4NbhT=^$6omME_LI+mnbD~Xxee3x?d zeluw$e7R$?hp{85X3^#_sL8#L#(*__Gqs40b)80Ml+e=VOG2=_JXD+9Fr)M zzgQHc+)NnxZEsRhP*r4uR$%6Mh3?^{7`2`tHEY2*h<`$oF7HoV;{QopAosiPb_Wr`?hYv2*BS=D6UHp$7w}WUIznH!dxL<>+;D2DP*&Gi{;kd@-75yyFYg%Y zx+^^Ko3L9;Zq!+q2xmi&?SE>mDsbrXr~Mcsi_v)X9$O4%tuC3;L?8C#PEHlR5B4ge z1xbZNglp9OjC#IYEL$YXUN@_2Xtbr%f*di&-*jeq!6&Cjs=K;mE_OB)m^!=oLGFBf z*YEG~4Y+7%wkk!0&83V%qAyf49$+NDwPcYXhmSSugi^-PqsCa(_SrKOu{%-i>8X(xKj8+>;iL~iLP=xU5^(g8T6>yJ$TCkSV2yZlV*Pk?!oz-t~uNj2@*#fh$r&u^HZ zZ_GPt0G7zZ4W_RON(-C~KnR5C7Qo@frVm`s>duaw`sRrf=2#<~Z%MZxfj?z#J=@~3 zm@w-R&g|XI`E?}tx9O!&64RuyvG4AxjNOlJWJ*Y>H1(S(l$B%R7qD1{MHO1!nW<5CFbPa=ytT3$lrGR^* zsatQ9UUO>IzGZf8qUjv3Si0nUJpj^gV4<_057ujEe4PZpA6nQ4v0kJ9Zv-&>-w5DC zDcyFiPqk^_6?I@x;DHyyc;Ldo3PTJPX`bw~;4O#TxqPQGr$?o>%P+qptu1@q`Qj!cy~pG3B>B6bsN5R1U7S>JRO`+n-XIfe8p)xzAPYREu{I5Sg(8%xY8Sidbv`w^NgFGh94)Wm4c$OT6=3C{L8DN^KgKzh@1JtexT z6l|D8Ie#?aE`Q>kSi}z3VSUt5wA1*qMH(XdiiGpknU5*#aCD%tH}U9PJ0B*P&Gw&z zkgz1}APChv{2gmk?7$aF!)jWeLEBu@?)bt8==6Q+_VM)2G&~*RMC3B3G1I(Yzn03Zf z!T%|WxwE!C@+ock9OnE#F-Qt_h%VpM0>1+~{TwmK$6m5$)UiJ~mA&^rA6dN3)L?x0 zKrlR|WIo(>#?}s>acG;d`;??uy%;J1XMzruqY5#{Xs}&y$GKKUT-IvgWNxPx zHHSEEn9i_?Di3pj@#sK(+pn%$m<81Iv6J$_ou4OG8V9#jw|L=@0=f1dSVb$?M`)oWnbMzieRs+GQ zvLHxf4G4O;k`*)9rv-x^_u;#I=W=%=US1JhU1k0`E9C?}-RhIeiW~!Uvxr6d{2Ff> zg5Uk6)a_FdgiHgWL_58AvWnbBf!0)fVF zmcT1ze&3>)+}{fVOS7_b-8R9htFr?6V*SE@JoCG^F#9xaR_$43gy1?6tzEA~D@wI# z692Q?&9Bio{@kL=n<`Bm4V3Ti6jYs);QypAv?y$2p>>5jeVbn9Thsqd#V^5TFc!!M zcTk>YU|)NmU=4Fz68CC|#Gibc+Rv%*0x>;Vy%^!Gsj zKgEp>+s(P&%U6wP`u+t%M^byOchsVdo0kT}-V-RJ6gAgR{A+z{Z(}P-1Lq!mUPs5| zKB@$7MKBelJ^ux6Xu^UFmh(lb(lj?f0?YzvQq_z5$E-NSFg(QUT5bFC@ofIx;mYZ9 zM8`VwY^T?>wTHoxjQ6nA#THfq_&@UMJFL1c>i3YjHMu9R)W)V<6JpcsZq-&_jn|>; zsZ34Uhw>E4A+cinAtM%}FGdqpm^36e2}n*Ij@l*d zsE0mb)j-DP$t`(Fh4D&a63C2a>A!7?#eqY$yB%JX>P-`s6h?|SFqNzjwW_-Px;HUo z#$~G0Ou1jOS^~?u%?Il3?ef+^AKK`>hXg(}a)}YB2&*QqXmQb3u$KrDyW<1H72g{E zJ^hR1qf1{r5JLg2$q+Td0H_nd`ytlROwWP9csW3^O>vztAipix=htG^+L)A}m6T81 zoCm*u2y@^fk?6O1^QSLfpJv=v>sqJpIhO6G)sPq(E7;4_(4;Dr!#;Dsr0TM4ChITGYC=TJ-pBg#N-_=goT= z!kwmm6Yk4u>y_5Jc5jcek9dEH#W9fE$9_FS8gt{;S3kFpYRFBlrvC3}nR3@;<+{t> zyn;))j!10J;?d@~e7QJipiMBI_B3J7*M_0X{hN`Q&QuW+z_laJxm(mEK4Ex zYzSwBzNeTXEfQeLU!s+XTTQV2IW`49=nM80Fj-bS74zh5KkbEiJH2rIvJaMNmpK}X z}8y(1fM8vB^BJ^}PVj;N&f7??5meATNp zozgJw@dw8*d*KtoMjWj;oJNeet+=tc4I)!wJ^aA-+m329Uq1p5{Xp=;`e2xYu9^bv z#^<+7b!=n0s$pzgoE>=k59aCA`#uSZ)+&0Ztx(P?VuVaszw$FTvw=Z+VR{vt7mY01 zdVR@Pm79Ro#(Lq)lTS<#eIMoh(4!lO)ZcI$%EE%fo0B|rsGF4aJW+^nUkYoAe7~TMcMUnX*+7#TtbjI-6BO6AFa)QspbmPg zhBb`5+0^YZvQTr~G^B2W=*Z7fpQf#(k5#~y1smjLzg)PM;YKMRw)b~j&p9|Yw(pd5 ztD(GRQ@blv1fyd?m0Ei7HCR;0M<=%er=A)p?o*4ky!^~2BJ+?iXQSf%IWLq~UbQL* z6wjC9(}BaFWa=8Xv4Wo4)K8^ZUKd&BB+oNK_^G1t37S1Mec7$Nvo#KXqC7v_B0Z++ zvm#;a`=GlY=3;Y9DGS+g)V>rH$5y@aB>o8hgpB;;Cxi3az|U&zxCm!cEnGwCsG8!q z1k(9f$eFv;UiiZv_>jCJN1NACb7U)FsYE>1q_x(M+ZM>C^*Wg-u#6D&(auJtm&!I< z4eQM|dBujCBqjJx)*&;zN6+&$FB9ROh+j z&Hq`g0Lyu>g^2UW^xXQQ*wg6I73=hXONak61GMUyx8(k{-zPaMjQpE$_mhNnf8z;A zx_bmQpx&#MQc3y@!836fUgs_0`*l^XoS)mUVEN+;>-W0uDDERZhCU|up=l8? z{LdE>R=A%fC@?(|a&1f0_V;w_Jwc_K;dacCml3@xy@rdzK>+5k4V!uJPs$HT*P{xz#bMF0twMWg^m(js4k+JeE;AaJvO< zk5lY#f%Ae!E#KHS9v5Mclov_Xk@jI-@|RGe65rCKM*N2AmZC>g%MYN&fTaSU)Zyj# z{Yu)v^q1>DZG1OoYmDh!gVQTQf2cAOk#MD{(6_}gen~CBX)BmcQ-$kuNaf@2NQx6o z(iA;}GQIaHpgFk^@F|?}SoPBr@fgH*hSO`v&{4M_wJ?annL*w7bbfuZ6f0xR%1!Gx z@{=!k7)8-5)dviEI0Him#_20{*=oYvHMjqThy=xBN7h#d+exjsY}Bl04`PZ?4IOga z+QG^5n1{4mFg=K}7eFyT`z2QD#9SgQ;>N32{#-?RlUR_QEu2s zgD#s0FynH~jrT!1 zz7unirGRQB!{ck6ZbFR%ZcBd7w(|CbaMFLiq!qnJX%~zvXfDiS#jvxEc7Fu4S{0RA z1RP)hEI-unk8IyRgBL^q6(1ZN2<_Hgq= z77~Gvg>#{_?w479F|R6}Ch<#b1r6vwPswvWsE_9%Komta!IF4BG>}yj>|b=sSoug# z`&AglhsHV;vOdl8J8Zd^T#0kfQ`iHS`_>Ho2R$=3GlvK5{zz-zSAw9vm^Cl(-TeAb zNSYiwZ!)t5mn(Z+i3=I>J9X$hcku3|)~~efC)){_At_+jE+~4OnLV9`18I-`Gfq(k zSRHsrtYBxSRVvMcEunFgQrVM2(zVKu@XFqcWmce*7(jA^i|TsOsd6p_r=h|_??$}L z;E1%pXRnUiFV3Y7M&b9B!}8B~@UiI*ttKO%gr5ncd|#ys=m7BX2@y}UpaHyfD|XjReTQ^7tj1Hqv3VF^2Mo(11Q~bofFAZJPCR@i`@EJJwr0KMz&x(b#qzM@Tw^L2NtUeE3 z@`KUYW-JJV9QwlZGAkBh{X7Sz6T{S#84XjfF&7KqOEH}%^As3>`*F90o4TZ)2u62b zaGR%X7SQRaRXOB-dIZAs0dt+XH|a|DS4tVk{#ElleQTSJuYaZx<}E zV30+E^Dvj7n}vgw+w7npi;7jWPsR2Q>xQOH%)_u9^vxF+-;VZs0*%>x2a~xe|G%*p z^GEl8R{q1+ZQAoSnCi(%ZBHT5_LXFt>;5w&v91bnLi>s^bXv}-Pu2nG?%kL7B)W1+4@g~Z$Bp7Dg ze`ilFZHCZl!gjT*{h+m?J~A@4tcb>i!J5hQh6m`-`;PDpd}z~9+d@~6?0>NzOk!z~ zVL>-$XHRfq4mM68z)2 zj5$E#z$tl|EjA3LRZe~P`K^1f;_8U;QB8%+Ej@oha5qVkDv}Z8(5#7#n%(ZvdWL}+ z1Z5T|67gX@w^BwSkPxRANTp4ab3Ep_QubDk4A<0V?4wz|%@ob2hA}*7#=qo>e7g__ z73Yrh4X(4aBXmB`o5G*EwK?{qRZ;-%wo&2kkLm&un8jC{y;1g&YN&`& zqd!(IEtgZ?e|Jm5^~Jbq9n=FuJBTl-X|UPCf0ij~O#{sJzy2MsKr7fYmm#!8n5OZX z1m*sygB`UJXdMA7%3oj12tkHAs$10KiNHlgk0SB!IvzlO+!Y1tePaBjgT$gj70$al zO_}%8ZukNFs<9k^fg0h;*UXT!k=CoQk9hXn0My-cm%y*>htEbA#~ZG`@Gsf?TQ7GJ z{{OJB4iF%c+k2$uDe41f=bU{Nnc7m>m&E)Ac`V7hx(XNSwb$xs{E$O1f#(JqU^`sl zWo>7E$j?jc#x*Jb_Nyrwya<;--eLB!Nowj^g*y?POOJ`+`xw^%IQ0nVzq%UFc}=BH zb5VABwg;B+dm+Jt+d$jWs(;^=H_z-NGe!#yxL#2Mi{Fu>yb^pUdO+z6T;Jkw<9=~_ z@$=tbUH7gieg-*#Lk(cQY8c%D6^XI5OA*HhckdXOt|r*)1($hKms$t22_`&?&box> zSXKQ%P9*PLUe>aGlANQn@^Kvs6Z9LpsYZ&4&bQn>4W9V{Raot9U2Vbk(Puz`R80U> zQ|8LRZ$T>Pr&52PBgeNbon?>uMK5r%Vvd21ueBMWv4TW+F~&TqSIc|1Y|5PB`+&4J zPnQSM+29e@-&Vzt*2*}Y{5bbf71rR6V_Sj!ZY{i*ED#lGciyDlmigXUB;S`y4v})i z=7=CtEz0huRqxvW9$Buyt~S%*wuH{ZQpea{_UeDVEc+O?m%SYdBLVb&s{}z*TTCkg zEZ*$&QY?^o$P_Ol2kk97lJ&n2U|!1Cyo(?1*6_Q@lfTK2T9-lr@u z%{$$URQb%)R<}$aHlw>@isX4Ab;9QDs%E9%GoJp@dK#dP*Rr}A(cX0g1H(4ax}=%Y zKAadYD$=1ynG+jdwcU%8qQ;_ev4w>pV_kPQ(>L+p+_trKFx;8JQBC0;ldHT2s_m>yHLW=!b4?fo{ z9UNv{NmD)?kGhf!kz}`&(R|(1=Hyeh@pbp>vJWH1`M0GOcCW4bMg`|-98&awii)m; z+o8nvvd57hf777-)k6RjD}-7 z(0j#?;O*h;+9AIAX#t_90z_Z(1?GK!;I)~X=dM@QZOA{}c5fnSJ0mks4`xUAPRRBl zpQ*d~UX1dUNapKMbK2)=`OTo31$4rqM18rrZaX}{K@d?)?{9rBx$D7$=iq6hT-=h@ z2sK1aU0J_p{XjYtVH$8J5-YJkCj|EJ(DXaWr}g={ubIX<_7+Hh=nUZYzf}-R;hL27 zUst^R(&<0Ww5+4slza9T=;nWMu&~`YcSMd26LZvMCxav9ZPn87Ae_PvYTN@pEb)5V ztFuc$+%@o^Wy+NVkR>(RmeKaUD8V_@sg2HDBs!3&tO)zzw1ljKl!@hR&Jw=`Z%!t8&zhqBDUcCtW(Xm8BFi&EA{@jlG}q&p-kKOdaB7()+CVW z^1L;a5(@wH7<{>Db)t?&E#DjlABeel;W?f5e(Gf7R8AKr6pD#vdL%nZbj-n5bYz1rGeZIl@C8f)6ASzDPAibKEqY(#Dt{MdA%LLz}AqOlOSs<_jpP zZg}`XT#5``oVb3KQ=>{z+AUtm4?B z_(mp72~c8AkiKE^UuX2dbjQLa^ON_2f)(ZXNxiyEN_`YFhff_HE?WUS)lVWE5*8%J z^E+fI7T37TGm_Z9qV%lrFlY(mv)xqmTdDQ~6Thnw=be@XD)-F%GIIX8&Vpa+i{=SD zuKjrWVb5xY(2vkpZXP3x!#cmsD1NOvHKW0!QX`G*rVP%rP+tGbo;LFvvb_$}p@FNE zluyg$fB_*xkwf%`Ue5-ly|?gJVgvIG7+LHOM)p*SF)wFs+Q#G|RbSJ2QB9KH??`X9 zpMrkVLkPM}N|okJ`7S+%YA5(=hO7B|=?RzZIxT3Vt^{>(5f5?)KoG~5=U4qkn#_wM zXS(%q88}it8ua^o$|}Z94tNw$tXsYfwa&UYdnVdyc+A@2ODuDtA2qyspd@Wv!a}T4 z5^362JkMo`?k$zQfdogR*U}W$%-gI{hD~h>)R{O<4A3bGHyErp45YgAW<&MzH2XySnU$Oi< zEN$~ssoMhqsbT5&jcPSeqVF#VvrRs2wMnQrrx)WD)4g66?h&lTwO4tluGJAnGF7-U z8rH}Z73z#{iVHb7*LfE;_L-;9Wo@@b1k+06?q5k_1GwM+qc@|$FVp@?x^hYMWBXi% zQ!f=89w{^yj_UzEF#TL12hnKyJuUff{X=V=SusuVYb)E@pJddmz+TH9&n9gt!4<5l z7&>tLY&-qA7F{o64e2+oWz=6p&Oa}w0TWsR?;z?@NxHLxF~TgoW^34oXf6t4q1(UW zkDK(VTi3h9pzea!lP|FH_3U6RhN00e@DD;8IxRos+-|w})??`)NLi_X>F3j2%-; zl(cfFTN2tS<3zKEXZ8_l4NQS4@uNAzyM=L#xTL>uMoC};d((rIGR|J$C-CJPkU>rB z9}y}u)4x&^8yBjV`!X9!Rr$s{-Mt$J;hEqWg+KWWLC|dzVRe6e_V0c)7xHq6H!o1h zd%Ul*?d7~tu^iTczPyqXYIcXb)+xH`cWKAXyFp8MK-F}Yyr=c?21A#f&`K!smHxx$ zpQ&X}-xrM7S?X)Zy#m2cms8h7JcB47@90T+*%K~->X2wa7_&aJ9p*>L@msyg*0_J` zoIa*ZT`fIp(>hjN!@swTi{6--g=@s|gdanFGKD%BDxazB2qarRSl zV{Gjpe|gE_f3W)8B+s?M3+!MnyJT z;89Om>cns7idd7L>J1TpNVMR_rq6C`0g2e*0&~Yc z5hn1EGWa{ZzST1AAIvZ>eu%Zq$s&VV0L3fQwuQlltan=Uz>eH=B)udrIQshgE&Tm| zcY2@iONjfO@7E=YAMDiZgJ~SiEtd%W&-t#y(g+ZL3Do~)xerOlix6Vg&^y)FkGXvK zW#7s@YKkL@lo*W-f(N9fUvKM#13@)Am^zXYxtShL7v;KinLgx0bHdBD^BO-~YacsF zOZOaWroTCIUQikFjsjXvufk8y16(m9R4UcW-_W?hc+Mos2|cynM|);Eu5l$??}rqB zW$WZTa)Z%JKJBbnf++dm$b6h549v8DhUP|9`~zz}u`yF!`pci;2z|sP#4KAy+lJ8j zP1Cq5Vy>M20KwH(5$G+@*Xb||z9AgrmRt(BaH{cIh!?005UOn5cc0In!xP$Vgq*P z*-dm>w{-~3QPNqYsY6sDXTDeBqrzb)i%Z_E!W(S}LnL^HrYDePr+iJ=Eu?p@;Y*4| z9hn*V%?H-rMGK+XWAbOysX5hXvC1{lYM1KzM<=`k95d*cIXWnj_qqn@gR=Y&8^7zr zykf+}x98$RV8`Rf;o@QU-Jctuo|sYjb_T`O__6zgjYhV${aS5^?;r2id9QIfYl=?$ zk(n@aSE4+E%)2`v#}-2d62=8uKsTp`@5a=aHMV0AE9>w_YxLCaZP-P8cVolr=WBt< z>k8HUU7(9&^;VIj9<^lrXc86V=QWb%>aUODxav%Zm?jvhYb~BW#)rg+oBx(GHwaFT zSuRB53@gzXrM>wH93XeGYooD{wut1}_PcfelN)I~$Qs2F7N6wrJHWAk=yGk~^X4VN5>(!`XaeEfHlvT*jF*JHj-HS{ zaKpTWc_HL|>}>)2%K=i3xy_V~E+s^dIYwTY;dxGmR~x%!uk*VBVJ#0s?WKxIMql-R z9#cq{qkRH+l*LBDeQ0MZ#_sc`kRyH#Px1$v*)UU4op{236ymJOuEF0&_p{v#&!ONU zNRh#0u8iv|3BB3Dp=wuFf>4slq+eFG#mhO|_DDn1xEH54kA9S-9=FUKeW*PkPshPW z^lncuEoVW{c1*yi=w#f1jYbgSfe#%cV^nSJR85U*{d4-|_jQ5=7e9RByQf9oFH|oH zy!on{pf3qh@e>3>X2{c@2V(kHC7+^Pa1yC3lAH!co%2k7vZFXn8BMw}hmKW?)8!3~ zvah81Ha)%Iri+JMBbe4tAWb96MLSOao;6VsV@A#ITtT+i> zZSlPUlA0axk{J)QCueJhBDhGzFl7deLVqV?fChxiGsII8MTLq;N)7v)0@oiCq-Umy zKD{#iC$#N;^?0nsbYPQ(q!f$z|0{$o+o z<+?aXldwx7`eQz~p^2o;qi=FALh8}4I6PzPx;mUP0b&i`2=9D~#^Ro5Vx0O4X&J+K zM^%*jm8y7tErBT`DJq>0565pzH{n;Yw!fsG>?a_%SzgRm?dAzH>b)TrN8L5}AmIof z^*4TnOSdOJW)ZSCZ8pDGd3Dbotr&7g(LSPGrFkAbmguz5*d;P!a*$8Yn(^;o^ZVPK zb{{{`VQ$<~Eda6c_j85Pl}QK$_o?hv*rJEedukJ%M6(WPTy1RZER*;PTb<%`;yKLM zPLCXSK>F7&P*9j~)1w|fGBUarywoU@+gxUg81b>|@!gQRUv3tR1jO%S*>|5|ou>^? z_S&FlNw@i2w!FQ@iCzN+R{#r!rqETGeRLA}Nr9be<*{xDZa`gu-!+9Ca6 z%o|7hJZo!Sq5PqyTcKJ!H=SoB=tJk934G2A!>W=0Bm_43NFJD(M;S=9uWdZ|iJzZl zDa`u8H5nBBRHc9+gqNm1;%i5J4xdi!WAM(Pq>tz!kcTR^pKWf*(npLHs(lo-rHtyb zSwM)V)f(uI9RB=?YZ7nwe^6r~zcxtz2 zb86TCATJvP6sfGrSG6i>sQn(*KN!H=Cw(mfXq+8SJyd~Srv3_xVQb!WjXTZiTpIp^ zm$yEJOc)zdbmn`w$NnQ}giP=rwRfH7pbgBGk-;05!TGkDR{|OtpeqFIFL|ef+c(^i zX8-oS3+p<42lgk*kU!VVA*N%DME~ea?x`pui)r$DQ)I0=52T^kV%+uI{AKF3e65}> z+P&o@Hg2G--xYP#@`VYCZ*mbK=QUw_j`t{@_jP_n@#K^l`)i?0B6Z^Najahl=Y$IB ztR$S*Tci>e{9U{ar(3cve|663|KaJa!=ikj_iq|RQo2iNmhJ`tr9-+wy1QdZ1qnsE zQMz+!mPWd3>28*8c$W9)`+NT8IN)Y??rY9D=XK3ww{W0AWy9?%(nDOmL|fIA zkdl${E!fN69)r(uY43$_E0xBf&D1_OR~_zUG-zj&zq@kD$Dz3YYAo&Tb!8Z1Om+-w_yq)Y|y?lkNgG-t+GR_-Gf^Jas&}@O}zp7*cMBc)8)WvaFe) ziuQZpcXxAMI=MbAE0g-v%dZ6Hkd2dSbOR6m@YFlbr6(Ov+ z7{@0ECm8rv@!Sg92(rZOP=R{Fqnkg>^E3U4v$lV;H*Z03;twO+$2T*or^j^Az~OA zpc{c-#qLs5L-R9Y{OAeSvm8XM5)onJqmrHzbr7*+VLQx@E@iB-?B4_%0Mp>AA`RO` z$6Vg)b5w|y06Q+USDjlD{gl$X-7jLJT+?+ zPT5W)gazTcWA3>0UfkdGJb!ALqHrOo*+9b25K=w)@ymYdv!oi`O|BaleP=9UiYSf* zw16sNbic8W+hw%P+!_7ga5hcLhKuN&&X!xvRtnZRZ`qrU7{w&@mJtqfLp=b<^Is^lU|S55w@)9papO*z-e|XLhhZvh@*)p(>kX39 z=-!3nDr4`*{PNT-Z#s~6)WZ8`95^lEK{bBlY|H|qu6O3nIXi|%w)qibKZjTxhH(yj zNX?D1;MhGY|FBjHAI)!L_1Yg{UGSZQt4JOTl&b<;Y@DZqGj9Ab#*u>WB@5p#?Tq^A z@5*2!twoEWM6GFo4MQtVSq}fe|4xMBA{csXZ(`&=>C}lWq$4%7YZ6)5Xl7<{~r!Il8%2KH!H% zJFE32!IB|xBy0~u<+ppMRru3>`t@YTmgecs=R>^8PNHX_BTc%B&v<+3~Fv2O|nYZ{WCf_DR+zPcR!Rf(yKKyDz)9 zE=!g;`YHbIR(yQZO2#x=F}CSC3|wElIqqFImd#}B;k<4$o?ugM<_Vj%MEZfh`t4=J zn1N?{#4T864&PftprRYXi*0LRQ0!JJ$?w6dVWPB&F13#kDt0)S0PwcLgV!#&^a!`! zn2xAGA8oP#W1e^qto=$LYv$P#2!kCVN0{6>|J-{_^S-NoA&ir5REW@C{5#=WkrHeEj@bD2po>}H+P0-LY1>%WBFM9z)TQ)OD7+I3 zvUcC$t3q5LU!n2G{tHeQYPJw;rF9?(67W=E?K=7F?q_k$eh`a8`Vv{f6w+!Rn|u<& zFy>|u@+oy0Hb^R`g}-PYDBZ3dH7mt#f*mZfT^-Fmz|&h3usdC{i>vs-sdP!-Rx+aA zJw<7InBCIPM|k2rxjOl-sR4XtdmB6S)Q=#tNG@1+o7(NSlyK*|f{X#u-0+jbwI09I z!fZu#8o>{*oK4~d{p;K#rG6qLI71Qew$7VpYki>#iI0o`hrkE|^S7oXE>UgirBTC= z#Ph)XVSpt2NN_>M*X&NEO}xE5*R5cN)N!yvzZB#ThNHiM`#1j#^{SnMEi}>Ifh&UK zd8Zzt`3cTncE<)Q`6wbH#zy|RgJWe{a~`n4bq{WBGv3Enw)hfjPt)uwVD>f!zqy=j zy8F4)%ux2{p%B5=C;WwmOigNJqs+VB{Cs`s)J0J!7}0reJXf8*XuQAP&lUG1(;&|p zZlg4qS_Ne->54<{Cvt{n3|IEimTMi&M;vQxb?C=3#irHqW633h2%FP1dA0P*JB#E6 zC2N|WZ*0Zsp=4Zb2+fb|a-=?x+=chm37=RsE}* zgZI?5X_u$SUIoY@k-Es&3A#d-g*HJ$bNtV}UvJsBcvmMppjo^Gbsb*rCLNK|fmWf^p<7#|s!|HHy7(l9o@ zXa5>3$X&*;tcv+uZ^uGn3^cn(iHEBm!@M9unweDM0_AS%7D*)_)H)lU69K`=fnG9m z01JPD`FqIWc^$swgsblz&jyZU?cjRfP4#quW8n>k zG_mpcoi0|$Hhw#`jR=3+y_E^#?lyR0Ih-9+YMQrHH#;VyhDUPx+WJ4TXP*%erPHCQ zJ#dQ0RE<7FgzzpZIp-3M!aeT)*$x53=8je=WFL~bE^{-<_pzQX)LISQ3xJ-czr42T z>IeeQW@qwap{rT`6z#W_{jUXy2Sm4=pr~fUXDIE%?OrTyN-GviPt#2W-A7k<4Tgj@ zyiEN-%$JqYH!OLrrS8loA#UTWxTq0IS*7!*kT_rq7n)+r@japChGHZZ7GU*|A#)tr zMr4M$fj?nkX-VKqS?5i>rek%T$R>gSYM1)ooog9o4#6#&8*(|)+aZa(P~!r)!05cU zzL2iTA0r`c|2zN-%9u6qzA(KXj#Xt_x#RgaW7uC}HeBmhr)DgNaLrzlnPR7x0#34) z(algeMc6rUVPm?7fSf(2y#n{uvT0>GzY@h@ zfZiDYt5!YE0yX{UU1s*5*tc8f@)!tl7SG$b-cYo9GL{EmRrIn2^lg)Rx@nSjyU`5RHa?Ly|iW-_eLSupoMW$TT!*2OS zS^!MCJv`E@7>N{`AdK&F$u%%@H!%Ee1P;0+7R-*<&|B5{Qeyj?IsguxO=hSAlUOn6 zmqkxib)Tb+z0}YMLcf{vKxSDG^m%{EOkB9GB&BbUAREqX(D;h(-tqJ+Y@)N(80`FJ z2nJi(@NwG|6124Z{;cVv=i;;+r4XD|!b*p1{)8aH)rLiMH~0+Cwm0#cPeWluG@^pfPg5P!<29_cO|J#^(q@%(J@GO4H7AgAd@t_=+cXrf2~DQgB|VfLWbW4f-r8i-;R zFt>eaTSNr0Vk1t;=DWCDjptVa&k@MoB8vvjXt15=`DEExU%Y^lo*DFc;axU zmw-U+2A6${YD6frt2f&O6lhUYk1pMv6(WOKWhgiL zy}M&je_zPYX-#;fm9~zmH-V-HQRC5v| z)4j>?`U(YI)p4^k*(y0yNw1!ymSjwaMQ`ML%=bx|DJ!PxbER zD0NEG z^_&;!_S&(=mQHwUgyV?;5!*;9%Z=aM+8BlpxHuv;lBHa?7KN&d>k}^*s0)886pI){ zC}Z@v8F7SB|<;Y&zUN+9bjQ#P4x6BATr`VLSf`byug}ziA zjr(2^`Vrnr1kRsn2t&)_07XZ|-1gE6Y_9H_u`|-;ixtCFeTQmx?3n>F?XlcVb#ZHy z{cNLBE;`>>!<12T^+N&yfG-JI%?MD_5Hu96toFG~vRRD`CtScF4=Sg0a&!it#g;Gs zT5it_+x>LO)NZFqP|f0=b{+zjx40{vzx(cXKILlCODsCfCko;OsO|o-r$@*LYC>K8qo;R9R}VX-Ra{)~BgPa$i&*ozm2^S~ui)GB zUK)2*oR~Hl%6b>OnR<8=c*A8X_Reao=*jzuiF_&u`2e;7+2uI%Yn0f+qGWL>kOR9+ zySCZpS@0b6r@HAel0O%S&{sUnz_jx#+u~Go_n;oViSio8h zExYZYPPtV>NA^SZpdefx5)Sk>4!2_Wr!;_0>msWeWz614)wxTa8q;&-wu8{ zj8DI6H+N=fdP)|k1rQYrNj73S+rdX7VpP5J8ecHf<}G~N;qOA~5rmdQCfwStswh>B zR>k8ycy?c9UxP`njNP&llh`HZWbU?;w5AcYH!zZDVPFKrnj3r1P*sv)*buy(I-vHDdQPCG~3I?us9^_UDQ?5G$No+=nf_2@)!iD*&?1w7+bi}c)E z+zLznwnZCt^wo8P1LJX=X7$H+Il*y$Cyh5{9%6D$qUG{m-gTU*q5RQwl@ukucf{f# z6fqFbl9UoE(GZ3Iw;K7oa-#u93+P#JQA~*%_q^4#sVeBcJ-lN`Wu2FlKD3VNSGvBy z;NiVNOSktO(xqch!v~EVf0(=}JY7bk@?q%TmM87f4s1lK z#m?l41if!_;Fm6dBS;@>2HQ* zeVm%fG%#anZ4E_kK1PBdXn)&%NwT}vj$yq~lc4;K0gDy?C#KC`oCUF7vxHVEi(X*r zTOzs)y^4TmVc3A>QvJ(z`up zrtJxVa4^A&S!{5wbfjzg_>q z(Ey52sJMo9C|X|rR482a$!+|EAc!m{F2< zUR51n+Um*{vqLru>ki4X|KzEL!j)Q8d-G;4Cu;k9&WPgzGvFA{3=M|2oz7TpW`&zR z57R&^MtchYtS(ij7NwbbAU^~JCijcGCB|Q1wuRr^ipPELWxI{M-*uwl;>?UlakUP< zi52=>#2N7X^SQf1EOo^79;M|tFmJ)NyWsU5AgTYt>%Pd{=NB;Oe26f1AJ^T(<89O zX{v2nMT;<3ohG{xwM)q3mHle;rCvFjs|n_yA8QlZ2B@V0+!ZXvFJOR)m(3!cI=uko zGXo02Kj>U0)jK(Q_v)-jgF!wj;i+JDf6xtXgy%}OkFjs*E$z~%4s7*X4#q2u=5ur9 zybAG4$aeA?#(uU&!U{NM*nu{%q_A0`<1)y1&WdZ0l!>z?C|r0c-+fzs!%lC&uBveJ~vNpqmv z@9@&kv+OiMZeRFxLJN&m;Pb~I2iL|zL`T95)*#(;&?Ecl*mjC&ZB6>oUa}mqMev=>|ouL zAxg7jJ!T!#Igfb)NG}ouiyCy%nML2Vd!jFZh4!zE!d46V}MuIt;ZP zg0nH3Z`E)!Mrj__zm<~95n4mwzxp`UXk6I|nWq72==wPq37mAKOXJ3U;TQ}NzS3K< zuUls}DXG6hDzzcw_SHJMLzUw*lgPBYdS8n2OQ5liZZ(F+HB5s+K(%=Aip&3DPHGHN zgsh8S8^5rv9-7`FF<>f>`sT^+vX;_I0BiaChMio}+PXy@`C;#aiWB1Ly`s`_NWjDO zmG4x8sD_CNzBBk?-NX4bwpYFXalE#4ZG3JlpkHu%T#KDM2 zb~v$yM{uA)qS5-VAJiv~-T}^CEi!jNXYTEMi_yyBMH!WssreS>12fTg+j?*S_^QUL zGrm6qQuy58tc-^Qz~pgg{nBg441zy58Znk!Khgz=&3D+!Z#U{-(_xHV6>aOVi&b6j zO3kER}sE5g%L zYFuFg{=hYdn)^H7_%*`Eh{eh+L6rogkPY#^DKdp16U<4Q^cozW`05Fq^9*C&27?>^ zBL+Aj=_1!4b8TLr6`OU|Q<}?9I+e!EDO@=(WY{z$fjlkDoU<|0>awA9S^TfTXFRPL zEb$wBlp4;q`R1ux*j@JaV$8ULZA;sdMC%{=->JN1EvTnNjo}e~{>z(o!UGRyTt;$4 z`9DVY&luX!S;G}yb$Em-tTs~dw`Mz939O#}<99o-4b69IHTz#xtrdpgQPw59*K5WX z8CJoY63d6iNYB>x3-W-FSLd$7clnG7uqlX1hAXM#)hHiPzgY-vqDlfcBUtfrhG#F7 z8KPZRjV7BTH1tZ4PdLX_Y}3wZCzj_&<2SaJukqoT4!{9I>&70?oeMI_c*aZSxEw2@ ztZ>@samW@UdL8SDXG<7IDo>?bJJ${KzVkmnPRN#L82&q%-H%m?1L#41FpVd^!UZ*{ z-H@3N7A!N8K%r&H8OyDr-fgb=VU)?YW6;7n^`YjPCNa zRb>yieE{1A>lorm?Qh|qIYiJHDDY@=9H`tq8fK9#zCIXK?RImYAXj`%FtTX1pU$4I z=b;lw-aSNVAv7%NF06{lbwsCzKA)gggfAyJB?;9zoI*L{E+?5XEXq~pbc7(JE;*OM- ze|87AH2(NaS)utg*c^khJ;CCeAOUNs`Hj!o0OyWCs0wvaG0?)0$|e4kt&_K)7N2|x z4SeTg@I>8pQtD$&c<=72XW!s>dS&~isx1xS4hBgCyy&lIeZR<;znlqYS!Zv5;{G22 z+F(Xd1fh}&TM@-*``w>N%bAGfT;70C_D-uOz!917>AS`FNH9D5+C8*n$5=*pl|wi! zRs*dpBKA$wM8q^Qp>xuiUo_v9tw)(;>?gz;N+0#IS>~~L3Ci`1z%YAebDK+*QTD*M zVzg_;9LE-=H-z2JgkMAHmH=Cmj)>n{8f;-m?P*1$S}%!p-y4#2^=`H@cwzkd&x(jd zsXk@!{l?{Ea`MY7n^$UmvS#SF)($(iB_fR_JlJ?*nykOAe-}RM@)Sci_ z_K_vPH~)qm+}VGK*y6#ePEmrbES!?71_!+HTJhpts=bO)kJ~TpuLya$$ejD1`@0aO z4VUX>X^FD4vtyf!EOp8wUAd+IbY!hAfbWWx%P|}$)`Q;Dn~+Q%Mo*%jdZ%=A^}*6% z{Sp0al6vN^vkzQ`p)4Y247;b>pRbO6w+@)?nPja>68s|X<3PNng5iNaU`G<*7ZG9?*u z$-fdDe=P8zGgT}Yi-eDS$R@~jD<3S;ly&)OXXtTJRnBOpCraOb04z@GE9L44w^wry zHkR&WqhYZ-aKfAQx898}V{M^}`}u2DIOCIC{|x6q2?|qFPRX7zcb!1nM&^e*Kx*+g z=*bfw8qE?#y|}n&YZh@3E|3^|Z40S*#dI(0k6ziQAKUNQjm31n1Z&ixorDJL&7AI^gT|WaHlxtUocj$Q)Cv$JllP| z^n5R@qpKZ+VxRwx0QNZ_3-_;er#}QSXj0A2#l>|Si}{X{ue=+pKQ6I^o$Fa*6b3(; zpG~GQefdpW*ZG*)(90O_7UAmQ zwDh=pOok{t2p&=7w1W*YPNY?Sq|`#Tkm#VXF)`guZb-PqHjH#>Gy9s3L+J=;tsNe_ zwzuO2uD}~IhQ9Sm%7EqIIY8&Sris5lISj|E*JMJZKmD=&+` zeEe;IxV3{xH;<5I=9aR$2i=GvnlEcaKA|gm{uztZzh95N6;KYh^@ z=uoQju~^&mFmqc;5f_;vud9-E>0MFDZcE6L^Yy0gDvw&B3v!eChK_8ojZan{hH)2h zw~F6!0ce)r={vJ2XbM-qCs(7_{;uZr?Thm=K&EQludngkzrGkr(C&WmJHq^5UeU69 z4Qc!WWzAKAES06jU?tcmKCs8wPd4dk;x9CFWaY!Z``vTXVyZDLM^)ig^Ea6ssTryc zppF)86s`uNat-}>fX1j#GfQ|#&+-@gqNL>l`L0&PFIoJ&tBX7gF;Mymi)XO;u8?PV zJ;wbZZotJvg7P@~%Cdt=+%oR%WWrVOaaVg~4JXkJ^rl=NI+v?%1s$*2!@(XpvgB`eWl>61a>#x&#^>CaW!)Ri< z3h)RRp6s`E&a*}l9N}gZ`}k=OhN+hv&ES-=0XqHajr0!xpsUS-E!^CwhgZ3Y+-dmU zhu6AVe((;#`An)rYldz2%=2|Ncm{!Y+tB+%C-8SXhP92}==}Y75S^PhOX8}hQ9Z)c zRCd_Hg0v=f_wkFDlWqu`oeg78ykfw?u42|)Tn4$>Kd8FR7yKH{oWHP3Nh9m9-XhRh z>~Nf6M=}U;=wU*OnFAUU*)Y#e(+S07BlURdF!RbF?QrEF2 zl!iT-3Q>JOj28a+Byz(HHWm@otC&ajE1e=5)k8B2GO zGNUY>At$|Po*K}RkEA?0Hn&PDz=-t>gj%y3ZJ?KWT>&nu635qfn8-4A!AmCu zNvKu6%Nsh2&_q78py=7vYGs3GQipn_ANm_vW0ncNR+Vw1k zt4ZDDPg$aVwi+UfAjEpibOZuMRn#>ZubVzHr!cE*(G7g?Sd~}XVJ38#TRfz08NDU*g3O1}B&TXNw<9te~y(8urMd}r}* z_ukl}hbAMRSh<$-n|ItJd;*@5^N0uSC1A*pDSgg?%CQ!MMU2%cl%Mh%mvpSR zX1kY^^k)lYJ*|S!r-A8CV_#Q%kw&+-(hyeZH!i(0)zNbCZ+Rc^S1-FBcCkZPN@_4ZcW!iy;M*l zZw^W;ft&qt51dQ>7gz#5=YIuT^RoybsZ8Tiz?wo@qc&gOCFgF;RFXZhMh{%E#>DER z%oTj=e6ixr0B@#RQo496lWp3;^V~tExBtRbAt*?Ax0d0KSWPlHD~AdV#6ZZw>^OpB zdZ)9%J24WB4oXkFLhYNW815;Tv~<{YUkh@oF}br5b2{jVxr<|RM$)BG|A z1}k)oUkDy>M($ot%}k@v_J`%u5UVU76o6q3#woR-3g0Y2j)ZQ|-+wcr`rhfgcp!t2 z5j*Qeo$vcXch&LVFIq@fd#+-l>tRN~U16-6{5N#d&q`l^W`b!^ExF78ZHN*89fe&Y zw3MAEwsmyk3$z%IX5(AqCATiGinJ8L37#J_@w{o#@U0bK4#JD`5CL=rv$CPyzmo9&WS@5FH;5HhmF2uFj zLZ^XE*XU#3nmx$;g1)ps-zjlwWZ4bamcCj}Gjb8Z7L}ew+N8|1opvZoO6hnd-`**E zv~$4z25BT$I#i$|(CiJIgw>5ISrv<1PVHu!uQ=(^wftvDEYo-@OwCcPdI&BxwHrj< zOeD{O#mU?E$XA-hFuV_aE$t2J)pC+&wwJqlckIYG0uE)eqw`nF7MHgndmFg2j!p)D zle$b%d@^T-h_T< zO{kzVwtSBQ?1-kK%ER!@{ zwc9-(Qhlij+#bdVmjF_Igq1FaaTAiKegI!DZAb4Zl!XV490iwfd{61$lBksDj^LJ= zE9*85@_It+^Zj3l9mv24BSc4*pKohh{#|w|c8~BgtfE_+cG&x0R(FwlUiDoq&_eU~ zLX5M5oQLjahkmqR28SAgD^^}y-!3LyP}+Em9(ibh@vY0@o-^-eiig%>4~=cwoUxO= zj~MLMsdfP{14&xRL8Cw-AKm=|HK04<%K2pJQ1stt+RKxGF_~nx@pwjE0~fmwW3by} z&D)tuKOz>NIq0-W?WhHY^|e*~keWyr6niPA$7C$c$$mp%J7m+~%O$GoaPSDas!^Pl zxY>iEaj2Du=Yw?LMD4iin+)WO{rl_h?CFy&uR&^sXqmOQv@qhwy`EePyAk7jf2lyx zHrMRqi|-77xG!I^Sx5_ zMq)zWSL+_K_O@^B^$UWT;x%6j=8~#Up>5`hKmfk?8W44DJXbnA=JEl#h3kvE56G;A zt9>;_xya-<+{rtkipT6ovj$q^dmi6>L&|f5<+`!TBL!8K1XNi@bG_)~vwW{q9AF>c zsj8Pt?2=*7aTq-WZS%Vv#XEF0Et-RzoDTf35qTdfmfi0HC|g027vcEc9=Yv_@!-fZqahZSBH~}Gq4N_>XNU(ZC9AZyVm;qI zL=)Ony`8`(mPL+Yp3s++s;p~#fn5c682R-lM{O>dzs|fj`|1>}0K%Mlf6kP9iLTKrmlI!Jwtd)g_4qBxz8P6$aucm`iRx)#EMvTa z;R$I*cc9V8`SJ>FZ^JGvOe;)y^4X*BMP0&*3y+eqLPzOBFQ$VD77844h-<|xd}X>S zEx5?bhvMnryMs>_Qb*Yrxpgkv5dBf)sbfFc1&ILkqG+djJ8a&=LeCP}=kw*&Ju5WA zheVlk3i_qs%}RJKNwlW=zu!NF#+LW_`Wu?eijE0dyLgCRp4+YaPk6?)b0HY}Z=A1u zo9@$cm(*t$POhuA)9-;r>*A~8NnYheKh?c)O}D$WtUk?b!g}6DRoH=E%PPZ?w5kd+ zH10)#ef>-MuCIE^?7mvTc>3JA-RHGr_>`qpxJn-=i;Tu1Q&KfkP%=DA}z{nb5 zkP%Ud=|dBT(Or}MJb`HMv0+D`v}uNw;O^ioFEZ>_;4ArAmMz#i)Y4#oOi&RF2kz+! zLtN>O#7?dlNa9t1+5pVlikMOtGMG7B{Yl)(;Ly5;+b`Llvjy5evl|;xdwwT?k8G4` z9vQcziJ14tkKt+^`og`eHG7O{K5&w(AHpPb1E|~ZqZ0hYYI`;k(3!gB;&Fp~@3^KP z_p^me8jzVy?AsA6V_O*u(8wIs?q~Pf+%$KL8ZT*I^ANMu@5a{=d^L?Hh2_>~>&2O_ za?nIdOC|8TA;Xog<*)wDj<{WT^tv@ z@j93Gbf2K1VHLgVk%3-zHKs z1Xm^~9>NK%E=H^&Tp&7*F0IC~6XqcOmSdqw>cWppOO|r5NsmtaB;FIa;-=Qvd z>XAV@F7g|0foWUmaBwg%>Z9bB-BFJ~GkZ+f5xf`upJAWY4M|oQVR#X`Z)_Dt(p1%`}R7u4&n0Op0FkD)sjJr>z}q zoD^vn?Yd9OCLnFx2(7uS(UE401(z$V`bCKhM=bVF?OIE^FYI2zo=>E|-Ju-mp@p69=^D;I7At-Gmjs6@&9t{w}UYoEU%oHv420>QcVFYsTONjMh5v6n3Aj{ z`mQgyR4O9l_!j;T63c&Ebypq@O%*0w$b8Q+Xl@t~EfQ`DFK%lm`}F|wle6gFI?T+p zKbAZaib~l_8$zxYm6UuILl)MYUXMeue1JY!yCixKr5j9kraVJ z_(e5Q6b~0>H2Q%_$tGCv@-=xrFHy`wzt+sr{oiK$KBD8GS_x?KU_092^oHB0a}YHU zEREAL2*6n!!8YFQYd@YpN;?Ps!Ig{Lgin4m+YLZ0x89;yvm77Yr55WuLdWu?>L-@o z9cW!D)%T*&J!!BHc>OXvUQbb|XRzU%C!fZKtDNU;;ofE=dET z=iW?#jBRl(;!~RiWQKaxN8og_QNK&Z)5t?69mdpEH1gj0?5azbN$|1t19&Un5AnKr z<1OWv|5bMAME)Q_)5cFn7eNuaua^p-^W@Dzcl>uY#>lz#k_XYE$zk}>WlBzVt3YXOV59Yq zJY2DaK|F)THZImFv@WjlHG$~ZJ1Z@!Adv#qR>CDn@RV3aj$ZioU0I&?;i3H9_?z&D z=YP!VXXB4(Fit;^Kcb{!VfKFE9V`jLCKDm8#I0a(T+L49qx0U>VnVm6Ua!;t_mak< ze?i8{ZVJF{D5sM-N1u~I4Q|Z|M(FS^sp)dq54de&OgYQ*{#X#Aq^kW)z@ez{FUnK^ zYEJk$pT(Kn4^3OX08NSh$0sXYmd3^?AU|ca=h(&Q_tscQw^2)W(69+8_SA|jfk{>S zzl%LT1Y14YY#j`w3*oey$A*W0uQVLpoY;dK>em}u3HhAV5qGQUv8wvi#A%T69J2ga zIT#>Lvmd%@oznJ|&^Pk(8Zt_|>9{J1xSB8?Yhp)_8e9_VPgk1%#5SaOE&YTTjEipW zkpzne)GZAI8YmzwUhGE@P$j0OJuWOXivGL`)P;%Q%()C^4{wzhe>(r|1>7NMQc3gi zFn&=rg3NK|z3A3z3!aIYVTQ#F$HDzE7kH=BvDsc^Wu8J=SYk`k3HM_I;=r=?%$vG{ zGe6nUU>mXdf`)%9RglkhE7J@?RJzrF7e!5^3ao zMZP{-*whnA6qW34sE5mLQ;7Qe?yDx4rE&{soJB|8PrN}K=4H_|SH5D0?tculv|?ac zwp2@M`X~1_*F3$;hlQVr|M#k3=;#~AQ@1#i-?_Es1}BqyN3>8%5o=`Ip_$29e&iA@ zSiWmNR)C(A4taQYye!IkLkEfkTQ!a08iF+72%!tnns zN^sTy>w%-5zRiSpuhvW39dYx}SQ|-4(Oy%6W5@m)3YAgn8x-I@mO~K&>IN+NoP~Sq zn_>h#W_}RxEie#;nVCK1E1+q07#1(a3P-ZGZ6FTgH;I%3#L&L+7Z|-4(AwmkXgba%H5+zTYac{eC zcv&LQVfAn;A<&*X$KguP$SNc-`?safhWqL}Y3m)a!fCynq5p8lp|@01Mgrw)5f}ULN|xnwR8%H z)*#=2b&Db-Pi%4lxZ2v*H3mgrkdIW0UUA8xm{K5iS5rezkFnM$InY87OT{S`;)poE(ppZ-5a4S+4>_2v+c|@P1Xm1R_jE z61_G1i>aQ8U3#EDj@I77ki_*ap91}S(RCfa#U&x+QPH`u!j^qZyX6U(ZV^A_NVtDX zX+&I(;m%29r1`O6>LP61F2Gw@b0mo$xdx`w0=*Aq8t(=Z@&_+AFZes>;tU;0{u@n~ ziXi=0Vus*KKp1lJ@56ip;j#iQVG!EX)GNTaD?KmN~P`vqf>Mf+AFN2wg|vut(yW7a~ghiI233+m9jmMOv`L zh3(*j=h>PMlRgV?`8)ZuTDr(rY*jjrm+^`HVaI4E`a?GdKfm@}XAY#rL#caYF7_K4 zFFhkU1w3aj74c8O3)N|dT>WvSNYy2=m^G1B0QWHN_yH=x-}U)@;}G92zYOu``BJ*k zneuy1UfylJB?=Mms)OA5XC-)z9E8`TB?H26LMHlqJc++23oNm;u;bG@+T*CMfS;Q3 zTf6*&YWl{eVIufLf2LdKphU*E`~BM_f{4xW{bO%QA^ff#saSZdFbnI13t%Q<2iJSM!CyF32Q7##WCXU4Vekgme4lE_WK zE z9`-@CISYRQ0QlB6J{SPDB5kT0RTY&P5DvGK4N*$9!n=gRw9E`#?OqP5-LSq^Hg0g; zo)v$%U5_RbZYMqXRbJ7YF4y0+fc`33_+OE{s(qyt)`j)VAU8b!!!W_t0O9HeQw@`R z<|^{}qj1Zo=78rDCGD=!^c95DMRx*4hxT6=?FbEKZ$JVFRNxPdnZkjTfzBJ4-uGL` zx6j`%+64SNVgmQaPpA(OmXBlSlDJ`o)-xe=45^dtAclgb_!Y#EIlaJ=opO%PZ4aa%KuN= zxaOa{48%Z-<5qfN*)ey=I{e=~KeB9(Qe-nAHLr4Wa|f_7XJkFUA0DMFc@&w4Q-p@T#$!- zE2_e!8^UG(`*@+zB~2mr##^jG*PRbZ7+7G`gfq2$+zLw- z`5rI$;P@`bjT6uC<@ls?RHjAV14JIk2V11o`1vDDk@fZW^_d0+wztH*PHYQ@eR!}E zcYWKcMHbofI4vYR{12A%F}A$m+(-Vz|F4KEk4rM^!ia-gE;Z#cX=c;cFVPem4O7ge zscHOWNuLd9hGONCfN1WTCRtcHYJQoPplFtsD5wR7pkdUMY0%;dscD%jskxNqKajAus8`(PZp;=mh={{gs&~gbEqn?>y(Xn!DSXSZ$`?{h* z)+==#2RpR&Qxrp@SCj7eRUqlmy4*fx_w7ekIu-EI4*&7xkEbuGY}Xx>7V!4|t{MDr zV+tYD^5oNE?L}+H2olDDs*3W^(S^C(ILdO?(OCC`+Z|ut=w7`o>RyTp*Fi)e?B)NL zsxLND?QzVxkSL5+oXCbN#a{MdRmdIzT=|%CuJyR_IMl&SG~AhF?PM_VR3wl=ng&%5{Qn~`VHWYQr%)>mtjV0iX< ziPc;}!*v)c%zH3Cv0=NS2>B)$_)kDUrgj7|sE1VMN=`iwRo-t~2z-b0=^W8wVd5r; z~`S_7@BF?2rYSJzBEy3*hhqWyL=L0sV}(}*V|^3`d8a(ARU!<=Y;9vH z9+kR&6XkxYT>j9h;Rvc}I!t<`+|9lvgIeCzCuBR6p{a2SldZP4U&dl~5VP<#Q*TeZ zVLDBcH4owO-&JLaoYW)oJ5WLv04?|vBF-0#-nxv}6ZKxXeU;On6;d`H<^~S>*u%tp zuM-IM3p4FrDAL_GSU53@J9*CPxxHJid4;M6XdMUi6AaT$zZ8C?S5YY9+O>A=1UAMt zUT!>%i*+W;O2sz7p(Kvd*M+O*F#3`|(#y=oO-3PLDyDy{Q}Y=pl;bL! z@(bAK4uvK{4$aMLRV_DR5Jgn#`arAm?#6+a6w1d}Z@XqsX;pyLUL3V6Tm_AO1y(>A zWO`XkAK^h>!IoO2jklKBw6vw=2ihlf&-{FrfkLe878=ef75J4#xdsS`L^Dus(#>0j zlg)pL^UNaCB`BY}0cNPp#*dIyKkHY;{eD3@4}jm-?n|wf8ZBja@I$q3!7G5SpgiI= z5qzguq||mBXV4w$DI;y6yQ5+zw{7%zbfU-49s#)~Um#vtJ1cE8?^TA~;+ZJhminlB z40j$UD|?jd-+O=bBl#rciZb9D^*^evB6G{03_o^SB2%E1zlVydUt}Ccc#3=${0x7u zJ1*Z7SlcHQ8SFBBcQ2fu4|1$gm4K;AM`e=ebdY03apl^cE6+@NIG)VP5FlQg1(ATL zd*55<7gffn3Tef zk3S*nQpcrtDNP`NS{xJi>nNMTcGG90YVJV_x~#en1lirMLG#!hB; z-Oj&8HqYE&n6brD|1hpV+PFffj_i*2j;scBtF75yq9^mF$EH~88svEWpRincg^r_5PDX&^ZYEyDf)f;Pu~^)JNzU=1O3ShS zgBQi=7p6bcBGgiQPc$Tvv7ky!U;KSp(NUO2LOFL#QS22TJyE@ImxSkQwG@E&l)}F3 zmK9)peM5ZCPSWfCF}lWCsPlJ`%qH_b?M9RzU&Xow*F{l_tRf?-t|&oC87dL8B!}%M zmb^_72n5N>I|hn^{QQgA+1a%Gb6vmc@*YQ#*O$5?fx11SStrrV>vLOrScGasG8j4J zU}wxJE$vEpDts4!*xvJx8;A|e^fTBjDwe|f^&fN}kE6=}+PqL%jGa1HaJ+*ha1 zFNTUuO2cgb>PU4nXXeW_)%B$9(N_o=Bw69vTa3g6GothO`~#Ajhu2>zvVvg6Vyj0Vg9XOU0l<2R<|+VMn^*)5;pHoUdGtPd&dB z6`4ui0x+(j%T=K+x7nOqni);Kc?SPCc=Q&0AC~A{6uUm4R=rGy4geQa%VCZp1$Unw zK5uNIVK&z){Bs#}^kv{_2^zSJ0Sj#HFE1S(3WtY1pN@?cAH`J%0)&Bt$#KBIn@BRr zkGXT(b%MDc;8SX%n}vxA0qEy=h1hKWxLKy4|2PwkF?|O$#UDrwnrV+wZ zf4ZJ(*B!?x%wC8Ks;&i*#=tWSh-VF0q#PsyUc+M)!FT3#u4Q(jV&#Wa#O~d0l7KezVsYQHuJx<742Pvx=ahAsHv3K;mfA3S=1v!8hJ|6$}> AkN^Mx diff --git a/test/jasmine/tests/gl3daxes_test.js b/test/jasmine/tests/gl3daxes_test.js index 4c8778b14c8..1e4b61912bd 100644 --- a/test/jasmine/tests/gl3daxes_test.js +++ b/test/jasmine/tests/gl3daxes_test.js @@ -11,7 +11,8 @@ describe('Test Gl3dAxes', function() { var options = { font: 'Open Sans', scene: {id: 'scene'}, - data: [{x: [], y: []}] + data: [{x: [], y: []}], + bgColor: '#fff' }; beforeEach(function() { From f7b24475890e58716b3699fbc62590aedde0c5dc Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Tue, 5 Apr 2016 00:26:29 +0200 Subject: [PATCH 46/76] jsdoc for titles.draw --- src/components/titles/index.js | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/src/components/titles/index.js b/src/components/titles/index.js index cb4a436188e..9fedc319856 100644 --- a/src/components/titles/index.js +++ b/src/components/titles/index.js @@ -24,7 +24,31 @@ var Titles = module.exports = {}; /** * Titles - (re)draw titles on the axes and plot: - * title can be 'xtitle', 'ytitle', 'gtitle' + * @param {DOM element} gd - the graphDiv + * @param {string} titleClass - the css class of this title + * @param {object} options - how and what to draw + * propContainer - the layout object containing `title` and `titlefont` + * attributes that apply to this title + * propName - the full name of the title property (for Plotly.relayout) + * [traceIndex] - include only if this property applies to one trace + * (such as a colorbar title) - then editing pipes to Plotly.restyle + * instead of Plotly.relayout + * dfltName - the name of the title in placeholder text + * [avoid] {object} - include if this title should move to avoid other elements + * selection - d3 selection of elements to avoid + * side - which direction to move if there is a conflict + * [offsetLeft] - if these elements are subject to a translation + * wrt the title element + * [offsetTop] + * attributes {object} - position and alignment attributes + * x - pixels + * y - pixels + * text-anchor - start|middle|end + * transform {object} - how to transform the title after positioning + * rotate - degrees + * offset - shift up/down in the rotated frame (unused?) + * containerGroup - if an svg element already exists to hold this + * title, include here. Otherwise it will go in fullLayout._infolayer */ Titles.draw = function(gd, titleClass, options) { var cont = options.propContainer, From 3974d114c4f25b5d1ecab22f017e6a497e9b2301 Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Tue, 5 Apr 2016 00:29:58 +0200 Subject: [PATCH 47/76] remove single-use vars --- src/plots/ternary/ternary.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/plots/ternary/ternary.js b/src/plots/ternary/ternary.js index 9a386cf7355..e253ea8a3e6 100644 --- a/src/plots/ternary/ternary.js +++ b/src/plots/ternary/ternary.js @@ -375,28 +375,27 @@ proto.drawAxes = function(doTitles) { } }); - var btitleSize = baxis.titlefont.size, - bpad = (baxis.showticklabels ? baxis.tickfont.size : 0) + - (baxis.ticks === 'outside' ? baxis.ticklen : 0) + 3; + var bpad = (baxis.showticklabels ? baxis.tickfont.size : 0) + + (baxis.ticks === 'outside' ? baxis.ticklen : 0) + 3; + Titles.draw(gd, 'b' + titlesuffix, { propContainer: baxis, propName: _this.id + '.baxis.title', dfltName: 'Component B', attributes: { x: _this.x0 - bpad, - y: _this.y0 + _this.h + btitleSize * 0.83 + bpad, + y: _this.y0 + _this.h + baxis.titlefont.size * 0.83 + bpad, 'text-anchor': 'middle' } }); - var ctitleSize = caxis.titlefont.size; Titles.draw(gd, 'c' + titlesuffix, { propContainer: caxis, propName: _this.id + '.caxis.title', dfltName: 'Component C', attributes: { x: _this.x0 + _this.w + bpad, - y: _this.y0 + _this.h + ctitleSize * 0.83 + bpad, + y: _this.y0 + _this.h + caxis.titlefont.size * 0.83 + bpad, 'text-anchor': 'middle' } }); From 06565e047ff4270345042a01d8e166d02253fc70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Wed, 6 Apr 2016 17:38:43 -0400 Subject: [PATCH 48/76] add test for lib/setcursor.js --- test/jasmine/tests/lib_test.js | 57 +++++++++++++++++++++++++++++++++- 1 file changed, 56 insertions(+), 1 deletion(-) diff --git a/test/jasmine/tests/lib_test.js b/test/jasmine/tests/lib_test.js index 96c4cadc91f..3b11c1868d9 100644 --- a/test/jasmine/tests/lib_test.js +++ b/test/jasmine/tests/lib_test.js @@ -1,5 +1,11 @@ var Lib = require('@src/lib'); -var Plots = require('@src/plots/plots'); +var setCursor = require('@src/lib/setcursor'); + +var d3 = require('d3'); +var PlotlyInternal = require('@src/plotly'); +var createGraphDiv = require('../assets/create_graph_div'); +var destroyGraphDiv = require('../assets/destroy_graph_div'); +var Plots = PlotlyInternal.Plots; describe('Test lib.js:', function() { @@ -715,4 +721,53 @@ describe('Test lib.js:', function() { }); }); + describe('setCursor', function() { + + beforeEach(function() { + this.el3 = d3.select(createGraphDiv()); + }); + + afterEach(destroyGraphDiv); + + it('should assign cursor- class', function() { + setCursor(this.el3, 'one'); + + expect(this.el3.attr('class')).toEqual('cursor-one'); + }); + + it('should assign cursor- class while present non-cursor- classes', function() { + this.el3.classed('one', true); + this.el3.classed('two', true); + this.el3.classed('three', true); + setCursor(this.el3, 'one'); + + expect(this.el3.attr('class')).toEqual('one two three cursor-one'); + }); + + it('should update class from one cursor- class to another', function() { + this.el3.classed('cursor-one', true); + setCursor(this.el3, 'two'); + + expect(this.el3.attr('class')).toEqual('cursor-two'); + }); + + it('should update multiple cursor- classes', function() { + this.el3.classed('cursor-one', true); + this.el3.classed('cursor-two', true); + this.el3.classed('cursor-three', true); + setCursor(this.el3, 'four'); + + expect(this.el3.attr('class')).toEqual('cursor-four'); + }); + + it('should remove cursor- if no new class is given', function() { + this.el3.classed('cursor-one', true); + this.el3.classed('cursor-two', true); + this.el3.classed('cursor-three', true); + setCursor(this.el3); + + expect(this.el3.attr('class')).toEqual(''); + }); + }); + }); From 670dc0dac4ac3eb3cce5c5b945ee97d2c40e2c8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Wed, 6 Apr 2016 17:39:11 -0400 Subject: [PATCH 49/76] add dragelment getCursor and align tests --- test/jasmine/tests/dragelement_test.js | 155 +++++++++++++++++++++++++ 1 file changed, 155 insertions(+) create mode 100644 test/jasmine/tests/dragelement_test.js diff --git a/test/jasmine/tests/dragelement_test.js b/test/jasmine/tests/dragelement_test.js new file mode 100644 index 00000000000..3f06b4a8394 --- /dev/null +++ b/test/jasmine/tests/dragelement_test.js @@ -0,0 +1,155 @@ +var dragElement = require('@src/components/dragelement'); + + +describe('dragElement.getCursor', function() { + 'use strict'; + + var getCursor = dragElement.getCursor; + + it('should return sw-resize when x < 1/3, y < 1/3', function() { + var cursor = getCursor(0.2, 0); + expect(cursor).toEqual('sw-resize'); + + cursor = getCursor(1, 0, 'left'); + expect(cursor).toEqual('sw-resize', 'with left xanchor'); + + cursor = getCursor(0.3, 1, null, 'bottom'); + expect(cursor).toEqual('sw-resize', 'with bottom yanchor'); + }); + + it('should return s-resize when 1/3 < x < 2/3, y < 1/3', function() { + var cursor = getCursor(0.4, 0.3); + expect(cursor).toEqual('s-resize'); + + cursor = getCursor(0, 0, 'center'); + expect(cursor).toEqual('s-resize', 'with center xanchor'); + + cursor = getCursor(0.63, 1, null, 'bottom'); + expect(cursor).toEqual('s-resize', 'with bottom yanchor'); + }); + + it('should return se-resize when x > 2/3, y < 1/3', function() { + var cursor = getCursor(0.9, 0.1); + expect(cursor).toEqual('se-resize'); + + cursor = getCursor(0, 0, 'right'); + expect(cursor).toEqual('se-resize', 'with right xanchor'); + + cursor = getCursor(0.63, 1, null, 'bottom'); + expect(cursor).toEqual('s-resize', 'with bottom yanchor'); + }); + + it('should return w-resize when x < 1/3, 1/3 < y < 2/3', function() { + var cursor = getCursor(0.1, 0.4); + expect(cursor).toEqual('w-resize'); + + cursor = getCursor(0.9, 0.5, 'left'); + expect(cursor).toEqual('w-resize', 'with left xanchor'); + + cursor = getCursor(0.1, 0.1, null, 'middle'); + expect(cursor).toEqual('w-resize', 'with middle yanchor'); + }); + + it('should return move when 1/3 < x < 2/3, 1/3 < y < 2/3', function() { + var cursor = getCursor(0.4, 0.4); + expect(cursor).toEqual('move'); + + cursor = getCursor(0.9, 0.5, 'center'); + expect(cursor).toEqual('move', 'with center xanchor'); + + cursor = getCursor(0.4, 0.1, null, 'middle'); + expect(cursor).toEqual('move', 'with middle yanchor'); + }); + + it('should return e-resize when x > 1/3, 1/3 < y < 2/3', function() { + var cursor = getCursor(0.8, 0.4); + expect(cursor).toEqual('e-resize'); + + cursor = getCursor(0.09, 0.5, 'right'); + expect(cursor).toEqual('e-resize', 'with right xanchor'); + + cursor = getCursor(0.9, 0.1, null, 'middle'); + expect(cursor).toEqual('e-resize', 'with middle yanchor'); + }); + + it('should return nw-resize when x > 1/3, y > 2/3', function() { + var cursor = getCursor(0.2, 0.7); + expect(cursor).toEqual('nw-resize'); + + cursor = getCursor(0.9, 0.9, 'left'); + expect(cursor).toEqual('nw-resize', 'with left xanchor'); + + cursor = getCursor(0.1, 0.1, null, 'top'); + expect(cursor).toEqual('nw-resize', 'with top yanchor'); + }); + + it('should return nw-resize when 1/3 < x < 2/3, y > 2/3', function() { + var cursor = getCursor(0.4, 0.7); + expect(cursor).toEqual('n-resize'); + + cursor = getCursor(0.9, 0.9, 'center'); + expect(cursor).toEqual('n-resize', 'with center xanchor'); + + cursor = getCursor(0.5, 0.1, null, 'top'); + expect(cursor).toEqual('n-resize', 'with top yanchor'); + }); + + it('should return nw-resize when x > 2/3, y > 2/3', function() { + var cursor = getCursor(0.7, 0.7); + expect(cursor).toEqual('ne-resize'); + + cursor = getCursor(0.09, 0.9, 'right'); + expect(cursor).toEqual('ne-resize', 'with right xanchor'); + + cursor = getCursor(0.8, 0.1, null, 'top'); + expect(cursor).toEqual('ne-resize', 'with top yanchor'); + }); +}); + +describe('dragElement.align', function() { + 'use strict'; + + var align = dragElement.align; + + it('should return min value if anchor is set to \'bottom\' or \'left\'', function() { + var al = align(0, 1, 0, 1, 'bottom'); + expect(al).toEqual(0); + + al = align(0, 1, 0, 1, 'left'); + expect(al).toEqual(0); + }); + + it('should return max value if anchor is set to \'top\' or \'right\'', function() { + var al = align(0, 1, 0, 1, 'top'); + expect(al).toEqual(1); + + al = align(0, 1, 0, 1, 'right'); + expect(al).toEqual(1); + }); + + it('should return center value if anchor is set to \'middle\' or \'center\'', function() { + var al = align(0, 1, 0, 1, 'middle'); + expect(al).toEqual(0.5); + + al = align(0, 1, 0, 1, 'center'); + expect(al).toEqual(0.5); + }); + + it('should return center value if anchor is set to \'middle\' or \'center\'', function() { + var al = align(0, 1, 0, 1, 'middle'); + expect(al).toEqual(0.5); + + al = align(0, 1, 0, 1, 'center'); + expect(al).toEqual(0.5); + }); + + it('should return min value ', function() { + var al = align(0, 1, 0, 1); + expect(al).toEqual(0); + }); + + it('should return max value ', function() { + var al = align(1, 1, 0, 1); + expect(al).toEqual(2); + }); +}); From aae3e47a5e2e7acf1efd72095a6467c8b55ce1cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Thu, 7 Apr 2016 16:27:43 -0400 Subject: [PATCH 50/76] add dragElement interaction tests --- test/jasmine/tests/dragelement_test.js | 146 +++++++++++++++++++++++++ 1 file changed, 146 insertions(+) diff --git a/test/jasmine/tests/dragelement_test.js b/test/jasmine/tests/dragelement_test.js index 3f06b4a8394..924f7f3bcaf 100644 --- a/test/jasmine/tests/dragelement_test.js +++ b/test/jasmine/tests/dragelement_test.js @@ -1,5 +1,151 @@ var dragElement = require('@src/components/dragelement'); +var d3 = require('d3'); +var createGraphDiv = require('../assets/create_graph_div'); +var destroyGraphDiv = require('../assets/destroy_graph_div'); +var mouseEvent = require('../assets/mouse_event'); + + +describe('dragElement', function() { + 'use strict'; + + beforeEach(function() { + this.gd = createGraphDiv(); + this.hoverlayer = document.createElement('div'); + this.element = document.createElement('div'); + + this.gd.className = 'js-plotly-plot'; + this.gd._fullLayout = { + _hoverlayer: d3.select(this.hoverlayer) + }; + this.element.innerHTML = 'drag element'; + + this.gd.appendChild(this.element); + this.element.appendChild(this.hoverlayer); + + var clientRect = this.element.getBoundingClientRect(); + this.x = clientRect.left + clientRect.width / 2; + this.y = clientRect.top + clientRect.height / 2; + }); + + afterEach(destroyGraphDiv); + + it('should init drag element', function() { + var options = { element: this.element }; + dragElement.init(options); + + expect(this.element.style.pointerEvents).toEqual('all', 'with pointer event style'); + expect(this.element.onmousedown).not.toBe(null, 'with on mousedown handler'); + }); + + it('should pass event, startX and startY to prepFn on mousedown', function() { + var args = []; + var options = { + element: this.element, + prepFn: function(event, startX, startY) { + args = [event, startX, startY]; + } + }; + dragElement.init(options); + + mouseEvent('mousedown', this.x, this.y); + mouseEvent('mouseup', this.x, this.y); + + expect(args[0]).not.toBe(null); + expect(args[1]).toEqual(Math.floor(this.x)); + expect(args[2]).toEqual(Math.floor(this.y)); + }); + + it('should pass dragged, dx and dy to moveFn on mousemove', function() { + var args = []; + var options = { + element: this.element, + moveFn: function(dx, dy, dragged) { + args = [dx, dy, dragged]; + } + }; + dragElement.init(options); + + mouseEvent('mousedown', this.x, this.y); + mouseEvent('mousemove', this.x + 10, this.y + 10); + mouseEvent('mouseup', this.x, this.y); + + expect(args[0]).toEqual(10); + expect(args[1]).toEqual(10); + expect(args[2]).toBe(true); + }); + + it('should pass dragged and numClicks to doneFn on mouseup & mouseout', function() { + var args = []; + var options = { + element: this.element, + doneFn: function(dragged, numClicks) { + args = [dragged, numClicks]; + } + }; + dragElement.init(options); + + mouseEvent('mousedown', this.x, this.y); + mouseEvent('mouseup', this.x, this.y); + + expect(args[0]).toBe(false); + expect(args[1]).toEqual(1); + + mouseEvent('mousedown', this.x, this.y); + mouseEvent('mousemove', this.x + 10, this.y + 10); + mouseEvent('mouseout', this.x, this.y); + + expect(args[0]).toBe(true); + expect(args[1]).toEqual(2); + }); + + it('should add a cover slip div to the DOM', function() { + function countCoverSlip() { + return d3.selectAll('.dragcover').size(); + } + + var options = { element: this.element }; + dragElement.init(options); + + expect(countCoverSlip()).toEqual(0); + + mouseEvent('mousedown', this.x, this.y); + expect(countCoverSlip()).toEqual(1); + + mouseEvent('mousemove', this.x + 10, this.y + 10); + expect(countCoverSlip()).toEqual(1); + + mouseEvent('mouseout', this.x, this.y); + expect(countCoverSlip()).toEqual(0); + }); + + it('should fire off click event when down/up without dragging', function() { + var options = { element: this.element }; + dragElement.init(options); + + var mockObj = { + handleClick: function() {}, + dummy: function() {} + }; + spyOn(mockObj, 'handleClick'); + spyOn(mockObj, 'dummy'); + + this.element.onclick = mockObj.handleClick; + + mouseEvent('mousedown', this.x, this.y); + mouseEvent('mouseup', this.x, this.y); + + expect(mockObj.handleClick).toHaveBeenCalled(); + + this.element.onclick = mockObj.dummy; + + mouseEvent('mousedown', this.x, this.y); + mouseEvent('mousemove', this.x + 10, this.y + 10); + mouseEvent('mouseup', this.x, this.y); + + expect(mockObj.dummy).not.toHaveBeenCalled(); + }); +}); describe('dragElement.getCursor', function() { 'use strict'; From fbd0a0b9aed1d188870453663b44a659e84e14d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Thu, 7 Apr 2016 16:51:53 -0400 Subject: [PATCH 51/76] enforce no multiple empty lines at EOF (just eol-last is enough) --- .eslintrc | 2 +- src/plots/ternary/ternary.js | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.eslintrc b/.eslintrc index f39c183260f..5f0ec7491e4 100644 --- a/.eslintrc +++ b/.eslintrc @@ -8,7 +8,7 @@ }, "rules": { "no-trailing-spaces": [2], - "no-multiple-empty-lines": [2, {"max": 2, "maxEOF": 1}], + "no-multiple-empty-lines": [2, {"max": 2, "maxEOF": 0}], "eol-last": [2], "linebreak-style": [2, "unix"], "indent": [2, 4, {"SwitchCase": 1}], diff --git a/src/plots/ternary/ternary.js b/src/plots/ternary/ternary.js index e253ea8a3e6..339286329c1 100644 --- a/src/plots/ternary/ternary.js +++ b/src/plots/ternary/ternary.js @@ -695,4 +695,3 @@ function removeZoombox(gd) { .selectAll('.zoombox,.js-zoombox-backdrop,.js-zoombox-menu,.zoombox-corners') .remove(); } - From 8cc59a9f734a737ec1fadd4f7c76e30ef4b4b911 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Fri, 8 Apr 2016 11:17:20 -0400 Subject: [PATCH 52/76] add axis.color bleading test with 2d and 3d axes --- test/jasmine/tests/axes_test.js | 61 +++++++++++++++++++++++++++ test/jasmine/tests/gl3dlayout_test.js | 25 +++++++++++ 2 files changed, 86 insertions(+) diff --git a/test/jasmine/tests/axes_test.js b/test/jasmine/tests/axes_test.js index 6d4eab3721b..f820e830263 100644 --- a/test/jasmine/tests/axes_test.js +++ b/test/jasmine/tests/axes_test.js @@ -321,6 +321,67 @@ describe('Test axes', function() { supplyLayoutDefaults(layoutIn, layoutOut, fullData); expect(layoutOut._hasCartesian).toBe(undefined); }); + + it('should use \'axis.color\' as default for \'axis.titlefont.color\'', function() { + layoutIn = { + xaxis: { color: 'red' }, + yaxis: {}, + yaxis2: { titlefont: { color: 'yellow' } } + }; + + layoutOut.font = { color: 'blue' }, + + supplyLayoutDefaults(layoutIn, layoutOut, fullData); + expect(layoutOut.xaxis.titlefont.color).toEqual('red'); + expect(layoutOut.yaxis.titlefont.color).toEqual('blue'); + expect(layoutOut.yaxis2.titlefont.color).toEqual('yellow'); + }); + + it('should use \'axis.color\' as default for \'axis.linecolor\'', function() { + layoutIn = { + xaxis: { showline: true, color: 'red' }, + yaxis: { linecolor: 'blue' }, + yaxis2: { showline: true } + }; + + supplyLayoutDefaults(layoutIn, layoutOut, fullData); + expect(layoutOut.xaxis.linecolor).toEqual('red'); + expect(layoutOut.yaxis.linecolor).toEqual('blue'); + expect(layoutOut.yaxis2.linecolor).toEqual('#444'); + }); + + it('should use \'axis.color\' as default for \'axis.zerolinecolor\'', function() { + layoutIn = { + xaxis: { showzeroline: true, color: 'red' }, + yaxis: { zerolinecolor: 'blue' }, + yaxis2: { showzeroline: true } + }; + + supplyLayoutDefaults(layoutIn, layoutOut, fullData); + expect(layoutOut.xaxis.zerolinecolor).toEqual('red'); + expect(layoutOut.yaxis.zerolinecolor).toEqual('blue'); + expect(layoutOut.yaxis2.zerolinecolor).toEqual('#444'); + }); + + it('should use combo of \'axis.color\', bgcolor and lightFraction as default for \'axis.gridcolor\'', function() { + layoutIn = { + paper_bgcolor: 'green', + plot_bgcolor: 'yellow', + xaxis: { showgrid: true, color: 'red' }, + yaxis: { gridcolor: 'blue' }, + yaxis2: { showgrid: true } + }; + + var bgColor = Color.combine('yellow', 'green'), + frac = 100 * (0xe - 0x4) / (0xf - 0x4); + + supplyLayoutDefaults(layoutIn, layoutOut, fullData); + expect(layoutOut.xaxis.gridcolor) + .toEqual(tinycolor.mix('red', bgColor, frac).toRgbString()); + expect(layoutOut.yaxis.gridcolor).toEqual('blue'); + expect(layoutOut.yaxis2.gridcolor) + .toEqual(tinycolor.mix('#444', bgColor, frac).toRgbString()); + }); }); describe('handleTickDefaults', function() { diff --git a/test/jasmine/tests/gl3dlayout_test.js b/test/jasmine/tests/gl3dlayout_test.js index 466a24f3314..8a2f4f81cdc 100644 --- a/test/jasmine/tests/gl3dlayout_test.js +++ b/test/jasmine/tests/gl3dlayout_test.js @@ -1,5 +1,8 @@ var Gl3d = require('@src/plots/gl3d'); +var tinycolor = require('tinycolor2'); +var Color = require('@src/components/color'); + describe('Test Gl3d layout defaults', function() { 'use strict'; @@ -226,5 +229,27 @@ describe('Test Gl3d layout defaults', function() { supplyLayoutDefaults(layoutIn, layoutOut, fullData); expect(layoutIn.scene).toBe(undefined); }); + + it('should use combo of \'axis.color\', bgcolor and lightFraction as default for \'axis.gridcolor\'', function() { + layoutIn = { + paper_bgcolor: 'green', + scene: { + bgcolor: 'yellow', + xaxis: { showgrid: true, color: 'red' }, + yaxis: { gridcolor: 'blue' }, + zaxis: { showgrid: true } + } + }; + + var bgColor = Color.combine('yellow', 'green'), + frac = 100 * (204 - 0x44) / (255 - 0x44); + + supplyLayoutDefaults(layoutIn, layoutOut, fullData); + expect(layoutOut.scene.xaxis.gridcolor) + .toEqual(tinycolor.mix('red', bgColor, frac).toRgbString()); + expect(layoutOut.scene.yaxis.gridcolor).toEqual('blue'); + expect(layoutOut.scene.zaxis.gridcolor) + .toEqual(tinycolor.mix('#444', bgColor, frac).toRgbString()); + }); }); }); From e4e1019894d4d022ae7e51fa340a5221fc39d27d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Fri, 8 Apr 2016 14:46:30 -0400 Subject: [PATCH 53/76] add check for selectable scatterternary trace: - only scatterternary traces with mode 'markers' or/and 'text' can be selected --- src/components/modebar/manage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/modebar/manage.js b/src/components/modebar/manage.js index c85db4a25e9..e0c6ba3f6bf 100644 --- a/src/components/modebar/manage.js +++ b/src/components/modebar/manage.js @@ -173,7 +173,7 @@ function isSelectable(fullData) { if(!trace._module || !trace._module.selectPoints) continue; - if(trace.type === 'scatter') { + if(trace.type === 'scatter' || trace.type === 'scatterternary') { if(scatterSubTypes.hasMarkers(trace) || scatterSubTypes.hasText(trace)) { selectable = true; } From e3c0dd875040f9039b456a03c8e7c2ea8b49a6da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Fri, 8 Apr 2016 14:47:57 -0400 Subject: [PATCH 54/76] fix ternary + other sub plot type graphs: - continue in calcdata loop in scatterternary trace isn't so that we don't attempt to fill calcdata[-1] --- src/traces/scatterternary/plot.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/traces/scatterternary/plot.js b/src/traces/scatterternary/plot.js index 8fb76c9b076..81eccb22518 100644 --- a/src/traces/scatterternary/plot.js +++ b/src/traces/scatterternary/plot.js @@ -21,15 +21,19 @@ module.exports = function plot(ternary, data) { }; var calcdata = new Array(data.length), - fullCalcdata = ternary.graphDiv.calcdata, - i, j; - for(i = 0; i < fullCalcdata.length; i++) { - j = data.indexOf(fullCalcdata[i][0].trace); - if(j !== -1) calcdata[j] = fullCalcdata[i]; + fullCalcdata = ternary.graphDiv.calcdata; + + for(var i = 0; i < fullCalcdata.length; i++) { + var j = data.indexOf(fullCalcdata[i][0].trace); + + if(j === -1) continue; + + calcdata[j] = fullCalcdata[i]; // while we're here and have references to both the Ternary object // and fullData, connect the two (for use by hover) data[j]._ternary = ternary; } + scatterPlot(ternary.graphDiv, plotinfo, calcdata); }; From 324446d9ac59bc44c6d5a64d90d29ad37bec378d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Fri, 8 Apr 2016 15:35:57 -0400 Subject: [PATCH 55/76] add mode bar manger test for ternary plots --- test/jasmine/tests/modebar_test.js | 69 ++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/test/jasmine/tests/modebar_test.js b/test/jasmine/tests/modebar_test.js index 1cd43aae7ce..9931091d431 100644 --- a/test/jasmine/tests/modebar_test.js +++ b/test/jasmine/tests/modebar_test.js @@ -373,6 +373,75 @@ describe('ModeBar', function() { checkButtons(modeBar, buttons, 1); }); + it('creates mode bar (un-selectable ternary version)', function() { + var buttons = getButtons([ + ['toImage', 'sendDataToCloud'], + ['zoom2d', 'pan2d'] + ]); + + var gd = getMockGraphInfo(); + gd._fullLayout._hasTernary = true; + + manageModeBar(gd); + var modeBar = gd._fullLayout._modeBar; + + checkButtons(modeBar, buttons, 1); + }); + + it('creates mode bar (selectable ternary version)', function() { + var buttons = getButtons([ + ['toImage', 'sendDataToCloud'], + ['zoom2d', 'pan2d', 'select2d', 'lasso2d'] + ]); + + var gd = getMockGraphInfo(); + gd._fullLayout._hasTernary = true; + gd._fullData = [{ + type: 'scatterternary', + visible: true, + mode: 'markers', + _module: {selectPoints: true} + }]; + + manageModeBar(gd); + var modeBar = gd._fullLayout._modeBar; + + checkButtons(modeBar, buttons, 1); + }); + + it('creates mode bar (ternary + cartesian version)', function() { + var buttons = getButtons([ + ['toImage', 'sendDataToCloud'], + ['zoom2d', 'pan2d'], + ['hoverClosestCartesian', 'hoverCompareCartesian'] + ]); + + var gd = getMockGraphInfo(); + gd._fullLayout._hasTernary = true; + gd._fullLayout._hasCartesian = true; + + manageModeBar(gd); + var modeBar = gd._fullLayout._modeBar; + + checkButtons(modeBar, buttons, 1); + }); + + it('creates mode bar (ternary + gl3d version)', function() { + var buttons = getButtons([ + ['toImage', 'sendDataToCloud'], + ['resetViews', 'toggleHover'] + ]); + + var gd = getMockGraphInfo(); + gd._fullLayout._hasTernary = true; + gd._fullLayout._hasGL3D = true; + + manageModeBar(gd); + var modeBar = gd._fullLayout._modeBar; + + checkButtons(modeBar, buttons, 1); + }); + it('throws an error if modeBarButtonsToRemove isn\'t an array', function() { var gd = getMockGraphInfo(); gd._context.modeBarButtonsToRemove = 'not gonna work'; From 6a00fc2fa20436f43fdff23aa9dd54f94965fe55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Fri, 8 Apr 2016 16:57:09 -0400 Subject: [PATCH 56/76] add ternary simple mock --- test/image/baselines/ternary_simple.png | Bin 0 -> 41350 bytes test/image/mocks/ternary_simple.json | 52 ++++++++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 test/image/baselines/ternary_simple.png create mode 100644 test/image/mocks/ternary_simple.json diff --git a/test/image/baselines/ternary_simple.png b/test/image/baselines/ternary_simple.png new file mode 100644 index 0000000000000000000000000000000000000000..c6d5869de4053376195a8a60b4176fc714004515 GIT binary patch literal 41350 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 Vd@Z5f$dv&IJYD@<);T3K0RT1e>ox!Y literal 0 HcmV?d00001 diff --git a/test/image/mocks/ternary_simple.json b/test/image/mocks/ternary_simple.json new file mode 100644 index 00000000000..f3f45548657 --- /dev/null +++ b/test/image/mocks/ternary_simple.json @@ -0,0 +1,52 @@ +{ + "data": [ + { + "a": [ + 2, + 1, + 1 + ], + "b": [ + 1, + 2, + 1 + ], + "c": [ + 1, + 1, + 2.12345 + ], + "type": "scatterternary", + "uid": "412fa8" + } + ], + "layout": { + "ternary": { + "aaxis": { + "showline": true, + "ticks": "outside", + "showgrid": true, + "color": "#ccc", + "min": 0.231 + }, + "baxis": { + "showline": true, + "ticks": "outside", + "showgrid": true, + "color": "#0f0", + "title": "chocolate", + "min": 0.2 + }, + "caxis": { + "showline": true, + "ticks": "outside", + "showgrid": true, + "min": 0.11 + }, + "bgcolor": "#eee" + }, + "height": 450, + "width": 1731.82, + "autosize": true + } +} From 8338be6a5004ba650faeb08987cad8f74e758dad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Fri, 8 Apr 2016 17:35:50 -0400 Subject: [PATCH 57/76] properly delete clip defs from paper --- src/plots/ternary/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plots/ternary/index.js b/src/plots/ternary/index.js index 84bbd38b634..2db0226490d 100644 --- a/src/plots/ternary/index.js +++ b/src/plots/ternary/index.js @@ -66,7 +66,7 @@ exports.clean = function(newFullData, newFullLayout, oldFullData, oldFullLayout) if(!newFullLayout[oldTernaryKey] && !!oldTernary) { oldTernary.plotContainer.remove(); - oldFullLayout.clipDef.remove(); + oldTernary.clipDef.remove(); } } }; From dfcd6c317e2fe418a5ecdafb2b21b7243eb88fc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Fri, 8 Apr 2016 17:37:43 -0400 Subject: [PATCH 58/76] add ternary test suite: - add one visibility toogle test - add one delete / add trace test --- test/jasmine/tests/ternary_test.js | 88 ++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 test/jasmine/tests/ternary_test.js diff --git a/test/jasmine/tests/ternary_test.js b/test/jasmine/tests/ternary_test.js new file mode 100644 index 00000000000..c2b81425596 --- /dev/null +++ b/test/jasmine/tests/ternary_test.js @@ -0,0 +1,88 @@ +var Plotly = require('@lib'); +var Lib = require('@src/lib'); +var d3 = require('d3'); +var createGraphDiv = require('../assets/create_graph_div'); +var destroyGraphDiv = require('../assets/destroy_graph_div'); + + +describe('ternary plots', function() { + 'use strict'; + + afterEach(destroyGraphDiv); + + describe('with scatterternary trace(s)', function() { + var mock = require('@mocks/ternary_simple.json'); + var gd; + + beforeEach(function(done) { + gd = createGraphDiv(); + + var mockCopy = Lib.extendDeep({}, mock); + + Plotly.plot(gd, mockCopy.data, mockCopy.layout).then(done); + }); + + it('should be able to toggle trace visibility', function(done) { + expect(countTraces('scatter')).toEqual(1); + + Plotly.restyle(gd, 'visible', false).then(function() { + expect(countTraces('scatter')).toEqual(0); + + return Plotly.restyle(gd, 'visible', true); + }).then(function() { + expect(countTraces('scatter')).toEqual(1); + + return Plotly.restyle(gd, 'visible', 'legendonly'); + }).then(function() { + expect(countTraces('scatter')).toEqual(0); + + return Plotly.restyle(gd, 'visible', true); + }).then(function() { + expect(countTraces('scatter')).toEqual(1); + + done(); + }); + }); + + it('should be able to delete and add traces', function(done) { + expect(countTernarySubplot()).toEqual(1); + expect(countTraces('scatter')).toEqual(1); + + Plotly.deleteTraces(gd, [0]).then(function() { + expect(countTernarySubplot()).toEqual(0); + expect(countTraces('scatter')).toEqual(0); + + var trace = Lib.extendDeep({}, mock.data[0]); + + return Plotly.addTraces(gd, [trace]); + }).then(function() { + expect(countTernarySubplot()).toEqual(1); + expect(countTraces('scatter')).toEqual(1); + + var trace = Lib.extendDeep({}, mock.data[0]); + + return Plotly.addTraces(gd, [trace]); + }).then(function() { + expect(countTernarySubplot()).toEqual(1); + expect(countTraces('scatter')).toEqual(2); + + return Plotly.deleteTraces(gd, [0]); + }).then(function() { + expect(countTernarySubplot()).toEqual(1); + expect(countTraces('scatter')).toEqual(1); + + done(); + }); + + }); + + }); + + function countTernarySubplot() { + return d3.selectAll('.ternary').size(); + } + + function countTraces(type) { + return d3.selectAll('.ternary').selectAll('g.trace.' + type).size(); + } +}); From b37439ea33f893ec13dbc86189f447fa156637cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Mon, 11 Apr 2016 14:34:22 -0400 Subject: [PATCH 59/76] fix typo (brings back ternary hover labels !!!) --- src/plots/cartesian/graph_interact.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plots/cartesian/graph_interact.js b/src/plots/cartesian/graph_interact.js index de3e76d955e..1175bc476b5 100644 --- a/src/plots/cartesian/graph_interact.js +++ b/src/plots/cartesian/graph_interact.js @@ -319,7 +319,7 @@ function hover(gd, evt, subplot) { }), yaArray = subplots.map(function(spId) { var ternary = (gd._fullLayout[spId] || {})._ternary; - if(ternary) return ternary.xaxis; + if(ternary) return ternary.yaxis; return Plotly.Axes.getFromId(gd, spId, 'y'); }), hovermode = evt.hovermode || fullLayout.hovermode; From e0c29f1b32f73cec5b8393a8250f18178db98baa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Mon, 11 Apr 2016 14:45:29 -0400 Subject: [PATCH 60/76] lint (mostly infix spacing) --- src/components/dragelement/cursor.js | 29 +++++++++++++++------------ src/components/dragelement/index.js | 18 ++++++++--------- src/components/dragelement/unhover.js | 8 +++++++- src/lib/setcursor.js | 5 +++-- 4 files changed, 35 insertions(+), 25 deletions(-) diff --git a/src/components/dragelement/cursor.js b/src/components/dragelement/cursor.js index 08cbfab29c3..8bffe085592 100644 --- a/src/components/dragelement/cursor.js +++ b/src/components/dragelement/cursor.js @@ -15,19 +15,22 @@ var Lib = require('../../lib'); // set cursors pointing toward the closest corner/side, // to indicate alignment // x and y are 0-1, fractions of the plot area -var cursorset = [['sw-resize','s-resize','se-resize'], - ['w-resize','move','e-resize'], - ['nw-resize','n-resize','ne-resize']]; -module.exports = function getCursor(x,y,xanchor,yanchor) { - if(xanchor==='left') x=0; - else if(xanchor==='center') x=1; - else if(xanchor==='right') x=2; - else x = Lib.constrain(Math.floor(x*3),0,2); - - if(yanchor==='bottom') y=0; - else if(yanchor==='middle') y=1; - else if(yanchor==='top') y=2; - else y = Lib.constrain(Math.floor(y*3),0,2); +var cursorset = [ + ['sw-resize', 's-resize', 'se-resize'], + ['w-resize', 'move', 'e-resize'], + ['nw-resize', 'n-resize', 'ne-resize'] +]; + +module.exports = function getCursor(x, y, xanchor, yanchor) { + if(xanchor === 'left') x=0; + else if(xanchor === 'center') x=1; + else if(xanchor === 'right') x=2; + else x = Lib.constrain(Math.floor(x * 3), 0, 2); + + if(yanchor === 'bottom') y = 0; + else if(yanchor === 'middle') y = 1; + else if(yanchor === 'top') y = 2; + else y = Lib.constrain(Math.floor(y * 3), 0, 2); return cursorset[y][x]; }; diff --git a/src/components/dragelement/index.js b/src/components/dragelement/index.js index 5213ccf7c14..e9d1ce56b9c 100644 --- a/src/components/dragelement/index.js +++ b/src/components/dragelement/index.js @@ -15,6 +15,15 @@ var Lib = require('../../lib'); var constants = require('../../plots/cartesian/constants'); +var dragElement = module.exports = {}; + +dragElement.align = require('./align'); +dragElement.getCursor = require('./cursor'); + +var unhover = require('./unhover'); +dragElement.unhover = unhover.wrapped; +dragElement.unhoverRaw = unhover.raw; + /** * Abstracts click & drag interactions * @param {object} options with keys: @@ -35,15 +44,6 @@ var constants = require('../../plots/cartesian/constants'); * numClicks is how many clicks we've registered within * a doubleclick time */ -var dragElement = module.exports = {}; - -dragElement.align = require('./align'); -dragElement.getCursor = require('./cursor'); - -var unhover = require('./unhover'); -dragElement.unhover = unhover.wrapped; -dragElement.unhoverRaw = unhover.raw; - dragElement.init = function init(options) { var gd = Lib.getPlotDiv(options.element) || {}, numClicks = 1, diff --git a/src/components/dragelement/unhover.js b/src/components/dragelement/unhover.js index 90a09b4a4ba..a477501fe50 100644 --- a/src/components/dragelement/unhover.js +++ b/src/components/dragelement/unhover.js @@ -18,26 +18,32 @@ var unhover = module.exports = {}; unhover.wrapped = function(gd, evt, subplot) { if(typeof gd === 'string') gd = document.getElementById(gd); + // Important, clear any queued hovers if(gd._hoverTimer) { clearTimeout(gd._hoverTimer); gd._hoverTimer = undefined; } - unhover.raw(gd,evt,subplot); + + unhover.raw(gd, evt, subplot); }; // remove hover effects on mouse out, and emit unhover event unhover.raw = function unhoverRaw(gd, evt) { var fullLayout = gd._fullLayout; + if(!evt) evt = {}; if(evt.target && Events.triggerHandler(gd, 'plotly_beforehover', evt) === false) { return; } + fullLayout._hoverlayer.selectAll('g').remove(); + if(evt.target && gd._hoverdata) { gd.emit('plotly_unhover', {points: gd._hoverdata}); } + gd._hoverdata = undefined; }; diff --git a/src/lib/setcursor.js b/src/lib/setcursor.js index ae37d367cfd..cab1f189b73 100644 --- a/src/lib/setcursor.js +++ b/src/lib/setcursor.js @@ -14,7 +14,8 @@ // omit cursor to revert to the default. module.exports = function setCursor(el3, csr) { (el3.attr('class') || '').split(' ').forEach(function(cls) { - if(cls.indexOf('cursor-')===0) el3.classed(cls,false); + if(cls.indexOf('cursor-') === 0) el3.classed(cls, false); }); - if(csr) el3.classed('cursor-'+csr, true); + + if(csr) el3.classed('cursor-' + csr, true); }; From ffa9d42d457b5234c2f3b36f14ba32eb531d0d98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Mon, 11 Apr 2016 15:37:13 -0400 Subject: [PATCH 61/76] fix ternary domain vertical offset: - domain[0] set to 0 means to bottom of the plot area, but svg offset are computed from the top --- src/plots/ternary/ternary.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plots/ternary/ternary.js b/src/plots/ternary/ternary.js index 339286329c1..57524a196c4 100644 --- a/src/plots/ternary/ternary.js +++ b/src/plots/ternary/ternary.js @@ -203,7 +203,7 @@ proto.adjustLayout = function(ternaryLayout, graphSize) { yDomainFinal = yDomain * h / hmax; x0 = graphSize.l + graphSize.w * xDomainCenter - w / 2; - y0 = graphSize.t + graphSize.h * yDomainCenter - h / 2; + y0 = graphSize.t + graphSize.h * (1 - yDomainCenter) - h / 2; _this.x0 = x0; _this.y0 = y0; From 11e533b4b1d1ae8b7877799acfb64f3335764684 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Mon, 11 Apr 2016 15:37:40 -0400 Subject: [PATCH 62/76] update plot types mock (now with a ternary subplot) --- test/image/baselines/plot_types.png | Bin 49168 -> 58496 bytes test/image/mocks/plot_types.json | 93 +++++++++++++++++++++++----- 2 files changed, 76 insertions(+), 17 deletions(-) diff --git a/test/image/baselines/plot_types.png b/test/image/baselines/plot_types.png index dfe2fe6d4f311571625bcddbc0cc05b51829b05b..2dadfb539a25f3293f4dfa56c8ddd5cc568ed3e1 100644 GIT binary patch literal 58496 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 diff --git a/test/image/mocks/plot_types.json b/test/image/mocks/plot_types.json index 8d29b07d163..3e0be5c8f91 100644 --- a/test/image/mocks/plot_types.json +++ b/test/image/mocks/plot_types.json @@ -11,8 +11,7 @@ 1, 2 ], - "legendgroup": "group", - "uid": "d036e4" + "uid": "574a94" }, { "type": "scatter3d", @@ -31,8 +30,7 @@ 1, 2 ], - "legendgroup": "group", - "uid": "a4b919" + "uid": "cd61e2" }, { "type": "scattergeo", @@ -47,7 +45,7 @@ 0, 45 ], - "uid": "cdc474" + "uid": "d8595c" }, { "type": "pie", @@ -63,27 +61,46 @@ ], "domain": { "x": [ - 0.5, - 1 + 0.33, + 0.67 ], "y": [ 0.5, 1 ] }, - "uid": "586104" + "uid": "d7e376" + }, + { + "type": "scatterternary", + "a": [ + 2, + 1, + 1 + ], + "b": [ + 1, + 2, + 1 + ], + "c": [ + 1, + 1, + 2.12345 + ], + "uid": "609928" } ], "layout": { "xaxis": { "domain": [ 0, - 0.5 + 0.33 ], "type": "linear", "range": [ - 0.8603667136812412, - 3.139633286318759 + 0.8719530783206721, + 3.128046921679328 ], "autorange": true }, @@ -94,16 +111,30 @@ ], "type": "linear", "range": [ - 0.9078947368421053, - 2.0921052631578947 + 0.9202898550724637, + 2.079710144927536 ], "autorange": true }, + "xaxis2": { + "anchor": "y2", + "domain": [ + 0.33, + 0.67 + ] + }, + "yaxis2": { + "anchor": "x2", + "domain": [ + 0.33, + 0.67 + ] + }, "scene": { "domain": { "x": [ 0, - 0.5 + 0.33 ], "y": [ 0.5, @@ -119,8 +150,8 @@ "geo": { "domain": { "x": [ - 0.5, - 1 + 0.33, + 0.67 ], "y": [ 0, @@ -128,8 +159,36 @@ ] } }, + "ternary": { + "domain": { + "x": [ + 0.67, + 1 + ], + "y": [ + 0.5, + 1 + ] + }, + "aaxis": { + "title": "A" + }, + "baxis": { + "title": "B" + }, + "caxis": { + "title": "C" + } + }, + "margin": { + "t": 25, + "l": 25, + "r": 25, + "b": 25 + }, + "showlegend": false, "height": 450, - "width": 1000, + "width": 1731.82, "autosize": true } } From f7d9c660e311ba9c82e9f44ac331525fd6bdd06b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Mon, 11 Apr 2016 18:08:07 -0400 Subject: [PATCH 63/76] add ternary plot interaction tests --- test/image/mocks/ternary_simple.json | 2 +- test/jasmine/tests/ternary_test.js | 135 +++++++++++++++++++++++++++ 2 files changed, 136 insertions(+), 1 deletion(-) diff --git a/test/image/mocks/ternary_simple.json b/test/image/mocks/ternary_simple.json index f3f45548657..ea1d78ff2a3 100644 --- a/test/image/mocks/ternary_simple.json +++ b/test/image/mocks/ternary_simple.json @@ -46,7 +46,7 @@ "bgcolor": "#eee" }, "height": 450, - "width": 1731.82, + "width": 700, "autosize": true } } diff --git a/test/jasmine/tests/ternary_test.js b/test/jasmine/tests/ternary_test.js index c2b81425596..e765bf3e943 100644 --- a/test/jasmine/tests/ternary_test.js +++ b/test/jasmine/tests/ternary_test.js @@ -1,19 +1,30 @@ var Plotly = require('@lib'); var Lib = require('@src/lib'); +var DBLCLICKDELAY = require('@src/plots/cartesian/constants').DBLCLICKDELAY; + var d3 = require('d3'); var createGraphDiv = require('../assets/create_graph_div'); var destroyGraphDiv = require('../assets/destroy_graph_div'); +var mouseEvent = require('../assets/mouse_event'); +var customMatchers = require('../assets/custom_matchers'); describe('ternary plots', function() { 'use strict'; + beforeAll(function() { + jasmine.addMatchers(customMatchers); + }); + afterEach(destroyGraphDiv); describe('with scatterternary trace(s)', function() { var mock = require('@mocks/ternary_simple.json'); var gd; + var pointPos = [391, 219]; + var blankPos = [200, 200]; + beforeEach(function(done) { gd = createGraphDiv(); @@ -73,7 +84,99 @@ describe('ternary plots', function() { done(); }); + }); + + it('should display to hover labels', function() { + var hoverLabels; + + mouseEvent('mousemove', blankPos[0], blankPos[1]); + hoverLabels = findHoverLabels(); + expect(hoverLabels.size()).toEqual(0, 'only on data points'); + + mouseEvent('mousemove', pointPos[0], pointPos[1]); + hoverLabels = findHoverLabels(); + expect(hoverLabels.size()).toEqual(1, 'one per data point'); + + var rows = hoverLabels.selectAll('tspan'); + expect(rows[0][0].innerHTML).toEqual('Component A: 0.5', 'with correct text'); + expect(rows[0][1].innerHTML).toEqual('B: 0.25', 'with correct text'); + expect(rows[0][2].innerHTML).toEqual('Component C: 0.25', 'with correct text'); + }); + + it('should respond to hover interactions by', function() { + var hoverCnt = 0, + unhoverCnt = 0; + + var hoverData, unhoverData; + + gd.on('plotly_hover', function(eventData) { + hoverCnt++; + hoverData = eventData.points[0]; + }); + + gd.on('plotly_unhover', function(eventData) { + unhoverCnt++; + unhoverData = eventData.points[0]; + }); + + mouseEvent('mousemove', blankPos[0], blankPos[1]); + expect(hoverData).toBe(undefined, 'not firing on blank points'); + expect(unhoverData).toBe(undefined, 'not firing on blank points'); + + mouseEvent('mousemove', pointPos[0], pointPos[1]); + expect(hoverData).not.toBe(undefined, 'firing on data points'); + expect(Object.keys(hoverData)).toEqual([ + 'data', 'fullData', 'curveNumber', 'pointNumber', + 'x', 'y', 'xaxis', 'yaxis' + ], 'returning the correct event data keys'); + expect(hoverData.curveNumber).toEqual(0, 'returning the correct curve number'); + expect(hoverData.pointNumber).toEqual(0, 'returning the correct point number'); + + mouseEvent('mouseout', pointPos[0], pointPos[1]); + expect(unhoverData).not.toBe(undefined, 'firing on data points'); + expect(Object.keys(unhoverData)).toEqual([ + 'data', 'fullData', 'curveNumber', 'pointNumber', + 'x', 'y', 'xaxis', 'yaxis' + ], 'returning the correct event data keys'); + expect(unhoverData.curveNumber).toEqual(0, 'returning the correct curve number'); + expect(unhoverData.pointNumber).toEqual(0, 'returning the correct point number'); + + expect(hoverCnt).toEqual(1); + expect(unhoverCnt).toEqual(1); + }); + + it('should respond to click interactions by', function() { + var ptData; + + gd.on('plotly_click', function(eventData) { + ptData = eventData.points[0]; + }); + + click(blankPos[0], blankPos[1]); + expect(ptData).toBe(undefined, 'not firing on blank points'); + + click(pointPos[0], pointPos[1]); + expect(ptData).not.toBe(undefined, 'firing on data points'); + expect(Object.keys(ptData)).toEqual([ + 'data', 'fullData', 'curveNumber', 'pointNumber', + 'x', 'y', 'xaxis', 'yaxis' + ], 'returning the correct event data keys'); + expect(ptData.curveNumber).toEqual(0, 'returning the correct curve number'); + expect(ptData.pointNumber).toEqual(0, 'returning the correct point number'); + }); + + it('should respond zoom drag interactions', function(done) { + assertRange(gd, [0.231, 0.2, 0.11]); + + Plotly.relayout(gd, 'ternary.aaxis.min', 0.1).then(function() { + assertRange(gd, [0.1, 0.2, 0.11]); + return doubleClick(pointPos[0], pointPos[1]); + }).then(function() { + assertRange(gd, [0, 0, 0]); + + done(); + }); }); }); @@ -85,4 +188,36 @@ describe('ternary plots', function() { function countTraces(type) { return d3.selectAll('.ternary').selectAll('g.trace.' + type).size(); } + + function findHoverLabels() { + return d3.select('.hoverlayer').selectAll('g'); + } + + function click(x, y) { + mouseEvent('mousemove', x, y); + mouseEvent('mousedown', x, y); + mouseEvent('mouseup', x, y); + } + + function doubleClick(x, y) { + return new Promise(function(resolve) { + click(x, y); + + setTimeout(function() { + click(x, y); + resolve(); + }, DBLCLICKDELAY / 2); + }); + } + + function assertRange(gd, expected) { + var ternary = gd._fullLayout.ternary; + var actual = [ + ternary.aaxis.min, + ternary.baxis.min, + ternary.caxis.min + ]; + + expect(actual).toBeCloseToArray(expected); + } }); From e99a5a6d71e7dc6fa293248f9ac77f2bc62a8b55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Mon, 11 Apr 2016 21:21:41 -0400 Subject: [PATCH 64/76] add ternary defaults tests --- test/jasmine/tests/ternary_test.js | 75 ++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/test/jasmine/tests/ternary_test.js b/test/jasmine/tests/ternary_test.js index e765bf3e943..167d46c4b54 100644 --- a/test/jasmine/tests/ternary_test.js +++ b/test/jasmine/tests/ternary_test.js @@ -2,6 +2,8 @@ var Plotly = require('@lib'); var Lib = require('@src/lib'); var DBLCLICKDELAY = require('@src/plots/cartesian/constants').DBLCLICKDELAY; +var supplyLayoutDefaults = require('@src/plots/ternary/layout/defaults'); + var d3 = require('d3'); var createGraphDiv = require('../assets/create_graph_div'); var destroyGraphDiv = require('../assets/destroy_graph_div'); @@ -221,3 +223,76 @@ describe('ternary plots', function() { expect(actual).toBeCloseToArray(expected); } }); + +describe('ternary defaults', function() { + 'use strict'; + + var layoutIn, layoutOut, fullData; + + beforeEach(function() { + // if hasTernary is not at this stage, the default step is skipped + layoutOut = { + _hasTernary: true, + font: { color: 'red' } + }; + + // needs a ternary-ref in a trace in order to be detected + fullData = [{ type: 'scatterternary', subplot: 'ternary' }]; + }); + + it('should fill empty containers', function() { + layoutIn = {}; + + supplyLayoutDefaults(layoutIn, layoutOut, fullData); + expect(layoutIn).toEqual({ ternary: {} }); + expect(layoutOut.ternary.aaxis.type).toEqual('linear'); + expect(layoutOut.ternary.baxis.type).toEqual('linear'); + expect(layoutOut.ternary.caxis.type).toEqual('linear'); + }); + + it('should coerce \'min\' values to 0 and delete them for user data if they contradict', function() { + layoutIn = { + ternary: { + aaxis: { min: 1 }, + baxis: { min: 1 }, + caxis: { min: 1 }, + sum: 2 + } + }; + + supplyLayoutDefaults(layoutIn, layoutOut, fullData); + expect(layoutOut.ternary.aaxis.min).toEqual(0); + expect(layoutOut.ternary.baxis.min).toEqual(0); + expect(layoutOut.ternary.caxis.min).toEqual(0); + expect(layoutOut.ternary.sum).toEqual(2); + expect(layoutIn.ternary.aaxis.min).toBeUndefined(); + expect(layoutIn.ternary.baxis.min).toBeUndefined(); + expect(layoutIn.ternary.caxis.min).toBeUndefined(); + }); + + it('should default \'title\' to Component + _name', function() { + layoutIn = {}; + + supplyLayoutDefaults(layoutIn, layoutOut, fullData); + expect(layoutOut.ternary.aaxis.title).toEqual('Component A'); + expect(layoutOut.ternary.baxis.title).toEqual('Component B'); + expect(layoutOut.ternary.caxis.title).toEqual('Component C'); + }); + + it('should default \'gricolor\' to 60% dark', function() { + layoutIn = { + ternary: { + aaxis: { showgrid: true, color: 'red' }, + baxis: { showgrid: true }, + caxis: { gridcolor: 'black' }, + bgcolor: 'blue' + }, + paper_bgcolor: 'green' + }; + + supplyLayoutDefaults(layoutIn, layoutOut, fullData); + expect(layoutOut.ternary.aaxis.gridcolor).toEqual('rgb(102, 0, 153)'); + expect(layoutOut.ternary.baxis.gridcolor).toEqual('rgb(27, 27, 180)'); + expect(layoutOut.ternary.caxis.gridcolor).toEqual('black'); + }); +}); From eae47bb9362a569a078aadf4e7ea26a1fc6a923f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Tue, 12 Apr 2016 10:19:07 -0400 Subject: [PATCH 65/76] fix scatterternary default logic: - make sure that len is considered 0 if a is defined but not b and c. - make sure the a, b, c are defined before trying to slice them --- src/traces/scatterternary/defaults.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/traces/scatterternary/defaults.js b/src/traces/scatterternary/defaults.js index f81b7976be5..169d922a958 100644 --- a/src/traces/scatterternary/defaults.js +++ b/src/traces/scatterternary/defaults.js @@ -44,6 +44,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout if(c) len = Math.min(len, c.length); } else if(c) len = Math.min(len, c.length); + else len = 0; } else if(b && c) { len = Math.min(b.length, c.length); @@ -55,9 +56,9 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout } // cut all data arrays down to same length - if(len < a.length) traceOut.a = a.slice(0, len); - if(len < b.length) traceOut.b = b.slice(0, len); - if(len < c.length) traceOut.c = c.slice(0, len); + if(a && len < a.length) traceOut.a = a.slice(0, len); + if(b && len < b.length) traceOut.b = b.slice(0, len); + if(c && len < c.length) traceOut.c = c.slice(0, len); coerce('sum'); From 277c8f8d7cb79884d16a1ce515f74772624a42b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Tue, 12 Apr 2016 10:19:21 -0400 Subject: [PATCH 66/76] add scatterteranry tests --- test/jasmine/tests/scatterternary_test.js | 318 ++++++++++++++++++++++ 1 file changed, 318 insertions(+) create mode 100644 test/jasmine/tests/scatterternary_test.js diff --git a/test/jasmine/tests/scatterternary_test.js b/test/jasmine/tests/scatterternary_test.js new file mode 100644 index 00000000000..59a508c56f8 --- /dev/null +++ b/test/jasmine/tests/scatterternary_test.js @@ -0,0 +1,318 @@ +var Plotly = require('@lib'); +var Lib = require('@src/lib'); +var ScatterTernary = require('@src/traces/scatterternary'); + +var d3 = require('d3'); +var createGraphDiv = require('../assets/create_graph_div'); +var destroyGraphDiv = require('../assets/destroy_graph_div'); +var customMatchers = require('../assets/custom_matchers'); + + +describe('scatterternary defaults', function() { + 'use strict'; + + var supplyDefaults = ScatterTernary.supplyDefaults; + + var traceIn, traceOut; + + var defaultColor = '#444', + layout = {}; + + beforeEach(function() { + traceOut = {}; + }); + + it('should allow one of \'a\', \'b\' or \'c\' to be missing (base case)', function() { + traceIn = { + a: [1, 2, 3], + b: [1, 2, 3], + c: [1, 2, 3] + }; + + supplyDefaults(traceIn, traceOut, defaultColor, layout); + expect(traceOut.visible).not.toBe(true); + }); + + it('should allow one of \'a\', \'b\' or \'c\' to be missing (\'c\' is missing case)', function() { + traceIn = { + a: [1, 2, 3], + b: [1, 2, 3] + }; + + supplyDefaults(traceIn, traceOut, defaultColor, layout); + expect(traceOut.visible).not.toBe(true); + }); + + it('should allow one of \'a\', \'b\' or \'c\' to be missing (\'b\' is missing case)', function() { + traceIn = { + a: [1, 2, 3], + c: [1, 2, 3] + }; + + supplyDefaults(traceIn, traceOut, defaultColor, layout); + expect(traceOut.visible).not.toBe(true); + }); + + it('should allow one of \'a\', \'b\' or \'c\' to be missing (\'a\' is missing case)', function() { + traceIn = { + b: [1, 2, 3], + c: [1, 2, 3] + }; + + supplyDefaults(traceIn, traceOut, defaultColor, layout); + expect(traceOut.visible).not.toBe(true); + }); + + it('should allow one of \'a\', \'b\' or \'c\' to be missing (\'b\ and \'c\' are missing case)', function() { + traceIn = { + a: [1, 2, 3] + }; + + supplyDefaults(traceIn, traceOut, defaultColor, layout); + expect(traceOut.visible).toBe(false); + }); + + it('should allow one of \'a\', \'b\' or \'c\' to be missing (\'a\ and \'c\' are missing case)', function() { + traceIn = { + b: [1, 2, 3] + }; + + supplyDefaults(traceIn, traceOut, defaultColor, layout); + expect(traceOut.visible).toBe(false); + }); + + it('should allow one of \'a\', \'b\' or \'c\' to be missing (\'a\ and \'b\' are missing case)', function() { + traceIn = { + c: [1, 2, 3] + }; + + supplyDefaults(traceIn, traceOut, defaultColor, layout); + expect(traceOut.visible).toBe(false); + }); + + it('should allow one of \'a\', \'b\' or \'c\' to be missing (all are missing case)', function() { + traceIn = {}; + + supplyDefaults(traceIn, traceOut, defaultColor, layout); + expect(traceOut.visible).toBe(false); + }); + + it('should truncate data arrays to the same length (\'c\' is shortest case)', function() { + traceIn = { + a: [1, 2, 3], + b: [1, 2], + c: [1] + }; + + supplyDefaults(traceIn, traceOut, defaultColor, layout); + expect(traceOut.a).toEqual([1]); + expect(traceOut.b).toEqual([1]); + expect(traceOut.c).toEqual([1]); + }); + + it('should truncate data arrays to the same length (\'a\' is shortest case)', function() { + traceIn = { + a: [1], + b: [1, 2, 3], + c: [1, 2] + }; + + supplyDefaults(traceIn, traceOut, defaultColor, layout); + expect(traceOut.a).toEqual([1]); + expect(traceOut.b).toEqual([1]); + expect(traceOut.c).toEqual([1]); + }); + + it('should truncate data arrays to the same length (\'a\' is shortest case)', function() { + traceIn = { + a: [1, 2], + b: [1], + c: [1, 2, 3] + }; + + supplyDefaults(traceIn, traceOut, defaultColor, layout); + expect(traceOut.a).toEqual([1]); + expect(traceOut.b).toEqual([1]); + expect(traceOut.c).toEqual([1]); + }); + it('should include \'name\' in \'hoverinfo\' default if multi trace graph', function() { + traceIn = { + a: [1, 2, 3], + b: [1, 2, 3], + c: [1, 2, 3] + }; + layout._dataLength = 2; + + supplyDefaults(traceIn, traceOut, defaultColor, layout); + expect(traceOut.hoverinfo).toBe('all'); + }); + + it('should not include \'name\' in \'hoverinfo\' default if single trace graph', function() { + traceIn = { + a: [1, 2, 3], + b: [1, 2, 3], + c: [1, 2, 3] + }; + layout._dataLength = 1; + + supplyDefaults(traceIn, traceOut, defaultColor, layout); + expect(traceOut.hoverinfo).toBe('a+b+c+text'); + }); +}); + +describe('scatterternary calc', function() { + 'use strict'; + + var calc = ScatterTernary.calc; + + beforeAll(function() { + jasmine.addMatchers(customMatchers); + }); + + var gd, trace, cd; + + beforeEach(function() { + gd = { + _fullLayout: { + ternary: { sum: 1 } + } + }; + + trace = { + subplot: 'ternary', + sum: 1 + }; + }); + + it('should fill in missing component (case \'c\')', function() { + trace.a = [0.1, 0.3, 0.6]; + trace.b = [0.3, 0.6, 0.1]; + + calc(gd, trace); + expect(trace.c).toBeCloseToArray([0.6, 0.1, 0.3]); + }); + + it('should fill in missing component (case \'b\')', function() { + trace.a = [0.1, 0.3, 0.6]; + trace.c = [0.1, 0.3, 0.2]; + + calc(gd, trace); + expect(trace.b).toBeCloseToArray([0.8, 0.4, 0.2]); + }); + + it('should fill in missing component (case \'a\')', function() { + trace.b = [0.1, 0.3, 0.6]; + trace.c = [0.8, 0.4, 0.1]; + + calc(gd, trace); + expect(trace.a).toBeCloseToArray([0.1, 0.3, 0.3]); + }); + + it('should skip over non-numeric values', function() { + trace.a = [0.1, 'a', 0.6]; + trace.b = [0.1, 0.3, null]; + trace.c = [8, 0.4, 0.1]; + + cd = calc(gd, trace); + + expect(objectToArray(cd[0])).toBeCloseToArray([ + 0.963414634, 0.012195121, 0.012195121, 0.012195121, 0.975609756 + ]); + expect(cd[1]).toEqual({ x: false, y: false }); + expect(cd[2]).toEqual({ x: false, y: false }); + }); + + function objectToArray(obj) { + return Object.keys(obj).map(function(k) { + return obj[k]; + }); + } + +}); + +describe('scatterternary plot and hover', function() { + 'use strict'; + + var mock = require('@mocks/ternary_simple.json'); + + afterAll(destroyGraphDiv); + + beforeAll(function(done) { + var gd = createGraphDiv(); + var mockCopy = Lib.extendDeep({}, mock); + + Plotly.plot(gd, mockCopy.data, mockCopy.layout).then(done); + }); + + it('should put scatterternary trace in \'frontplot\' node', function() { + var nodes = d3.select('.frontplot').selectAll('.scatter'); + + expect(nodes.size()).toEqual(1); + }); + + it('should generate one line path per trace', function() { + var nodes = d3.selectAll('path.js-line'); + + expect(nodes.size()).toEqual(mock.data.length); + }); + + it('should generate as many points as there are data points', function() { + var nodes = d3.selectAll('path.point'); + + expect(nodes.size()).toEqual(mock.data[0].a.length); + }); +}); + +fdescribe('scatterternary hover', function() { + 'use strict'; + + var hoverPoints = ScatterTernary.hoverPoints; + + var gd, pointData; + + beforeAll(function(done) { + gd = createGraphDiv(); + + var data = [{ + type: 'scatterternary', + a: [0.1, 0.2, 0.3], + b: [0.3, 0.2, 0.1], + c: [0.1, 0.4, 0.5] + }]; + + Plotly.plot(gd, data).then(done); + }); + + beforeEach(function() { + var cd = gd.calcdata, + ternary = gd._fullLayout.ternary._ternary; + + pointData = { + index: false, + distance: 20, + cd: cd[0], + trace: cd[0][0].trace, + xa: ternary.xaxis, + ya: ternary.yaxis + }; + + }); + + afterAll(destroyGraphDiv); + + it('should generate extra text field on hover', function() { + var xval = 0.42, + yval = 0.37, + hovermode = 'closest'; + + var scatterPointData = hoverPoints(pointData, xval, yval, hovermode); + + expect(scatterPointData[0].extraText).toEqual( + 'Component A: 0.3333333
Component B: 0.1111111
Component C: 0.5555556' + ); + + expect(scatterPointData[0].xLabelVal).toBeUndefined(); + expect(scatterPointData[0].yLabelVal).toBeUndefined(); + }); + +}); From 6e38cca811ab2db0f7531484d4b66b4e7dcb5977 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Tue, 12 Apr 2016 10:21:08 -0400 Subject: [PATCH 67/76] fix typos in comments --- src/plots/plots.js | 2 +- src/traces/scatterternary/attributes.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plots/plots.js b/src/plots/plots.js index 9eba3429056..2e5a0aec57a 100644 --- a/src/plots/plots.js +++ b/src/plots/plots.js @@ -645,7 +645,7 @@ plots.supplyDataDefaults = function(traceIn, i, layout) { traceOut._module = _module; } - // gets overwritten in pie and geo + // gets overwritten in pie, geo and ternary modules if(visible) coerce('hoverinfo', (layout._dataLength === 1) ? 'x+y+z+text' : undefined); if(_module && visible) _module.supplyDefaults(traceIn, traceOut, defaultColor, layout); diff --git a/src/traces/scatterternary/attributes.js b/src/traces/scatterternary/attributes.js index 6d904e95477..3a032831dfa 100644 --- a/src/traces/scatterternary/attributes.js +++ b/src/traces/scatterternary/attributes.js @@ -67,7 +67,7 @@ module.exports = { 'Sets text elements associated with each (a,b,c) point.', 'If a single string, the same string appears over', 'all the data points.', - 'If an array of string, the items are mapped in order to the', + 'If an array of strings, the items are mapped in order to the', 'the data points in (a,b,c).' ].join(' ') }), From 6cefff0bc2ee819ae7640d857e5964c5c38c3f2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Tue, 12 Apr 2016 10:34:10 -0400 Subject: [PATCH 68/76] sub fdescribe -> describe --- test/jasmine/tests/scatterternary_test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/jasmine/tests/scatterternary_test.js b/test/jasmine/tests/scatterternary_test.js index 59a508c56f8..e2d6c293085 100644 --- a/test/jasmine/tests/scatterternary_test.js +++ b/test/jasmine/tests/scatterternary_test.js @@ -263,7 +263,7 @@ describe('scatterternary plot and hover', function() { }); }); -fdescribe('scatterternary hover', function() { +describe('scatterternary hover', function() { 'use strict'; var hoverPoints = ScatterTernary.hoverPoints; From 6465c6c6a3ff7e8af1dd82abc0401132f08b0649 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Tue, 12 Apr 2016 14:21:51 -0400 Subject: [PATCH 69/76] add tick label and tick value attributes/default to ternary axes --- src/plots/ternary/layout/axis_attributes.js | 11 +++++++++++ src/plots/ternary/layout/axis_defaults.js | 10 +++++++--- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/plots/ternary/layout/axis_attributes.js b/src/plots/ternary/layout/axis_attributes.js index 92c8f95db74..42b00778682 100644 --- a/src/plots/ternary/layout/axis_attributes.js +++ b/src/plots/ternary/layout/axis_attributes.js @@ -18,12 +18,23 @@ module.exports = { titlefont: axesAttrs.titlefont, color: axesAttrs.color, // ticks + tickmode: axesAttrs.tickmode, nticks: extendFlat({}, axesAttrs.nticks, {dflt: 6, min: 1}), + tick0: axesAttrs.tick0, + dtick: axesAttrs.dtick, + tickvals: axesAttrs.tickvals, + ticktext: axesAttrs.ticktext, ticks: axesAttrs.ticks, ticklen: axesAttrs.ticklen, tickwidth: axesAttrs.tickwidth, tickcolor: axesAttrs.tickcolor, showticklabels: axesAttrs.showticklabels, + showtickprefix: axesAttrs.showtickprefix, + tickprefix: axesAttrs.tickprefix, + showticksuffix: axesAttrs.showticksuffix, + ticksuffix: axesAttrs.ticksuffix, + showexponent: axesAttrs.showexponent, + exponentformat: axesAttrs.exponentformat, tickfont: axesAttrs.tickfont, tickangle: axesAttrs.tickangle, tickformat: axesAttrs.tickformat, diff --git a/src/plots/ternary/layout/axis_defaults.js b/src/plots/ternary/layout/axis_defaults.js index 43ec3bf068f..bc48a9b1a05 100644 --- a/src/plots/ternary/layout/axis_defaults.js +++ b/src/plots/ternary/layout/axis_defaults.js @@ -13,7 +13,10 @@ var colorMix = require('tinycolor2').mix; var Lib = require('../../../lib'); var layoutAttributes = require('./axis_attributes'); +var handleTickLabelDefaults = require('../../cartesian/tick_label_defaults'); var handleTickMarkDefaults = require('../../cartesian/tick_mark_defaults'); +var handleTickValueDefaults = require('../../cartesian/tick_value_defaults'); + module.exports = function supplyLayoutDefaults(containerIn, containerOut, options) { @@ -48,10 +51,11 @@ module.exports = function supplyLayoutDefaults(containerIn, containerOut, option // range is just set by 'min' - max is determined by the other axes mins coerce('min'); - coerce('nticks'); - + handleTickValueDefaults(containerIn, containerOut, coerce, 'linear'); + handleTickLabelDefaults(containerIn, containerOut, coerce, 'linear', + { noHover: false }); handleTickMarkDefaults(containerIn, containerOut, coerce, 'linear', - {outerticks: false}); + { outerticks: false }); // TODO - below is a bit repetitious from cartesian still... From e033d08382e9ec53112a4a2ed6a592506af73d05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Tue, 12 Apr 2016 14:34:20 -0400 Subject: [PATCH 70/76] add three ternary image tests --- test/image/baselines/ternary_array_styles.png | Bin 0 -> 38970 bytes test/image/baselines/ternary_lines.png | Bin 0 -> 38310 bytes test/image/baselines/ternary_markers.png | Bin 0 -> 44884 bytes test/image/mocks/ternary_array_styles.json | 116 +++++ test/image/mocks/ternary_lines.json | 399 ++++++++++++++++++ test/image/mocks/ternary_markers.json | 121 ++++++ 6 files changed, 636 insertions(+) create mode 100644 test/image/baselines/ternary_array_styles.png create mode 100644 test/image/baselines/ternary_lines.png create mode 100644 test/image/baselines/ternary_markers.png create mode 100644 test/image/mocks/ternary_array_styles.json create mode 100644 test/image/mocks/ternary_lines.json create mode 100644 test/image/mocks/ternary_markers.json diff --git a/test/image/baselines/ternary_array_styles.png b/test/image/baselines/ternary_array_styles.png new file mode 100644 index 0000000000000000000000000000000000000000..f58c8741dd499655ba91f1fc75b08a95462464b6 GIT binary patch literal 38970 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 #5!D$@jCI8JT)Gbm^QfH4bn0D|f<={kCt$ESy`3 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 P;LmMUZIyB*%aH#A6P2MN literal 0 HcmV?d00001 diff --git a/test/image/baselines/ternary_lines.png b/test/image/baselines/ternary_lines.png new file mode 100644 index 0000000000000000000000000000000000000000..9cf209330311e3466c12d315c7f8bc1334ec4c8f GIT binary patch literal 38310 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 literal 0 HcmV?d00001 diff --git a/test/image/baselines/ternary_markers.png b/test/image/baselines/ternary_markers.png new file mode 100644 index 0000000000000000000000000000000000000000..f131b3266f58e6ac6c0476c1bc5704f99f7bc7bc GIT binary patch literal 44884 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 literal 0 HcmV?d00001 diff --git a/test/image/mocks/ternary_array_styles.json b/test/image/mocks/ternary_array_styles.json new file mode 100644 index 00000000000..c127c4c3b22 --- /dev/null +++ b/test/image/mocks/ternary_array_styles.json @@ -0,0 +1,116 @@ +{ + "data": [ + { + "type": "scatterternary", + "a": [ + 0.2, + 0.3, + null, + 0.1 + ], + "b": [ + 0.1, + 0.2, + null, + 0.4 + ], + "c": [ + 0.1, + 0.2, + null, + 0.1 + ], + "marker": { + "symbol": [ + "square", + "cross", + null, + "diamond" + ], + "size": [ + 10, + 20, + null, + 30 + ], + "color": [ + 20, + 30, + null, + 10 + ], + "showscale": true, + "line": { + "width": [ + 3, + 5, + null, + 2 + ], + "color": [ + 10, + null, + 30, + 20 + ], + "colorscale": "Greens", + "cmin": 10, + "cmax": 30 + }, + "cmin": 10, + "cmax": 30, + "colorscale": [ + [ + 0, + "rgb(220,220,220)" + ], + [ + 0.2, + "rgb(245,195,157)" + ], + [ + 0.4, + "rgb(245,160,105)" + ], + [ + 1, + "rgb(178,10,28)" + ] + ] + }, + "line": { + "dash": "dash", + "shape": "spline", + "width": 3 + }, + "connectgaps": true, + "uid": "35ff50" + } + ], + "layout": { + "ternary": { + "aaxis": { + "showline": true, + "ticks": "inside", + "showgrid": true, + "color": "#ccc" + }, + "baxis": { + "showline": true, + "ticks": "inside", + "showgrid": true, + "color": "#0f0", + "title": "chocolate", + "min": 0.2 + }, + "caxis": { + "showline": true, + "ticks": "inside", + "showgrid": true + } + }, + "height": 450, + "width": 700, + "autosize": false + } +} diff --git a/test/image/mocks/ternary_lines.json b/test/image/mocks/ternary_lines.json new file mode 100644 index 00000000000..bae46288fa1 --- /dev/null +++ b/test/image/mocks/ternary_lines.json @@ -0,0 +1,399 @@ +{ + "data": [ + { + "type": "scatterternary", + "mode": "lines", + "a": [ + 0, + 10, + 0 + ], + "b": [ + 100, + 90, + 90 + ], + "c": [ + 0, + 0, + 10 + ], + "line": { + "color": "#c00" + }, + "uid": "4a9e8d" + }, + { + "type": "scatterternary", + "mode": "lines", + "a": [ + 0, + 10, + 15, + 0 + ], + "b": [ + 90, + 90, + 85, + 70 + ], + "c": [ + 10, + 0, + 0, + 30 + ], + "line": { + "color": "#c00" + }, + "uid": "6b700b" + }, + { + "type": "scatterternary", + "mode": "lines", + "a": [ + 0, + 15, + 20, + 20, + 5, + 5, + 0 + ], + "b": [ + 70, + 85, + 80, + 53, + 53, + 45, + 50 + ], + "c": [ + 30, + 0, + 0, + 32, + 42, + 50, + 50 + ], + "line": { + "color": "#c00" + }, + "uid": "0e280c" + }, + { + "type": "scatterternary", + "mode": "lines", + "a": [ + 20, + 35, + 35, + 28, + 20 + ], + "b": [ + 80, + 65, + 45, + 45, + 53 + ], + "c": [ + 0, + 0, + 20, + 27, + 32 + ], + "line": { + "color": "#c00" + }, + "uid": "40fccc" + }, + { + "type": "scatterternary", + "mode": "lines", + "a": [ + 35, + 35, + 55 + ], + "b": [ + 65, + 45, + 45 + ], + "c": [ + 0, + 20, + 0 + ], + "line": { + "color": "#c00" + }, + "uid": "d990f2" + }, + { + "type": "scatterternary", + "mode": "lines", + "a": [ + 55, + 100, + 60, + 40, + 40 + ], + "b": [ + 45, + 0, + 0, + 20, + 45 + ], + "c": [ + 0, + 0, + 40, + 40, + 15 + ], + "line": { + "color": "#c00" + }, + "uid": "b6c367" + }, + { + "type": "scatterternary", + "mode": "lines", + "a": [ + 40, + 40, + 28, + 28 + ], + "b": [ + 45, + 20, + 20, + 45 + ], + "c": [ + 15, + 40, + 52, + 27 + ], + "line": { + "color": "#c00" + }, + "uid": "90d4e2" + }, + { + "type": "scatterternary", + "mode": "lines", + "a": [ + 60, + 40, + 40 + ], + "b": [ + 0, + 0, + 20 + ], + "c": [ + 40, + 60, + 40 + ], + "line": { + "color": "#c00" + }, + "uid": "c483e4" + }, + { + "type": "scatterternary", + "mode": "lines", + "a": [ + 28, + 28, + 40, + 40 + ], + "b": [ + 0, + 20, + 20, + 0 + ], + "c": [ + 72, + 52, + 40, + 60 + ], + "line": { + "color": "#c00" + }, + "uid": "9b3fa9" + }, + { + "type": "scatterternary", + "mode": "lines", + "a": [ + 0, + 28, + 28, + 12, + 12, + 0 + ], + "b": [ + 50, + 22, + 0, + 0, + 8, + 20 + ], + "c": [ + 50, + 50, + 72, + 88, + 80, + 80 + ], + "line": { + "color": "#c00" + }, + "uid": "5c8c5a" + }, + { + "type": "scatterternary", + "mode": "lines", + "a": [ + 0, + 0, + 12, + 12 + ], + "b": [ + 0, + 20, + 8, + 0 + ], + "c": [ + 100, + 80, + 80, + 88 + ], + "line": { + "color": "#c00" + }, + "uid": "845650" + }, + { + "type": "scatterternary", + "mode": "lines", + "a": [ + 28, + 28, + 5, + 5, + 20 + ], + "b": [ + 45, + 22, + 45, + 53, + 53 + ], + "c": [ + 27, + 50, + 50, + 42, + 32 + ], + "line": { + "color": "#c00" + }, + "uid": "ca74b0" + } + ], + "layout": { + "ternary": { + "aaxis": { + "title": "Clay", + "min": 0.01, + "linewidth": 2, + "ticks": "outside", + "ticklen": 8, + "showgrid": true, + "tickvals": [ + 0.2, + 0.4, + 0.6, + 0.8 + ], + "ticktext": [ + "20%", + "40%", + "60%", + "80%" + ] + }, + "baxis": { + "title": "Sand", + "min": 0.01, + "linewidth": 2, + "ticks": "outside", + "ticklen": 8, + "showgrid": true, + "tickvals": [ + 0.2, + 0.4, + 0.6, + 0.8 + ], + "ticktext": [ + "20%", + "40%", + "60%", + "80%" + ] + }, + "caxis": { + "title": "Silt", + "min": 0.01, + "linewidth": 2, + "ticks": "outside", + "ticklen": 8, + "showgrid": true, + "tickvals": [ + 0.2, + 0.4, + 0.6, + 0.8 + ], + "ticktext": [ + "20%", + "40%", + "60%", + "80%" + ] + } + }, + "showlegend": false, + "width": 700, + "height": 450, + "autosize": false + } +} diff --git a/test/image/mocks/ternary_markers.json b/test/image/mocks/ternary_markers.json new file mode 100644 index 00000000000..12e33c13448 --- /dev/null +++ b/test/image/mocks/ternary_markers.json @@ -0,0 +1,121 @@ +{ + "data": [ + { + "type": "scatterternary", + "mode": "markers", + "a": [ + 75, + 70, + 75, + 5, + 10, + 10, + 20, + 10, + 15, + 10, + 20 + ], + "b": [ + 25, + 10, + 20, + 60, + 80, + 90, + 70, + 20, + 5, + 10, + 10 + ], + "c": [ + 0, + 20, + 5, + 35, + 10, + 0, + 10, + 70, + 80, + 80, + 70 + ], + "text": [ + "point 1", + "point 2", + "point 3", + "point 4", + "point 5", + "point 6", + "point 7", + "point 8", + "point 9", + "point 10", + "point 11" + ], + "marker": { + "symbol": 100, + "color": "#DB7365", + "size": 14, + "line": { + "width": 2 + } + }, + "uid": "de1eca" + } + ], + "layout": { + "ternary": { + "sum": 100, + "aaxis": { + "titlefont": { + "size": 20 + }, + "tickfont": { + "size": 15 + }, + "tickcolor": "rgba(0, 0, 0, 0)", + "ticklen": 5, + "showline": true, + "showgrid": true, + "title": "Journalist", + "tickangle": 0 + }, + "baxis": { + "titlefont": { + "size": 20 + }, + "tickfont": { + "size": 15 + }, + "tickcolor": "rgba(0, 0, 0, 0)", + "ticklen": 5, + "showline": true, + "showgrid": true, + "title": "
Developer", + "tickangle": 45 + }, + "caxis": { + "titlefont": { + "size": 20 + }, + "tickfont": { + "size": 15 + }, + "tickcolor": "rgba(0, 0, 0, 0)", + "ticklen": 5, + "showline": true, + "showgrid": true, + "title": "
Designer", + "tickangle": -45 + }, + "bgcolor": "#fff1e0" + }, + "paper_bgcolor": "#fff1e0", + "height": 450, + "width": 700, + "autosize": false + } +} From a04365552673a243054e18d8c3602468f5b5d383 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Tue, 12 Apr 2016 14:34:55 -0400 Subject: [PATCH 71/76] set width to 1000 --- test/image/mocks/plot_types.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/image/mocks/plot_types.json b/test/image/mocks/plot_types.json index 3e0be5c8f91..8ac1cd0abab 100644 --- a/test/image/mocks/plot_types.json +++ b/test/image/mocks/plot_types.json @@ -188,7 +188,7 @@ }, "showlegend": false, "height": 450, - "width": 1731.82, + "width": 1000, "autosize": true } } From 9d3776e62440f24051de98c6f73274c5a895d2a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Tue, 12 Apr 2016 14:45:14 -0400 Subject: [PATCH 72/76] remove 'fill' and 'fillcolor' (for now): - scatterternary fill will be part of the next iteration --- src/traces/scatterternary/attributes.js | 5 ----- src/traces/scatterternary/defaults.js | 10 +++------- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/src/traces/scatterternary/attributes.js b/src/traces/scatterternary/attributes.js index 3a032831dfa..697b86fcb92 100644 --- a/src/traces/scatterternary/attributes.js +++ b/src/traces/scatterternary/attributes.js @@ -80,11 +80,6 @@ module.exports = { smoothing: scatterLineAttrs.smoothing }, connectgaps: scatterAttrs.connectgaps, - fill: extendFlat({}, scatterAttrs.fill, { - values: ['none', 'tozeroa', 'tozerob', 'tozeroc', - 'tonexta', 'tonextb', 'tonextc'] - }), - fillcolor: scatterAttrs.fillcolor, marker: { symbol: scatterMarkerAttrs.symbol, opacity: scatterMarkerAttrs.opacity, diff --git a/src/traces/scatterternary/defaults.js b/src/traces/scatterternary/defaults.js index 169d922a958..8094cb3fa7e 100644 --- a/src/traces/scatterternary/defaults.js +++ b/src/traces/scatterternary/defaults.js @@ -17,7 +17,6 @@ var handleMarkerDefaults = require('../scatter/marker_defaults'); var handleLineDefaults = require('../scatter/line_defaults'); var handleLineShapeDefaults = require('../scatter/line_shape_defaults'); var handleTextDefaults = require('../scatter/text_defaults'); -var handleFillColorDefaults = require('../scatter/fillcolor_defaults'); var attributes = require('./attributes'); @@ -85,11 +84,8 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout coerce('marker.maxdisplayed'); } - coerce('fill'); - if(traceOut.fill !== 'none') { - handleFillColorDefaults(traceIn, traceOut, defaultColor, coerce); - if(!subTypes.hasLines(traceOut)) handleLineShapeDefaults(traceIn, traceOut, coerce); - } - coerce('hoverinfo', (layout._dataLength === 1) ? 'a+b+c+text' : undefined); + + // until 'fill' and 'fillcolor' are supported + traceOut.fill = 'none'; }; From ab22ef957d2a58e0ae2ac7bedcc3f6c7f37cb2db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Tue, 12 Apr 2016 14:58:39 -0400 Subject: [PATCH 73/76] fix name hover label fill color: - add fall back if plot_bgcolor isn't defined --- src/plots/cartesian/graph_interact.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/plots/cartesian/graph_interact.js b/src/plots/cartesian/graph_interact.js index 1175bc476b5..5a4d70701f1 100644 --- a/src/plots/cartesian/graph_interact.js +++ b/src/plots/cartesian/graph_interact.js @@ -524,10 +524,15 @@ function hover(gd, evt, subplot) { hoverData.sort(function(d1, d2) { return d1.distance - d2.distance; }); + var bgColor = Plotly.Color.combine( + fullLayout.plot_bgcolor || Plotly.Color.background, + fullLayout.paper_bgcolor + ); + var labelOpts = { hovermode: hovermode, rotateLabels: rotateLabels, - bgColor: Plotly.Color.combine(fullLayout.plot_bgcolor, fullLayout.paper_bgcolor), + bgColor: bgColor, container: fullLayout._hoverlayer, outerContainer: fullLayout._paperdiv }; From c3ab79f3a4028f54813d45e8b76171fa4f76ddf9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Tue, 12 Apr 2016 15:06:54 -0400 Subject: [PATCH 74/76] :camel: eats :snake: --- src/plots/ternary/ternary.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/plots/ternary/ternary.js b/src/plots/ternary/ternary.js index 57524a196c4..28f0518722c 100644 --- a/src/plots/ternary/ternary.js +++ b/src/plots/ternary/ternary.js @@ -169,7 +169,7 @@ proto.makeFramework = function() { _this.plotContainer.selectAll('.backplot,.frontplot,.grids') .call(Drawing.setClipUrl, clipId); - _this.init_interactions(); + _this.initInteractions(); }; var w_over_h = Math.sqrt(4/3); @@ -420,7 +420,7 @@ var STARTMARKER = 'm0.5,0.5h5v-2h-5v-5h-2v5h-5v2h5v5h2Z'; // I guess this could be shared with cartesian... but for now it's separate. var SHOWZOOMOUTTIP = true; -proto.init_interactions = function() { +proto.initInteractions = function() { var _this = this, dragger = _this.layers.plotbg.select('path').node(), gd = _this.graphDiv, @@ -434,7 +434,7 @@ proto.init_interactions = function() { doubleclick: doubleClick, subplot: _this.id, prepFn: function(e, startX, startY) { - // these aren't available yet when init_interactions + // these aren't available yet when initInteractions // is called dragOptions.xaxes = [_this.xaxis]; dragOptions.yaxes = [_this.yaxis]; From 6f8d4685b63b2abf2ad0f8437d375ffda503f7af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Tue, 12 Apr 2016 15:23:49 -0400 Subject: [PATCH 75/76] add multiple ternary image test --- test/image/baselines/ternary_multiple.png | Bin 0 -> 34571 bytes test/image/mocks/ternary_multiple.json | 95 ++++++++++++++++++++++ 2 files changed, 95 insertions(+) create mode 100644 test/image/baselines/ternary_multiple.png create mode 100644 test/image/mocks/ternary_multiple.json diff --git a/test/image/baselines/ternary_multiple.png b/test/image/baselines/ternary_multiple.png new file mode 100644 index 0000000000000000000000000000000000000000..bb6d078a5aba1e36032999919ac1019ce3bb7946 GIT binary patch literal 34571 zcmeFZcQ{;K_%@0RMvLCtXwivIh~7e?Mf4iIB#4NKZj^{lgb+fML?=X|j50(bYKUHk z5D~->y`D8G@B99)bG|>$cb)U!cU>db$k=<rPuoRdbpKQJre)9K5jcVJQ`&B ztc^(GzeCavLu9!8XNcNuLl8c=aC5U2%fEAwjDs&a@OMJ6V+$)HGQd~_1UAV2Gb`W? zvCXsp3=xL=erMxp5;A~r}q-E^T8wFl=v=E(bVo7XrR zN?Z zfVT8|G-Y{S1@FDSI zZ>Gc&_(_%Zxk>vzH)!i&U+hGswS0+5MV99*(){*x{c|O+$=4;*&4H$)6_&jExr&cp zyM5M4V!O~j($>QR1`a0Wouh?aI%;+^<-p#0myg0_L@_4iuAx#W59uyC*Qn-|Ku&ZSdeJ=uOhol zAuzkCH@5_P(uE(<@Eggpe%)w2OTTa}OFNEEc(HkJ_L0X}<;SiRuAPCM?<2Jj#|X*U zy6SztaNj637P!el7e_02;pA}V`YYLxWBC2!{a4qZsu((cBlS)R^^0GJ>O4#tTHpNasmV zQ>~$)v|VQG>KHJGi$g`acs(K(8?((t@XZAgVdOc&^J(SgwK`G6w8m>48r)Y>xmzYL z?dtx?iMVQE2pPLK-C5Kpr$-x3--knj%li=z^upjZ||0PMl=rSWcD;b-D*=*3yYkrm&h5Ed&Tx1HmCB_R$SHY-a9G+>)hn^z)$@cOy zDUoK>kjoIo$!Id>ZF{@jwTVGw2g|cRAR<2(2j6RZL&abKhwhWU(uuK2g$3=8Rez{< zJ&7zeQa3~7aT@>iQsqcX<%t?6HKN(hIC@<$_d72A5U-Ka8zV(1*Rzw2&>NAem;mw0 z0nj=+wb8Fyj<fjQ836 zi@N~ap{Rg7QwpuDTCjK0z+}}DtS*D4TS;eHfGWaOEc3=!{%MT)+LIw_Ou-=3w)y45 z+Xl&cdGMW=1Gb9~NjvbK@)_n|?2+Jt&vW9k+ts_udO)MkkwD&0y!4I`^#)t72yE2B z#t$snf3N;2xca*rI3G+yM=7~9ivv-F>_K^)WVjAc0zu>AWLOXGJMW)g`_%9&9*bC8 zbl!1A{~t9)9vO^;bSx^ z0&g$!5wCvfnedQ5>hkj&WhR#fufNp$xV`du=Zqq$OFgWYOw8eZd>UHAZtV2rNGgF| zoC!9_CQBlJEqYVM3Px)C7Ko2 zPkG_JSyH;6pUAzDzBO4aDq-~I)gMT!Tn(@)*=Wvk;|OQ$YiFnKG!C+VFe3>TNn^g= zaigU6-ag_H{pSM@a8TXXmOD2tKn*ZiVefSRpyoC_EI6%xXoYYAZ!Uj%-}L7L*eCFZ z1e1wAEq@mScF*6Y%tQN;(2HL94=jaB0uUVj#vxnuKlnDx5}a>^$gxs8 zC4Wd{735>h=Fz2FnQ3}ksGY*8^Yw$uKb1f)mDZgxfR@wP*+tkI17xw;k^R7%O^>)`dv}!loF+x7?re z$YtPJNwS~w{AG{?ujVRRg8*Jwi{PohJ<}-e|HJCalZ*B=eQ6#4)EZAXK*7-85Q)o_TZYsm0wU|Bc%YpI

t$cbOu?Rv6-+iCKKl6;P0yB5w=OSC>%1e=g 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 Yj{aMe_M^NH1n}pwlA2=HMYF*F1Cr-#761SM literal 0 HcmV?d00001 diff --git a/test/image/mocks/ternary_multiple.json b/test/image/mocks/ternary_multiple.json new file mode 100644 index 00000000000..b41308d655f --- /dev/null +++ b/test/image/mocks/ternary_multiple.json @@ -0,0 +1,95 @@ +{ + "data": [ + { + "type": "scatterternary", + "mode": "markers", + "a": [ + 0.1, + 0.2, + 0.3 + ], + "b": [ + 0.3, + 0.2, + 0.1 + ], + "name": "c missing", + "uid": "757d30" + }, + { + "type": "scatterternary", + "mode": "markers", + "a": [ + 0.1, + 0.2, + 0.3 + ], + "c": [ + 0.3, + 0.2, + 0.1 + ], + "subplot": "ternary2", + "name": "b missing", + "uid": "6c0753" + }, + { + "type": "scatterternary", + "mode": "markers", + "b": [ + 0.1, + 0.2, + 0.3 + ], + "c": [ + 0.3, + 0.2, + 0.1 + ], + "subplot": "ternary3", + "name": "a missing", + "uid": "12520d" + } + ], + "layout": { + "ternary": { + "domain": { + "x": [ + 0, + 0.5 + ], + "y": [ + 0, + 0.4 + ] + } + }, + "ternary2": { + "domain": { + "x": [ + 0, + 0.5 + ], + "y": [ + 0.6, + 1 + ] + } + }, + "ternary3": { + "domain": { + "x": [ + 0.5, + 1 + ], + "y": [ + 0.25, + 0.75 + ] + } + }, + "height": 450, + "width": 700, + "autosize": false + } +} From fce4d68229f6b97817b25987c7ab78b943353c81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Tue, 12 Apr 2016 17:05:42 -0400 Subject: [PATCH 76/76] bring back Fx.unhover (for backward compatible) --- src/plots/cartesian/graph_interact.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/plots/cartesian/graph_interact.js b/src/plots/cartesian/graph_interact.js index 5a4d70701f1..8ff557f7674 100644 --- a/src/plots/cartesian/graph_interact.js +++ b/src/plots/cartesian/graph_interact.js @@ -23,6 +23,10 @@ var dragElement = require('../../components/dragelement'); var fx = module.exports = {}; +// TODO remove this in version 2.0 +// copy on Fx for backward compatible +fx.unhover = dragElement.unhover; + fx.layoutAttributes = { dragmode: { valType: 'enumerated',