diff --git a/src/plots/ternary/layout/axis_attributes.js b/src/plots/ternary/layout/axis_attributes.js index 05d34dfef19..fa35570ecb3 100644 --- a/src/plots/ternary/layout/axis_attributes.js +++ b/src/plots/ternary/layout/axis_attributes.js @@ -47,6 +47,7 @@ module.exports = { showgrid: extendFlat({}, axesAttrs.showgrid, {dflt: true}), gridcolor: axesAttrs.gridcolor, gridwidth: axesAttrs.gridwidth, + layer: axesAttrs.layer, // range min: { valType: 'number', diff --git a/src/plots/ternary/layout/axis_defaults.js b/src/plots/ternary/layout/axis_defaults.js index 0ed502a839e..0a02d2260b5 100644 --- a/src/plots/ternary/layout/axis_defaults.js +++ b/src/plots/ternary/layout/axis_defaults.js @@ -79,4 +79,6 @@ module.exports = function supplyLayoutDefaults(containerIn, containerOut, option coerce('gridcolor', colorMix(dfltColor, options.bgColor, 60).toRgbString()); coerce('gridwidth'); } + + coerce('layer'); }; diff --git a/src/plots/ternary/ternary.js b/src/plots/ternary/ternary.js index c5840d5e9ec..719b89168a7 100644 --- a/src/plots/ternary/ternary.js +++ b/src/plots/ternary/ternary.js @@ -31,7 +31,7 @@ function Ternary(options, fullLayout) { this.id = options.id; this.graphDiv = options.graphDiv; this.init(fullLayout); - this.makeFramework(); + this.makeFramework(fullLayout); } module.exports = Ternary; @@ -43,6 +43,7 @@ proto.init = function(fullLayout) { this.defs = fullLayout._defs; this.layoutId = fullLayout._uid; this.traceHash = {}; + this.layers = {}; }; proto.plot = function(ternaryCalcData, fullLayout) { @@ -60,15 +61,15 @@ proto.plot = function(ternaryCalcData, fullLayout) { } } + _this.updateLayers(ternaryLayout); _this.adjustLayout(ternaryLayout, graphSize); - Plots.generalUpdatePerTraceModule(_this, ternaryCalcData, ternaryLayout); - _this.layers.plotbg.select('path').call(Color.fill, ternaryLayout.bgcolor); }; -proto.makeFramework = function() { +proto.makeFramework = function(fullLayout) { var _this = this; + var ternaryLayout = fullLayout[_this.id]; var defGroup = _this.defs.selectAll('g.clips') .data([0]); @@ -95,50 +96,75 @@ proto.makeFramework = function() { _this.plotContainer.enter().append('g') .classed(_this.id, true); - _this.layers = {}; + _this.updateLayers(ternaryLayout); + + Drawing.setClipUrl(_this.layers.backplot, clipId); + Drawing.setClipUrl(_this.layers.grids, clipId); +}; + +proto.updateLayers = function(ternaryLayout) { + var _this = this; + var layers = _this.layers; // 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', - 'frontplot', - 'aaxis', 'baxis', 'caxis', 'axlines' - ]; + + var plotLayers = ['draglayer', 'plotbg', 'backplot', 'grids']; + + if(ternaryLayout.aaxis.layer === 'below traces') { + plotLayers.push('aaxis', 'aline'); + } + if(ternaryLayout.baxis.layer === 'below traces') { + plotLayers.push('baxis', 'bline'); + } + if(ternaryLayout.caxis.layer === 'below traces') { + plotLayers.push('caxis', 'cline'); + } + + plotLayers.push('frontplot'); + + if(ternaryLayout.aaxis.layer === 'above traces') { + plotLayers.push('aaxis', 'aline'); + } + if(ternaryLayout.baxis.layer === 'above traces') { + plotLayers.push('baxis', 'bline'); + } + if(ternaryLayout.caxis.layer === 'above traces') { + plotLayers.push('caxis', 'cline'); + } + var toplevel = _this.plotContainer.selectAll('g.toplevel') - .data(plotLayers); + .data(plotLayers, String); + + var grids = ['agrid', 'bgrid', 'cgrid']; + toplevel.enter().append('g') .attr('class', function(d) { return 'toplevel ' + d; }) .each(function(d) { var s = d3.select(this); - _this.layers[d] = s; + 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); - }); + 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 === 'aline' || d === 'bline' || d === 'cline') { + s.append('path'); + } else if(d === 'grids') { + grids.forEach(function(d) { + layers[d] = s.append('g').classed('grid ' + 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,.grids') - .call(Drawing.setClipUrl, clipId); + toplevel.order(); }; var w_over_h = Math.sqrt(4 / 3); @@ -315,18 +341,17 @@ proto.adjustLayout = function(ternaryLayout, graphSize) { // make these counterproductive. _this.plotContainer.selectAll('.crisp').classed('crisp', false); - var axlines = _this.layers.axlines; - axlines.select('.aline') + _this.layers.aline.select('path') .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') + _this.layers.bline.select('path') .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') + _this.layers.cline.select('path') .attr('d', caxis.showline ? 'M' + (x0 + w / 2) + ',' + y0 + 'l' + (w / 2) + ',' + h : 'M0,0') .call(Color.stroke, caxis.linecolor || '#000') @@ -336,8 +361,10 @@ proto.adjustLayout = function(ternaryLayout, graphSize) { _this.initInteractions(); } - _this.plotContainer.select('.frontplot') - .call(Drawing.setClipUrl, _this._hasClipOnAxisFalse ? null : _this.clipId); + Drawing.setClipUrl( + _this.layers.frontplot, + _this._hasClipOnAxisFalse ? null : _this.clipId + ); }; proto.drawAxes = function(doTitles) { diff --git a/test/image/baselines/ternary_axis_layers.png b/test/image/baselines/ternary_axis_layers.png new file mode 100644 index 00000000000..e2e02dbf1b1 Binary files /dev/null and b/test/image/baselines/ternary_axis_layers.png differ diff --git a/test/image/mocks/ternary_axis_layers.json b/test/image/mocks/ternary_axis_layers.json new file mode 100644 index 00000000000..a13e0ddd591 --- /dev/null +++ b/test/image/mocks/ternary_axis_layers.json @@ -0,0 +1,59 @@ +{ + "data": [ + { + "type": "scatterternary", + "mode": "markers", + "a": [70, 75, 5, 10], + "b": [10, 20, 60, 80], + "c": [20, 5, 35, 10], + "name": "not clipped", + "marker": { + "symbol": "square", + "size": 40, + "line": { "width": 4 } + }, + "cliponaxis": false + }, + { + "type": "scatterternary", + "mode": "markers", + "name": "clipped", + "a": [10, 20, 10, 15, 10, 20], + "b": [90, 70, 20, 5, 10, 10], + "c": [0, 10, 70, 80, 80, 70], + "marker": { + "symbol": "diamond", + "size": 30, + "line": { "width": 4 } + }, + "cliponaxis": true + } + ], + "layout": { + "ternary": { + "sum": 100, + "aaxis": { + "title": "", + "layer": "below traces", + "linewidth": 4, + "min": 4.5 + }, + "baxis": { + "title": "", + "layer": "below traces", + "linewidth": 4, + "min": 7.5 + }, + "caxis": { + "title": "", + "layer": "below traces", + "linewidth": 4, + "min": 7.5 + } + }, + "title": "ternary axes below traces", + "showlegend": false, + "height": 450, + "width": 500 + } +} diff --git a/test/jasmine/tests/ternary_test.js b/test/jasmine/tests/ternary_test.js index ade4abf3d2c..5140fdf40ab 100644 --- a/test/jasmine/tests/ternary_test.js +++ b/test/jasmine/tests/ternary_test.js @@ -6,13 +6,13 @@ 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'); +var fail = require('../assets/fail_test'); var mouseEvent = require('../assets/mouse_event'); var click = require('../assets/click'); var doubleClick = require('../assets/double_click'); var customMatchers = require('../assets/custom_matchers'); var getClientPosition = require('../assets/get_client_position'); - describe('ternary plots', function() { 'use strict'; @@ -244,6 +244,78 @@ describe('ternary plots', function() { }); }); + it('should be able to reorder axis layers when relayout\'ing *layer*', function(done) { + var gd = createGraphDiv(); + var fig = Lib.extendDeep({}, require('@mocks/ternary_simple.json')); + var dflt = [ + 'draglayer', 'plotbg', 'backplot', 'grids', + 'frontplot', + 'aaxis', 'aline', 'baxis', 'bline', 'caxis', 'cline' + ]; + + function _assert(layers) { + var toplevel = d3.selectAll('g.ternary > .toplevel'); + + expect(toplevel.size()).toBe(layers.length, '# of layer'); + + toplevel.each(function(d, i) { + var className = d3.select(this) + .attr('class') + .split('toplevel ')[1]; + + expect(className).toBe(layers[i], 'layer ' + i); + }); + } + + Plotly.plot(gd, fig).then(function() { + _assert(dflt); + return Plotly.relayout(gd, 'ternary.aaxis.layer', 'below traces'); + }) + .then(function() { + _assert([ + 'draglayer', 'plotbg', 'backplot', 'grids', + 'aaxis', 'aline', + 'frontplot', + 'baxis', 'bline', 'caxis', 'cline' + ]); + return Plotly.relayout(gd, 'ternary.caxis.layer', 'below traces'); + }) + .then(function() { + _assert([ + 'draglayer', 'plotbg', 'backplot', 'grids', + 'aaxis', 'aline', 'caxis', 'cline', + 'frontplot', + 'baxis', 'bline' + ]); + return Plotly.relayout(gd, 'ternary.baxis.layer', 'below traces'); + }) + .then(function() { + _assert([ + 'draglayer', 'plotbg', 'backplot', 'grids', + 'aaxis', 'aline', 'baxis', 'bline', 'caxis', 'cline', + 'frontplot' + ]); + return Plotly.relayout(gd, 'ternary.aaxis.layer', null); + }) + .then(function() { + _assert([ + 'draglayer', 'plotbg', 'backplot', 'grids', + 'baxis', 'bline', 'caxis', 'cline', + 'frontplot', + 'aaxis', 'aline' + ]); + return Plotly.relayout(gd, { + 'ternary.baxis.layer': null, + 'ternary.caxis.layer': null + }); + }) + .then(function() { + _assert(dflt); + }) + .catch(fail) + .then(done); + }); + function countTernarySubplot() { return d3.selectAll('.ternary').size(); }