From cd2f59de5e581008001849a9786252ede9b4201e Mon Sep 17 00:00:00 2001 From: Nicolas Riesco Date: Tue, 8 Nov 2016 16:51:02 +0000 Subject: [PATCH 01/37] bar: add text-related attributes * Added attributes textposition, textfont, insidetextfont and outsidetextfont. * All these attributes accept arrays. --- src/plot_api/helpers.js | 2 +- src/traces/bar/attributes.js | 28 +++++++ src/traces/bar/defaults.js | 140 ++++++++++++++++++++++++++++++++++- 3 files changed, 168 insertions(+), 2 deletions(-) diff --git a/src/plot_api/helpers.js b/src/plot_api/helpers.js index 1753335c49f..02f1c64e146 100644 --- a/src/plot_api/helpers.js +++ b/src/plot_api/helpers.js @@ -299,7 +299,7 @@ exports.cleanData = function(data, existingData) { trace.scene = Plots.subplotsRegistry.gl3d.cleanId(trace.scene); } - if(!Registry.traceIs(trace, 'pie')) { + if(!Registry.traceIs(trace, 'pie') && !Registry.traceIs(trace, 'bar')) { if(Array.isArray(trace.textposition)) { trace.textposition = trace.textposition.map(cleanTextPosition); } diff --git a/src/traces/bar/attributes.js b/src/traces/bar/attributes.js index c5659f56611..bedb65f7bea 100644 --- a/src/traces/bar/attributes.js +++ b/src/traces/bar/attributes.js @@ -10,6 +10,7 @@ var scatterAttrs = require('../scatter/attributes'); var colorAttributes = require('../../components/colorscale/color_attributes'); +var fontAttrs = require('../../plots/font_attributes'); var extendFlat = require('../../lib/extend').extendFlat; var scatterMarkerAttrs = scatterAttrs.marker; @@ -35,8 +36,35 @@ module.exports = { y: scatterAttrs.y, y0: scatterAttrs.y0, dy: scatterAttrs.dy, + text: scatterAttrs.text, + textposition: { + valType: 'enumerated', + role: 'info', + values: ['inside', 'outside', 'auto', 'none'], + dflt: 'none', + arrayOk: true, + description: [ + 'Specifies the location of the `textinfo`.' + ].join(' ') + }, + + textfont: extendFlat({}, fontAttrs, { + arrayOk: true, + description: 'Sets the font used for `textinfo`.' + }), + + insidetextfont: extendFlat({}, fontAttrs, { + arrayOk: true, + description: 'Sets the font used for `textinfo` lying inside the bar.' + }), + + outsidetextfont: extendFlat({}, fontAttrs, { + arrayOk: true, + description: 'Sets the font used for `textinfo` lying outside the bar.' + }), + orientation: { valType: 'enumerated', role: 'info', diff --git a/src/traces/bar/defaults.js b/src/traces/bar/defaults.js index 6e094021140..95f9fd56f86 100644 --- a/src/traces/bar/defaults.js +++ b/src/traces/bar/defaults.js @@ -9,6 +9,9 @@ 'use strict'; +var isNumeric = require('fast-isnumeric'); +var tinycolor = require('tinycolor2'); + var Lib = require('../../lib'); var Color = require('../../components/color'); @@ -23,6 +26,125 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout return Lib.coerce(traceIn, traceOut, attributes, attr, dflt); } + function coerceEnumerated(attributeDefinition, value, defaultValue) { + if(attributeDefinition.coerceNumber) value = +value; + + if(attributeDefinition.values.indexOf(value) !== -1) return value; + + return (defaultValue !== undefined) ? + defaultValue : + attributeDefinition.dflt; + } + + function coerceString(attributeDefinition, value, defaultValue) { + if(typeof value === 'string') { + if(value || !attributeDefinition.noBlank) return value; + } + else if(typeof value === 'number') { + if(!attributeDefinition.strict) return String(value); + } + + return (defaultValue !== undefined) ? + defaultValue : + attributeDefinition.dflt; + } + + function coerceNumber(attributeDefinition, value, defaultValue) { + if(isNumeric(value)) { + value = +value; + + var min = attributeDefinition.min, + max = attributeDefinition.max, + isOutOfBounds = (min !== undefined && value < min) || + (max !== undefined && value > max); + + if(!isOutOfBounds) return value; + } + + return (defaultValue !== undefined) ? + defaultValue : + attributeDefinition.dflt; + } + + function coerceColor(attributeDefinition, value, defaultValue) { + if(tinycolor(value).isValid()) return value; + + return (defaultValue !== undefined) ? + defaultValue : + attributeDefinition.dflt; + } + + function coerceFont(attributeDefinition, value, defaultValue) { + value = value || {}; + defaultValue = defaultValue || {}; + + return { + family: coerceString( + attributeDefinition.family, value.family, defaultValue.family), + size: coerceNumber( + attributeDefinition.size, value.size, defaultValue.size), + color: coerceColor( + attributeDefinition.color, value.color, defaultValue.color) + }; + } + + function coerceArray(attribute, coerceFunction, defaultValue, defaultValue2) { + var attributeDefinition = attributes[attribute], + arrayOk = attributeDefinition.arrayOk, + inValue = traceIn[attribute], + inValueIsArray = Array.isArray(inValue), + defaultValueIsArray = Array.isArray(defaultValue), + outValue, + i; + + // Case: inValue and defaultValue not treated as arrays + if(!arrayOk || (!inValueIsArray && !defaultValueIsArray)) { + outValue = coerceFunction( + attributeDefinition, inValue, defaultValue); + traceOut[attribute] = outValue; + return outValue; + } + + // Coerce into an array + outValue = []; + + // Case: defaultValue is an array and inValue isn't + if(!inValueIsArray) { + for(i = 0; i < defaultValue.length; i++) { + outValue.push( + coerceFunction( + attributeDefinition, inValue, defaultValue[i])); + } + } + + // Case: inValue is an array and defaultValue isn't + else if(!defaultValueIsArray) { + for(i = 0; i < inValue.length; i++) { + outValue.push( + coerceFunction( + attributeDefinition, inValue[i], defaultValue)); + } + } + + // Case: inValue and defaultValue are both arrays + else { + for(i = 0; i < defaultValue.length; i++) { + outValue.push( + coerceFunction( + attributeDefinition, inValue[i], defaultValue[i])); + } + for(; i < inValue.length; i++) { + outValue.push( + coerceFunction( + attributeDefinition, inValue[i], defaultValue2)); + } + } + + traceOut[attribute] = outValue; + + return outValue; + } + var len = handleXYDefaults(traceIn, traceOut, coerce); if(!len) { traceOut.visible = false; @@ -33,7 +155,23 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout coerce('base'); coerce('offset'); coerce('width'); - coerce('text'); + + coerceArray('text', coerceString); + + var textPosition = coerceArray('textposition', coerceEnumerated); + + var hasBoth = Array.isArray(textPosition) || textPosition === 'auto', + hasInside = hasBoth || textPosition === 'inside', + hasOutside = hasBoth || textPosition === 'outside'; + if(hasInside || hasOutside) { + var textFont = coerceArray('textfont', coerceFont, layout.font); + if(hasInside) { + coerceArray('insidetextfont', coerceFont, textFont, layout.font); + } + if(hasOutside) { + coerceArray('outsidetextfont', coerceFont, textFont, layout.font); + } + } handleStyleDefaults(traceIn, traceOut, coerce, defaultColor, layout); From 7d7017c5b97b7204a3e74ef44d3d03de720d2be7 Mon Sep 17 00:00:00 2001 From: Nicolas Riesco Date: Tue, 8 Nov 2016 16:53:32 +0000 Subject: [PATCH 02/37] bar: draw bar texts --- src/traces/bar/plot.js | 275 +++++++++++++++++++++++++++++++- src/traces/bar/set_positions.js | 25 ++- 2 files changed, 291 insertions(+), 9 deletions(-) diff --git a/src/traces/bar/plot.js b/src/traces/bar/plot.js index 4e0cb53ac29..0f4cb7f0558 100644 --- a/src/traces/bar/plot.js +++ b/src/traces/bar/plot.js @@ -13,7 +13,10 @@ var d3 = require('d3'); var isNumeric = require('fast-isnumeric'); var Lib = require('../../lib'); +var svgTextUtils = require('../../lib/svg_text_utils'); + var Color = require('../../components/color'); +var Drawing = require('../../components/drawing'); var ErrorBars = require('../../components/errorbars'); var arraysToCalcdata = require('./arrays_to_calcdata'); @@ -42,9 +45,9 @@ module.exports = function plot(gd, plotinfo, cdbar) { arraysToCalcdata(d); - d3.select(this).selectAll('path') + d3.select(this).selectAll('g.point') .data(Lib.identity) - .enter().append('path') + .enter().append('g').classed('point', true) .each(function(di, i) { // now display the bar // clipped xf/yf (2nd arg true): non-positive @@ -75,15 +78,18 @@ module.exports = function plot(gd, plotinfo, cdbar) { d3.select(this).remove(); return; } + var lw = (di.mlw + 1 || trace.marker.line.width + 1 || (di.trace ? di.trace.marker.line.width : 0) + 1) - 1, offset = d3.round((lw / 2) % 1, 2); + function roundWithLine(v) { // if there are explicit gaps, don't round, // it can make the gaps look crappy return (fullLayout.bargap === 0 && fullLayout.bargroupgap === 0) ? d3.round(Math.round(v) - offset, 2) : v; } + function expandToVisible(v, vc) { // if it's not in danger of disappearing entirely, // round more precisely @@ -93,6 +99,7 @@ module.exports = function plot(gd, plotinfo, cdbar) { // its neighbor (v > vc ? Math.ceil(v) : Math.floor(v)); } + if(!gd._context.staticPlot) { // if bars are not fully opaque or they have a line // around them, round to integer pixels, mainly for @@ -108,8 +115,14 @@ module.exports = function plot(gd, plotinfo, cdbar) { y0 = fixpx(y0, y1); y1 = fixpx(y1, y0); } - d3.select(this).attr('d', + + // append bar path and text + var bar = d3.select(this); + + bar.append('path').attr('d', 'M' + x0 + ',' + y0 + 'V' + y1 + 'H' + x1 + 'V' + y0 + 'Z'); + + appendBarText(gd, bar, d, i, x0, x1, y0, y1); }); }); @@ -117,3 +130,259 @@ module.exports = function plot(gd, plotinfo, cdbar) { bartraces.call(ErrorBars.plot, plotinfo); }; + +function appendBarText(gd, bar, calcTrace, i, x0, x1, y0, y1) { + var trace = calcTrace[0].trace; + + // get bar text + var traceText = trace.text; + if(!traceText) return; + + var text; + if(Array.isArray(traceText)) { + if(i >= traceText.length) return; + text = traceText[i]; + } + else { + text = traceText; + } + + // get text position + var traceTextPosition = trace.textposition, + textPosition; + if(Array.isArray(traceTextPosition)) { + if(i >= traceTextPosition.length) return; + textPosition = traceTextPosition[i]; + } + else { + textPosition = traceTextPosition; + } + + if(textPosition === 'none') return; + + var barWidth = Math.abs(x1 - x0), + barHeight = Math.abs(y1 - y0), + barIsTooSmall = (barWidth < 8 || barHeight < 8), + + barmode = gd._fullLayout.barmode, + inStackMode = (barmode === 'stack'), + inRelativeMode = (barmode === 'relative'), + inStackOrRelativeMode = inStackMode || inRelativeMode, + + calcBar = calcTrace[i], + isOutmostBar = !inStackOrRelativeMode || calcBar._outmost; + + if(textPosition === 'auto') { + textPosition = (barIsTooSmall && isOutmostBar) ? 'outside' : 'inside'; + } + + if(textPosition === 'outside') { + if(!isOutmostBar) textPosition = 'inside'; + } + + if(textPosition === 'inside') { + if(barIsTooSmall) return; + } + + + // get text font + var textFont; + + if(textPosition === 'outside') { + var traceOutsideTextFont = trace.outsidetextfont; + if(Array.isArray(traceOutsideTextFont)) { + if(i >= traceOutsideTextFont.length) return; + textFont = traceOutsideTextFont[i]; + } + else { + textFont = traceOutsideTextFont; + } + } + else { + var traceInsideTextFont = trace.insidetextfont; + if(Array.isArray(traceInsideTextFont)) { + if(i >= traceInsideTextFont.length) return; + textFont = traceInsideTextFont[i]; + } + else { + textFont = traceInsideTextFont; + } + } + + if(!textFont) { + var traceTextFont = trace.textfont; + if(Array.isArray(traceTextFont)) { + if(i >= traceTextFont.length) return; + textFont = traceTextFont[i]; + } + else { + textFont = traceTextFont; + } + } + + if(!textFont) { + textFont = gd._fullLayout.font; + } + + // append bar text + var textSelection = bar.append('text') + // prohibit tex interpretation until we can handle + // tex and regular text together + .attr('data-notex', 1) + .text(text) + .attr({ + 'class': 'bartext', + transform: '', + 'data-bb': '', + 'text-anchor': 'middle', + x: 0, + y: 0 + }) + .call(Drawing.font, textFont); + + textSelection.call(svgTextUtils.convertToTspans); + textSelection.selectAll('tspan.line').attr({x: 0, y: 0}); + + // position bar text + var textBB = Drawing.bBox(textSelection.node()), + textWidth = textBB.width, + textHeight = textBB.height; + if(!textWidth || !textHeight) { + textSelection.remove(); + return; + } + + // compute translate transform + var transform; + if(textPosition === 'outside') { + transform = getTransformToMoveOutsideBar(x0, x1, y0, y1, textBB, + trace.orientation); + } + else { + transform = getTransformToMoveInsideBar(x0, x1, y0, y1, textBB); + } + + textSelection.attr('transform', transform); +} + +function getTransformToMoveInsideBar(x0, x1, y0, y1, textBB) { + // compute text and target positions + var barWidth = Math.abs(x1 - x0), + barHeight = Math.abs(y1 - y0), + textWidth = textBB.width, + textHeight = textBB.height, + barX = (x0 + x1) / 2, + barY = (y0 + y1) / 2, + textX = (textBB.left + textBB.right) / 2, + textY = (textBB.top + textBB.bottom) / 2; + + // apply target padding + var targetWidth = 0.95 * barWidth, + targetHeight = 0.95 * barHeight; + + return getTransform( + textX, textY, textWidth, textHeight, + barX, barY, targetWidth, targetHeight); +} + +function getTransformToMoveOutsideBar(x0, x1, y0, y1, textBB, orientation) { + + // compute text and target positions + var textWidth = textBB.width, + textHeight = textBB.height, + textX = (textBB.left + textBB.right) / 2, + textY = (textBB.top + textBB.bottom) / 2; + + var targetWidth, targetHeight, + targetX, targetY; + if(orientation === 'h') { + if(x1 < x0) { + // bar end is on the left hand side + targetWidth = 2 + textWidth; // padding included + targetHeight = Math.abs(y1 - y0); + targetX = x1 - targetWidth / 2; + targetY = (y0 + y1) / 2; + } + else { + targetWidth = 2 + textWidth; // padding included + targetHeight = Math.abs(y1 - y0); + targetX = x1 + targetWidth / 2; + targetY = (y0 + y1) / 2; + } + } + else { + if(y1 > y0) { + // bar end is on the bottom + targetWidth = Math.abs(x1 - x0); + targetHeight = 2 + textHeight; // padding included + targetX = (x0 + x1) / 2; + targetY = y1 + targetHeight / 2; + } + else { + targetWidth = Math.abs(x1 - x0); + targetHeight = 2 + textHeight; // padding included + targetX = (x0 + x1) / 2; + targetY = y1 - targetHeight / 2; + } + } + + return getTransform( + textX, textY, textWidth, textHeight, + targetX, targetY, targetWidth, targetHeight); +} + +/** + * Compute SVG transform to move a text box into a target box + * + * @param {number} textX X pixel coord of the text box center + * @param {number} textY Y pixel coord of the text box center + * @param {number} textWidth text box width + * @param {number} textHeight text box height + * @param {number} targetX X pixel coord of the target box center + * @param {number} targetY Y pixel coord of the target box center + * @param {number} targetWidth target box width + * @param {number} targetHeight target box height + * + * @returns {string} SVG transform + */ +function getTransform( + textX, textY, textWidth, textHeight, + targetX, targetY, targetWidth, targetHeight) { + + // compute translate transform + var translateX = targetX - textX, + translateY = targetY - textY, + translate = 'translate(' + translateX + ' ' + translateY + ')'; + + // if bar text doesn't fit, compute rotate and scale transforms + var doesntFit = (textWidth > targetWidth || textHeight > targetHeight), + rotate, scale, scaleX, scaleY; + + if(doesntFit) { + var textIsHorizontal = (textWidth > textHeight), + targetIsHorizontal = (targetWidth > targetHeight); + if(textIsHorizontal !== targetIsHorizontal) { + rotate = 'rotate(-90 ' + textX + ' ' + textY + ')'; + scaleX = targetWidth / textHeight; + scaleY = targetHeight / textWidth; + } + else { + scaleX = targetWidth / textWidth; + scaleY = targetHeight / textHeight; + } + + if(scaleX > 1) scaleX = 1; + if(scaleY > 1) scaleY = 1; + + if(scaleX !== 1 || scaleY !== 1) { + scale = 'scale(' + scaleX + ' ' + scaleY + ')'; + } + } + + // compute transform + var transform = translate; + if(scale) transform += ' ' + scale; + if(rotate) transform += ' ' + rotate; + + return transform; +} diff --git a/src/traces/bar/set_positions.js b/src/traces/bar/set_positions.js index fd5edd0487f..62c112bd157 100644 --- a/src/traces/bar/set_positions.js +++ b/src/traces/bar/set_positions.js @@ -179,6 +179,24 @@ function setGroupPositionsInStackOrRelativeMode(gd, pa, sa, calcTraces) { // set bar bases and sizes, and update size axis stackBars(gd, sa, sieve); + + // flag the outmost bar (for text display purposes) + for(var i = 0; i < calcTraces.length; i++) { + var calcTrace = calcTraces[i]; + + for(var j = 0; j < calcTrace.length; j++) { + var bar = calcTrace[j]; + + if(!isNumeric(bar.s)) continue; + + var isOutmostBar = ((bar.b + bar.s) === sieve.get(bar.p, bar.s)); + if(isOutmostBar) bar._outmost = true; + } + } + + // Note that marking the outmost bars has to be done + // before `normalizeBars` changes `bar.b` and `bar.s`. + if(barnorm) normalizeBars(gd, sa, sieve); } @@ -506,12 +524,7 @@ function stackBars(gd, sa, sieve) { } // if barnorm is set, let normalizeBars update the axis range - if(barnorm) { - normalizeBars(gd, sa, sieve); - } - else { - Axes.expand(sa, [sMin, sMax], {tozero: true, padded: true}); - } + if(!barnorm) Axes.expand(sa, [sMin, sMax], {tozero: true, padded: true}); } From c2cb2c8eb19468fe0c210e9b75f7e8e47f418eba Mon Sep 17 00:00:00 2001 From: Nicolas Riesco Date: Tue, 8 Nov 2016 16:54:20 +0000 Subject: [PATCH 03/37] test: bar texts --- test/jasmine/tests/bar_test.js | 260 ++++++++++++++++++++++++++++++++- 1 file changed, 256 insertions(+), 4 deletions(-) diff --git a/test/jasmine/tests/bar_test.js b/test/jasmine/tests/bar_test.js index d9d93fc48d9..32f675f1307 100644 --- a/test/jasmine/tests/bar_test.js +++ b/test/jasmine/tests/bar_test.js @@ -1,17 +1,17 @@ var Plotly = require('@lib/index'); -var Plots = require('@src/plots/plots'); + +var Bar = require('@src/traces/bar'); var Lib = require('@src/lib'); +var Plots = require('@src/plots/plots'); var PlotlyInternal = require('@src/plotly'); var Axes = PlotlyInternal.Axes; -var Bar = require('@src/traces/bar'); - var createGraphDiv = require('../assets/create_graph_div'); var destroyGraphDiv = require('../assets/destroy_graph_div'); var customMatchers = require('../assets/custom_matchers'); -describe('bar supplyDefaults', function() { +describe('Bar.supplyDefaults', function() { 'use strict'; var traceIn, @@ -84,6 +84,96 @@ describe('bar supplyDefaults', function() { supplyDefaults(traceIn, traceOut, defaultColor); expect(traceOut.width).toBeUndefined(); }); + + it('should coerce textposition to none', function() { + traceIn = { + y: [1, 2, 3] + }; + supplyDefaults(traceIn, traceOut, defaultColor); + expect(traceOut.textposition).toBe('none'); + expect(traceOut.texfont).toBeUndefined(); + expect(traceOut.insidetexfont).toBeUndefined(); + expect(traceOut.outsidetexfont).toBeUndefined(); + }); + + it('should default textfont to layout.font', function() { + traceIn = { + textposition: 'inside', + y: [1, 2, 3] + }; + + var layout = { + font: {family: 'arial', color: '#AAA', size: 13} + }; + + supplyDefaults(traceIn, traceOut, defaultColor, layout); + + expect(traceOut.textposition).toBe('inside'); + expect(traceOut.textfont).toEqual(layout.font); + expect(traceOut.textfont).not.toBe(layout.font); + expect(traceOut.insidetextfont).toEqual(layout.font); + expect(traceOut.insidetextfont).not.toBe(layout.font); + expect(traceOut.insidetextfont).not.toBe(traceOut.textfont); + expect(traceOut.outsidetexfont).toBeUndefined(); + }); + + it('should coerce text-related attributes', function() { + traceIn = { + y: [10, 20, 30, 40], + type: 'bar', + text: ['T1P1', 'T1P2', 'T1P3', 14], + textposition: ['inside', 'outside', 'BADVALUE'], + textfont: [{ + family: '"comic sans"', + color: '#02A' + }, { + color: '#A20' + }], + insidetextfont: [ + {size: 8}, + {size: 12}, + {size: 16} + ], + outsidetextfont: [ + null, + {size: 24}, + {size: 32} + ] + }; + + var layout = { + font: {family: 'arial', color: '#AAA', size: 13} + }; + + supplyDefaults(traceIn, traceOut, defaultColor, layout); + + var expected = { + y: [10, 20, 30, 40], + type: 'bar', + text: ['T1P1', 'T1P2', 'T1P3', '14'], + textposition: ['inside', 'outside', 'none'], + textfont: [ + {family: '"comic sans"', color: '#02A', size: 13}, + {family: 'arial', color: '#A20', size: 13} + ], + insidetextfont: [ + {family: '"comic sans"', color: '#02A', size: 8}, + {family: 'arial', color: '#A20', size: 12}, + {family: 'arial', color: '#AAA', size: 16} + ], + outsidetextfont: [ + {family: '"comic sans"', color: '#02A', size: 13}, + {family: 'arial', color: '#A20', size: 24}, + {family: 'arial', color: '#AAA', size: 32} + ] + }; + + expect(traceOut.text).toEqual(expected.text); + expect(traceOut.textposition).toEqual(expected.textposition); + expect(traceOut.textfont).toEqual(expected.textfont); + expect(traceOut.insidetextfont).toEqual(expected.insidetextfont); + expect(traceOut.outsidetextfont).toEqual(expected.outsidetextfont); + }); }); describe('heatmap calc / setPositions', function() { @@ -612,6 +702,168 @@ describe('A bar plot', function() { afterEach(destroyGraphDiv); + function getAllTraceNodes(node) { + return node.querySelectorAll('g.points'); + } + + function getAllBarNodes(node) { + return node.querySelectorAll('g.point'); + } + + function assertTextIsInsidePath(textNode, pathNode) { + var textBB = textNode.getBoundingClientRect(), + pathBB = pathNode.getBoundingClientRect(); + + expect(pathBB.left).not.toBeGreaterThan(textBB.left); + expect(textBB.right).not.toBeGreaterThan(pathBB.right); + expect(pathBB.top).not.toBeGreaterThan(textBB.top); + expect(textBB.bottom).not.toBeGreaterThan(pathBB.bottom); + } + + function assertTextIsAbovePath(textNode, pathNode) { + var textBB = textNode.getBoundingClientRect(), + pathBB = pathNode.getBoundingClientRect(); + + expect(textBB.bottom).not.toBeGreaterThan(pathBB.top); + } + + function assertTextIsBelowPath(textNode, pathNode) { + var textBB = textNode.getBoundingClientRect(), + pathBB = pathNode.getBoundingClientRect(); + + expect(pathBB.bottom).not.toBeGreaterThan(textBB.top); + } + + function assertTextIsAfterPath(textNode, pathNode) { + var textBB = textNode.getBoundingClientRect(), + pathBB = pathNode.getBoundingClientRect(); + + expect(pathBB.right).not.toBeGreaterThan(textBB.left); + } + + function assertTextIsBeforePath(textNode, pathNode) { + var textBB = textNode.getBoundingClientRect(), + pathBB = pathNode.getBoundingClientRect(); + + expect(textBB.right).not.toBeGreaterThan(pathBB.left); + } + + it('should show bar texts (inside case)', function(done) { + var gd = createGraphDiv(), + data = [{ + y: [10, 20, 30], + type: 'bar', + text: ['1', 'Very very very very very long bar text'], + textposition: 'inside', + }], + layout = { + }; + + Plotly.plot(gd, data, layout).then(function() { + var traceNodes = getAllTraceNodes(gd), + barNodes = getAllBarNodes(traceNodes[0]); + + for(var i = 0; i < barNodes.length; i++) { + var barNode = barNodes[i], + pathNode = barNode.querySelector('path'), + textNode = barNode.querySelector('text'); + if(textNode) assertTextIsInsidePath(textNode, pathNode); + } + + done(); + }); + }); + + it('should show bar texts (outside case)', function(done) { + var gd = createGraphDiv(), + data = [{ + y: [10, -20, 30], + type: 'bar', + text: ['1', 'Very very very very very long bar text'], + textposition: 'outside', + }], + layout = { + barmode: 'relative' + }; + + Plotly.plot(gd, data, layout).then(function() { + var traceNodes = getAllTraceNodes(gd), + barNodes = getAllBarNodes(traceNodes[0]); + + for(var i = 0; i < barNodes.length; i++) { + var barNode = barNodes[i], + pathNode = barNode.querySelector('path'), + textNode = barNode.querySelector('text'); + if(textNode) { + if(data[0].y[i] > 0) assertTextIsAbovePath(textNode, pathNode); + else assertTextIsBelowPath(textNode, pathNode); + } + } + + done(); + }); + }); + + it('should show bar texts (horizontal case)', function(done) { + var gd = createGraphDiv(), + data = [{ + x: [10, -20, 30], + type: 'bar', + text: ['Very very very very very long bar text', -20], + textposition: 'outside', + }], + layout = { + }; + + Plotly.plot(gd, data, layout).then(function() { + var traceNodes = getAllTraceNodes(gd), + barNodes = getAllBarNodes(traceNodes[0]); + + for(var i = 0; i < barNodes.length; i++) { + var barNode = barNodes[i], + pathNode = barNode.querySelector('path'), + textNode = barNode.querySelector('text'); + if(textNode) { + if(data[0].x[i] > 0) assertTextIsAfterPath(textNode, pathNode); + else assertTextIsBeforePath(textNode, pathNode); + } + } + + done(); + }); + }); + + it('should show bar texts (barnorm case)', function(done) { + var gd = createGraphDiv(), + data = [{ + x: [100, -100, 100], + type: 'bar', + text: [100, -100, 100], + textposition: 'outside', + }], + layout = { + barmode: 'relative', + barnorm: true + }; + + Plotly.plot(gd, data, layout).then(function() { + var traceNodes = getAllTraceNodes(gd), + barNodes = getAllBarNodes(traceNodes[0]); + + for(var i = 0; i < barNodes.length; i++) { + var barNode = barNodes[i], + pathNode = barNode.querySelector('path'), + textNode = barNode.querySelector('text'); + if(textNode) { + if(data[0].x[i] > 0) assertTextIsAfterPath(textNode, pathNode); + else assertTextIsBeforePath(textNode, pathNode); + } + } + + done(); + }); + }); + it('should be able to restyle', function(done) { var gd = createGraphDiv(), mock = Lib.extendDeep({}, require('@mocks/bar_attrs_relative')); From 2526e83c58cc2203d9336309d8de7d1ab082a9de Mon Sep 17 00:00:00 2001 From: Nicolas Riesco Date: Tue, 8 Nov 2016 16:55:25 +0000 Subject: [PATCH 04/37] test: add mocks using bar texts --- .../baselines/bar_text_horizontal_group.png | Bin 0 -> 13165 bytes .../bar_text_horizontal_relative.png | Bin 0 -> 10535 bytes .../bar_text_horizontal_relative_percent.png | Bin 0 -> 9499 bytes test/image/baselines/bar_text_relative.png | Bin 0 -> 15414 bytes .../mocks/bar_text_horizontal_group.json | 23 ++++++++++ .../mocks/bar_text_horizontal_relative.json | 20 +++++++++ .../bar_text_horizontal_relative_percent.json | 22 ++++++++++ test/image/mocks/bar_text_relative.json | 40 ++++++++++++++++++ 8 files changed, 105 insertions(+) create mode 100644 test/image/baselines/bar_text_horizontal_group.png create mode 100644 test/image/baselines/bar_text_horizontal_relative.png create mode 100644 test/image/baselines/bar_text_horizontal_relative_percent.png create mode 100644 test/image/baselines/bar_text_relative.png create mode 100644 test/image/mocks/bar_text_horizontal_group.json create mode 100644 test/image/mocks/bar_text_horizontal_relative.json create mode 100644 test/image/mocks/bar_text_horizontal_relative_percent.json create mode 100644 test/image/mocks/bar_text_relative.json diff --git a/test/image/baselines/bar_text_horizontal_group.png b/test/image/baselines/bar_text_horizontal_group.png new file mode 100644 index 0000000000000000000000000000000000000000..78c1b2c663c1a9c7c18f06a84387b20b2cd5608e GIT binary patch literal 13165 zcmeHubySpXyDttS0xAp$B1jC~ARr|*NQWSZbVv*;DIqn4gD52}DS{%3bSWK*C=4k` zgP;OK2n^k^?*U)m_k7>k=j^?|bJjZXvex_o>z?Pi}AdP_$qCRN&7ceXc|CRLtW#dUMh64TbE zC#!Mihqt$PY-}t`XlUqr+Q(LGTU%S^mqbOcFYzhvvD`yYO;ChD(q3>uT%bcGry#{p zn&@*7or@4O*Eh*97MRL2{#H0l_0hl8gsByt*v?TgO-zX9;*f>tymO;cf=42FTY5au(kpEu-F1Gb znXlF5fw5-pxY1hIF~1E=oVF}?1f6K?%$F>UU@``qclVBYXnQG>qPZ@xz>Z6+bQO_I zti*9If9oq-9)EQr)1tIcTAhihkk{HxDuGpBkB(EWdirGLT1F>wH-W5V@|H~KPA8q* z?J{luUe_qrhHc@uMsWMSyqK&sANB6;?iy6v-g7jS^unk(&OADU=wZb3+jvP-KQB zW%Dc6ndP1i{q7v6I(N;^aN-Hm*UD2KkH6Cd@Gx{owK6cdCnTGdY3wy#i+ru`oIJz_ zQ)uy>t^X0m%YuRn)@^3+nUraMia5{twc2yzMnXbDO9H=1rrT79Ro$-L=wiEN*6OPJ z)>>fzzrw>)OUq4>3@eK6i{IpFWqjVWrOBA2T)fR9ag{g-iEx9V#tp7OgLolt$m_q(uL=jA6kEU6hpzP?B%&5=4x1UL%#Cv z0XPFpo!^el=HmC!F>&WnzJRS(i$>z{)yd)}#+8Y?z1Izt_8n|W?0R`{m2Xc%bYOpM zo+@puT!7E>9_<`M_!n6_Rv8g;jdTtRR7`roPo68WC)w*I0 z#C+eYt*98-!O87@C!g#SnUZajjQUwn-LbP1dWO^H+dK3edO9nApB5I9W_d+U!SPHp zI*^U_>9AVbe3=pDxO%xsGta-~$X?xO_*5(dLX&EWHJbT>bP4DG8ZzI;)#onfmyp z2a#Mq@mDeFOdpnQtJ8m7g#5AO3D;MMHfwU(&1dE1i{FEpS~i!)Ygf(>4i38R%$K(^ zc#bgF`m8Ol!`m2Jx?ZSErlA&MxUaoZ&DGDZnUZp!j=tzP^u|=A&9a~xS2rB@u#&Wu zzPoDkNBCr)iF2>$yd20L??4zW(kt1=A+!=2TLt$wc1pgTIEZb_4AV{`_rjehsTdB{ z6@1=Ifiz1V5`ei;_hzca)!+g(Qzdl790u+rAliD3!_V@^1_ePe7|c>Z<2FXypJjgN z?Mgwauz{uD(np%n2>GllPdI4Iy`DRaROqxl=VK*vTZ`X9_ShHJuIJe%O^GE^KKWpa z&8(&9Og69|^njrUVy(#)=Y67M9rj?DBC9sMcm7s29j%wV>fptM+O7NdI{o z*ICbbo=iMC`RTztSxJemBTWdU_A`)|yBODxRu+zUMDM?~|1wRw?PWExwJz|IFw*4w zRu0@~wlUEs@qnyzUz3jG!T4hQAekIgw-;q;79G=&ui3Gbq67~U7e36ebjoOn%V&~= z{fXHwoM&1_!(mEeO)?#92A3yPq6Y_xt!ocNPePWw*Ohe`(Tc|~Q`cc3ou{up zR)NRA6X`c7G^^J4Uwy%(=IUDFNqEuigyss89-5JKm+S>`BeO%RE^#BJDS;JnCh3)d zT2#68_VkP8EV(@rv>y6Ndae&dO>N1IxYu~tcU(e9sCdX9_S*Qw%_SS`Nw+MYh`62} zz3?|I%Tdm9Q03a5R?3<6dHyb=I@WuLjMURAk@QPWdbUpd7rYj4Kq=Y9>kULUPPVkQ zt)kBL>W|Uui(bA+nSENr-@mS$w&VEnMi&cCFp}|mgqva&S%m-IIioY^)T}P7xiD7w zrZOBWMu24$#ny@0ey3Mmv|uM@8;}V*j^5mhE5nv6w^f?D5h}Z#tGh%4UUkK7`dzA~ zc)6)S_07|eP|CEZTxJ;4cNLvWXQ4l)w1-j@g0Q1TSmm%>gTi$--KQrZ*(_I9MB=cV zT<67|+89--2e*nZL6+|Otcu!KBX;-IOumq~t$cF&T7|hH{*|pYQ821bQYSAI!GpH( zkZE_OIgffWnSQXF)s`-2k-%s43zbN@r?8;7op2c8!GZgPGd=!&@@KYplHXNQuo{_!8PoQ@PB z;YJQd_hkS>A+yFGUAS&B56nRBb~u8M<7F-}#6@YSjto+KbBcvF7vi!OS4Q0Uk*_Qd zjo^DP`G^@7T*R;bF%Yr4t7ZbV65=*ff`#yQuiu4*$Vky(3}GQ-a<}cbo7LQDN-M)e zVXD;Vts<}i$9$;OKLsLk?yTXuUOa!R-HFfWn+H6-hQ4|V(dsjo%QzD0@?&1rOFISmb$)igC91O7@^ zIGKUKv+XM1piJ9~?mCfly_y7Qu6u_}PUj|Ma5u2IWK3cxXXrXdz^*}TN{Vr{7>{xp zNNscxzXKr*@Y-XYQ(S;*gD?N0+SEuq!S2+7X51q93$w(h!t@s)6BGP}r2cn#@O|)n z-$)jaQxBQ?Ek0tldI@vJu`XkE4JF1(8b0TX=f*BUFxN!?(-J~j|J}|eBYb%D}fBndW5|55Xq~CDvU!zT9<4mFz*91 zT_MTUr7zvH12lcaFX=^kgf?P6ZwSWg*^7W9ukNaeq>8-^)YesxUh@8)L>jX%g2{X-4$Bs zQ;wAeuCq*W<>VKa*CiGlt9iR0#5|E|G0T^WUtf!qnmq+^A*&)H1rO3Y0;*OBH&j!8;XozNv{=h6Wy!PkTQp0V#JrKJH^l-YB{>n;VRv;ya>} zd?zO~8^4s5CorC_r*PBq+xt#*2)ntb#(IpD%SYK6x(5keE{fXYf$Y}N<|@I*&|)aK zpMV4#O^+nEw!d$&^wZLhsG^FCT10vEnrZV@M%`z^>F+*NFzk@;N63ST0#pR7!`cY` zlT0*vAz7tPCg|=(g;xPR#%Cvdxz;GdC)Xp)COYP{Sq&^}QC!z-Jw~ywc_&-%5Vz9I z_3BdnmLP^_n==E-WB8l8*}2Wxc(*c7*{>N*z<2Bbsu*WtTleNY)$xSW zOdei$uK=9^qL}(Hem?;yS?!s$C;dApUx;zM6|m@f*8d z^@YkQ#@pSQp^2B_vYn$`Ev(U8@U0qB$WrC8uw~**N-rP+;_46faLZrP_x147MS_fy^9sEk4a6% z$s%1TWq=ee3`F=kz4HcJabsi3_k{v{irI#Y7Dy6#B63DPJg;xfk3e*Y*X{<;qc_Ly zw|)IGt2J?$(!#TBg`}?G{2c2`sYwWDCHdEE#+VY&-0BT`$9~r zq+7=&OG*1azM0R@3C9S#+N4#C@B|G>yAI&=I2(Yuh#TaGLY^F*B(CIEtOQI>>E&kD z7=BR;bX?Ms$+;;So9TB4a1 zYoTrvRBWXriN;g;^|`_EuLk)pEMe!Vzu&=s`Bz^KzVjs+uP$Yah$C+-YbnGX^r&j!ohhh2~%k( zEp>JE8U^DlLHY*Qi!NMesE7Hd%IrC*>BkG3S2_r$NcFzGLQ7i35w}0E>MzSA7I*V76`X*Gy zmLDE^Td0bL50+jSBNU2yQX8%FLRqc9z^pT)kul^&s6z5RU~I0Pt%q7Kue%*ZI~JFpXI?q~#4b)2!8A0UHfO#hMa zBM~ME-(d;t755r}3r6u59!Z2Y3_PiN3Xlf$Z_N7a*bXcTHJ10u*<@u$ zDc+~>U(?fL%MxH>&Za$WUk2+mX9X7q!g~bvcOhFF2dc9cCnPeaUTFf4FZF9-Tl?9M zTGU`qM1LO?Z_9s~^5ALfe^f?Jj67QjAD>)e$bb1pZz4v+Ls8$@?b&m_ufv*=XW7;6 zXR^@7>}8v#xb&b#cKv>lrry#oTFt2%LJ)xn4y>Z~uE z3UJbqUlEXcdRx(@X6{l9N0#~zJc*iwx?+dq%=)hGZB1An9%+6`^eP~!&yI*J)QU4Yqg#UZpm5!F<-tzoO{^tOja2;yJ99zL zP9loUjKkTUb;Z9(DW!*7@9r7&>%}cPWjN{XuLbCt!Cz8xUMnw8=)281abUWs^g#Q9 zBg%fJYw>(#6{bJOi3LV2Yw>|t5zTbOf1`6qGW8VG23(45IH)m&#`D>4G;GN;JC6rv z8Yp~Ky9@m>{qZUxo+0!63Y6<-nwdPxVjpi>ZLLX86fayQoJsSZ7*;S;5wY1;$w$g} z1jnWsX3lazbiS{MK0;7!{$eDd=5W-?wTZ;@g7KLZv98Z8%63hioc3R8ZhM{|bmcRP z{h?G6tKCwhpRN|XIzFq#0u#MFmIiqG=U=<~KXoOz=zzWh#0;N0GQi*UBg_B7Z zb4+PV5!Em*cVbY3M*|n+h}P%;%R&c1I?Y2oAsykpHJOM6j*#o}gj(g!LN$ddg40#$ z!)5q;=W`Ge|0o_hW{9LVPRwzL?dhevu}kAktsxYwFx6)4_G)}3C<0KH=omL5A_Mvme5i|hVXE)w0>SWTo=7sZBFQQ6;kl8^ z9DPp|iiTD*U#DjPp2a!v(R-(_06%6=3?cDQ$dR}N)m6HYRoFp8LXG#|N)hTB8k1X7 z>4^sB-b%nRKFeHX-9+Y%O}=RZ;AE7&O?=F^baj5XALTpK{j$UcL#c*mB>^5DWo^kK zO*-No0w2`e$37cqSpMnijT!^IoElUr92DVXS$VqHWubz6UHaX!_l|yufbsJ}CJmL{UHstMF50hJXkKeG+mk&y8WN)lI}4=@5*Ua&h8`#d`N5+g16chJ zEdd{1#f{ZpMkhJ@kkbk)+IFV#UrD(b16G-Dz6 z>|&m1K;JPY&b92$mMSrLqeQ7N(xuLU2Eeehue}7Paqd{#jV> zujTzG!4UP^b~h@zfzHgzlA0w(;g!d8yz)4EG_4E-(P=t1l_0pF?+*``F~oT~_vMA(rHV_vUCIOmXSnda0KkumywGPz3)Qv0^;B92 zi~%_flr!*_W2-RU6+~D8Ax?aFkobaB8Q=Zi-1kp+@?RNh2eop#ZElhPEQiXS6f!HP zSBB?75G)7)S$}Xyho`@P2xb=%`J(eSMKCTgaf*Zj-6L(!lbTKdk(7Hu+W*&i%>7?M zBk)x`N27nHJnYkja%Z;|>-j%)HR1DA&m=3G=nKnL=Dt&>jsb3W{1*}=JgXmM6|OEt zR@fEqE?KDA+1d48&SM~kfU_d{byj@xtiUBKrUvZK|5qBfBB2A@6P^zWo~!4NPza{+ z8Gp(S9|0X3`-NqOfy#h4TJZvXv-d|hP+r8BN=*R4oEHB*iB9I|-5*}wzAEopgXOY$T5s`w0I$2C|872pXnqzFJ1QvLua_GuBQYleim$5Yj~ zYwRv1bK!Ze5bqoIsG0sb-kG6qAqEbEiC2&Z7`DpsAGO`M+SZAW3rvbZLbpv!D%S%E zyGy@|6E~KK%7=kDi~pXp*?Y<8BL>oX(x!PQvE@<6FdJ{>XWp{!cl?a@kqM6+OHDpa zn6qe9IYyqc`o0j(#+t24Bz8cFHZ0>%#cwRp?~TocN1KREH1DSJcf)5M8}I=x#kc<^ z*|To^dA=jE3(2HRv^0{2)z#%0WzH{opkVmVi{v4;nFh0Xqs63F`Z&(K_-(K-a?*gN z`KzM`#taxiC%=j-5eWqtoWy8eV!tl{Dtsj(X#A3-{#?>=v~w z#ht~u&ddW;BBs<#T%)$p${9}X;K$#~>$Y0s5!JbbMkP#zF@^MBlGN_T3g z6%XI;DvQzE!s5oz=6hHRC9T*gYWC(w41=IJTvbjZoU^vj+-salSUh*-9zJ26`h5vj zuDJiJx1i$-ZhpAD^^CSOGpJ_A&doU_U$6|k=&(7D6K9S7Ru9rTG77I$2Ev%5)wj)b zby*aG$>Q;XuH5M>UT~KH&73UlEes*S{Dh(n#Ylf$!|cql;WEcKfIoP*q2?`sawI51 z;*0NU8XDH*Y|z5^8D2Mb@B?Lya0&rk1+hsr%hFQo zipM)NqT4WgqK5>K>`ZFs`n|I9CKp`ohH@VrsArGd?Ki{M>!FlMw@sH2j}X}UQcfuA z=|0$#o3C09&W~k%IuW0qj_XChMI)<}N#W7zMRohzKbM!9m{7q*EeSy8WAe*Kc_bwz zi8Ga7*{sQOLyE^k5pxwEje)t;z0{j)(7L_$b8>$N7nLXKyLks+oCZjA3?1~XHxMzG z67=U-1}Tqbe5Zi6+}@jVeSq+%&RuwSM#abH5`^S^?s4>hC<$<_!rxMXrHo@Ux8}>o z1VAbE<^)fk=a`>?ZAa?N%Zf+hn}Re@tA19fRTF=oE?|ueW0h`Mc!O&lHzvq${AQ8t zrl{i(6*_1l9!!Al!LJE$UFu*w>h35e^s7R&rERWs$_Wh)R;H7zhr_#Pvd%|*peVV z>ZQ<^XEg3tygq@66ZV)h#A%&&94?D*VN`&7$dFipCsYOgBFzlbUp5e+D*EjyVa?2| zCpKHllQEpW-_W2nqTavzt-S%x*%sGGKg%HIkfgVM4pRJ64R#HLIgpWujUsc1bZ@do zQ}jT6z=11-R@f%Q)Bgf5Z!52rqBT?mcs<#3LSU2P;IF{tyiG-dLGYSP=r(t zPUl?DWihZ!HrX9<>y+t!sX_Da&n1~gz;s%TO+cWdx;<*E@_xz7lBSlv?ar~@3enAq43larIQ#BX8d~EAqt<+*6IXi=K(TYdqpD6lo0Z4xr zei#DeOtO5*$;pv9y}rqyysI6e%<|S!9{zoS1wb|Vuc`cNy-ELVfr~_y^&AltPJk|= zK)@}3o*REa0ENR~5MnlX#EoRhezmUgwGSIE9@vGM8K%*o4c%uW#7FUDT+#oe&)h)q z=y^RbPyV#_^yZODx7x9Sg|F2cMubVSepR43Lhs=zS{7{k?Uu*fCAYd*)U>qLZx&JO z`Tc`lWrc8abH@O=bH8l@;p^BFF=FZxIJ;*einU0LqArmY5g zF*2>2A}67`I`urm`t{{aAyoh;MuW*&mqFW2vvrgV*3kl4_`Il1u6~Q?xh$l3x`B{f z`de)USn2G`_VYdadpk%Vcg}WVwGZ`GmOLx4dsi@@9frdN>&{Vp0v#C>!0C<$3Tj(q zfHWsX+;jzd(SO$q*PiV_-Z&?jEYnyb!&7<%TVB;}-qaG$I|({TT!$UYy*sPirnH>3 zCug$K*Zqb)aHIU7!FPH4XQ76VkB=l;ucxr3tXJ9~Iw6?J->{$(OE=S-o2a)+S2Z3f zby?&fu(7YO7!EtRjE$?5Y>bPKpL{9qFaQS?b($3x@>XG&ABy;vG%6Yz4WR65wdVlh zxZ-=AL~I{&riPr?_#grH`TY_#T|K>t`L2$mHmVp0XBjS4g6^-0oZNyiY1_lLsL=56 zla1J?8gS~hR$s5|TPE!%0-y2O%F>2Do_}euN+&!u+#q%k*f0>~0(Nf}`=v_KWjN1B zM9?wv!xQZy%OOHZx6=0Y)#geZPKNdV zwIcxsdoe#g1d}I%9v~D%XiB=Edf^hT_zb6(7fN4#j4=DPg|ZK|WA0!FoR3#OZ}HRS z^ouBEfuYyaqBb4K2afy4(Zv?l2>98F_UpppBX<-gQr~F^)=n^NSFUtO@u%^hqbNPm zou$o$`{I4DzxQ!uD%ZT$XY^a!(kxKCqf#CWjgy+7(h<<6wp_A8hrRGrQq8FhGk4o} zZ>>K@y8+8`taPg4#{0l`4nvjehz@#Fqk4ggZ-J!cy{RfoD507$pP&4=%a6VYHsGhE z$W*p9C2lSElNEMy8(UH8ZJ9(qaCF5A^7sspG7YyEbV_S0gP0GB+tc7R@ldQiWgQeRFN0ZN_VqI2Yr zISwX!BOGqcE7M&sCOi0(y=`Q@l3!-@Umd`vr$5>&h(PQv5UeqSX8WMW1Q3$jy5^yE3S@bgz5r#-vURL+9*=qXvKlF$9OJ*Sa6Gvn%70+$UGpc$g zir!93t<5=6J8DIGPaK?w-&O`rl~MBQ7qG~ZaYfBuOUTN2^=546>aFbot?)!HRMxLu zM3(|Dh8^(?i?tnbks}uQ>AGGyy6V$Jr^Hdb-Q+0MOwG9CIc_eiqoWh|E073@Av%Ma z!@VSe=Wr3<(1Z(UN(gfmiGqj_Xq-F|jQ!Y#{c8YW($4&I6@mU|AO86s{>h7f3kl#1 zI3$E5zo;l7>Y4;eom|Y@w_Ke3{E5$>KgaBj4Gm$3hljPbwJ*(`4CM~d=|#`Z&feR- zve|~ly*8iR1B)Vg|uQ)Dc!rMxI#_xWN$&_t+{EHX#EB19- zrl#ps)YQo?j)j@G^0v*HSX)jD4$r3F*S;&=``#A0Szvyw^5b(dt7*MQ z0-ezPj`#3co4sG(j7EZ>ihB=BPzX2v%lh#F2(Qgfj`<&{t}k|0Csji)UYmQ@%@=2z z;iWiH>Bzy~xD{J?w~XcBG+n|oYHc;QSbu1JjWaKeMdoB9n)TkYt=RO#xQ4^E`hi^a zR6CRPuZ{ChY0dBw>kT^6`*WAY4;Hg+evXy(pPp`ge-+9Y3zq5X3pn|`*zVeVT=h9c zMAfBso`$!b61MxWXhw;gHDfQleu~WUC_EWC?Q!*guuk0(Od)-pLguJcEm=?@gq-e@ ztQHf!X`^?g@2aDD-0EbtQox@DdFewvclaBA-4)akSXOdD7b)W0-!}MiHNxb!%ea4|EW1wl3?lED%*)DUU)$Jz#uQvBK=6T?y zj=D!H*2_cr=8v)LYDpNYwuqRZpdj4E>S>QjX)9WOGae@2UvLDy2-m?rcS!@w?pL3Z z>lVHTZqtuO6*dpnJe%ux+f~?A6Zs+?^87IP3gor)Xz1#N5F5V`Xt4zL{Yo9?J)vx> zCoJ>TXI;v$=8soS^(r(W_&eKTG%3L2W z=QvoeeXy`5q|vzZ%Ix>e{kgd8%=L*%RmF%aAE;5yp}dzt!9ts(=CYw79c7ur@xC3! zoMO=-9o0`E7sCBTy|?VN449WU>SvDEgbXkNd!LwCYEAz5K+N0LVAszwUgex>@~~f_ zzoyc0RMCL*(7Ycj#ccv(*%tOBk+Y8>9#?+&3vrfNsXl+~=^Tg5vz@|>j}zvYSr%)-&HJ(?MdOciXE+c;Uv%E45LS_+X`zWBpe3DDE`Z&;a z&`|xG;)czx#d1vZ$#%(nE3Nra1h`naR=2`uKV4Tz&M0})n`49`U{7vora54%mvfu1 zuqRbaNo*ud+OO`a_u{L~&2Ij1Z!Xnma*c<}c{0cOX%7dKGvPvqg6c~Jc|AknJ1UZg z&BokIT%KQBvbndJUu4vmPD)dN;;_|5LCg2@|0qoYc@zhyDw=}%@F$2Frk^JC75`6?GP zRM5cIp($OYq!>KS`}|!4e&Z@NE-tPd=gF#mrljSINz0#Z7R$W5g!|nz?DK`mZ%Ydq z+b{dr!pC+$fazf(H%SvSEuTy*H$LK~j_5T2r)QhDS4O*C0}hRA0gOCIva!96DEnlp zWUxIn9IvO;5m8c<(ED^7C4X+{*ika!yE#H)3oX85UM;_{!Gu6zf7rh%E{<~1&2{1Z zW5m$5HO{L-kytrFwbG3Lfo81Jqet%H5 z!NT7YZN7I-?0I}9Lx68aggoXP%4k|uae7gWRDPNSn$A|79&OtW<%50ur73Y99DfGz zOXyEf39mYrI%;xrx=dgH-aXPyI$dQmI;L&Iz5Y?~I(pLFzThiwzFD`P*gaj%Pb-hEL9FTR%T@=T*C*KccMVB{}{|tcyUpnG2Rm}tXF*;(dk~?Eo^GY%YF_N1d ztVR|rc$z=ns%maew|0G9-q;${>~8S-ZT%sL*r=&T)iL&vy4whoS?wW>OR~xOHgfTp zlPA^Oe$v-rh*l%r|6mE#GFQ^SIo~}$V(c1ec6yzMCtlj`Kz?`LV0X~-7=}zV!?r~( zcPPP02P($MkL!jk;5AT+vKwOX(FlR(Q8;G+E1#!2}EbXz$uU zlPWUn8Yi(33&>k1|JS^T&c!bwW;F1Vj6ZH3o;UsLfzqkW-ecfGY=}u92fTwNM{)(fAb;Bu zrWF#VF{6=}T9yDUX$ymndM?5kJVt{b%vMCsx_Y3HFw)H%OcP*12b9ZUP}s1+aOfzK z65dO_hLFX`rkR0h5n!#KX;ipJ&W!?Du2P3@)Q&b!arXB4Wf{2)fDb8aX~is6wH@SF zr;bvjv$VFhws{ZWm;fq7zt%}`^1aw19QZFZifphba?8=d;d#^N!knBIoD74ygQH_M z*lm*IEh$QzcZh~Qgwbe=9n3=GG-|Fm!9<8MqG@?mq-BtMl@uCR<(r?8L4;85`_f5< zYU#Y|ZbXIXB&7}J1zioz%T&8(klLNzs&k0W7yQG6pl~eF-7-7_-=tI32)~UH|7KZA zj6k&@9$x093?uCt-+?gWo3FjS!Mm4|8e(f#ohe?8+>*yA-WbFZ1Asx8_3BXKdOM6#0-=i83hf9V3&Rh^MVyHK5J5#| zQgmE~7D}F?J-|Q~W8`MU83GmF8y%s7?d0gP(THbf zI8VU0k(p4yN*Pwr*i$}@qY=2k(eF3Cpod3aBuBID+M8lcUsau1)is0?Hl>|bw<QiP2{ zSIJPY-G#HTU90`;D~PfnQ)8WCsNz+G77A_&qzXWR$L$)9LA4B9&la7BTfXPm#-Brs z-!;bWHXPa*8cxejR9q6T)2URu>DyT0Jp3jUZsV#V-aPu<*S)ngXtVQkTw7m1*Uk2n ztCd#8UP(o^2axsqFL_`5hA;&Vw>mi@iM7qNS$$IEi;a!h&6i4b{CyXF*p*wCZFDf< zG`z7k-7k~@@mEU`CWTv6ewGKP$|2p!7^s+Tz#we^sFAB?iQz@4je>LdwN>Q~nVD0Xc&eiibR&?Z~C_642*L6)* zA9nSI=({Vf@6I=T5>>H~)p;jnd-FVOjT*gUkF-QRRCGq;F5XMzF=SC;0mb~Y#fwRV z*F7tK!YI@EB@cXVP)^tCblH&#(AHLh}cma$&^_H9?e zC?q;e-Dy*B*xfkKZ51mhMh+)grR#AZW2p^=9#P`RdjX8aNcw|rrR&cR!B(I5f|wa4 zpH}|(zTm4%_#Df++n-j)u1jPmqp+-{B;73N;8G#Gr_e>BN9|G9 z1RpKyimw!w&oRvGDb}GR(H*lrQZ^eu7ze)Sq8ryX%-KZTwVE#sym};8*Jyi#0k$R@ z{zDmQd8Ouu2P%j7CD{GgZvJMI5DF$fxzLO1(O=KPIWsx6WPB)W_w=UhN1i2Z<8uW1 z(<*bkr0M=WQcT6d;+u5#5J&X6i&v8*0zhw9P6K*7Lf(8QYa#P+R;T>;^X8! zvGzyFb2}rmsPhsm0dM?|#q(d5$5e>^PcfrVg?oh|IXsq=*(3&P$qj#E0EK!HF(x4W z=l0NquAqTr_6~%Scf10k*WhkNhf)8WNO335x?mG=8-*pw*^?unR&kU(rkMS*t0@Z0 z$RU-Tk@6}}BZ4b3GE&>zoFCB1NP&qy=4hElHO5r|BNbKENasI?PM6vI>Z`;CqWR+m z|D_O^WK4Ydf?0O;1g!R%DQzucy_jMMgB!2MBerv5 zq!B22Ce=Ky`Cy2z-X)5saBZE+b2{8m`ylLU<^Jg+-->SIMAz=?nwqhEk+P)6NWy3K zHS#v*tHMt0c}0xH+?8cMAgXk-;BN#e{C_<}`+cBUJ;-}Fy;o6DG|e7aL^hwDF~6x* zoxLq$_bQeXmaKgp&NDTp>Oen%Q%)tJ(>aK`I-K9u!MJ~$lQl`mJ_r*0;Lv;>(Vr-r z=UmdcR>h`0`YMrXWIdk0#0wH-I$_<ZcNXt)_BY?it%dixg0U!4kGOBxbR&D=Q`*@{W0y=8m2oK zDFG8t7M8^blxZa0kZp^)77kf%3JQF#fK;j)SXSSTcKX=l;dErVFB?NnW|5h{CdKBQIT!|6sumL<8J<&FUks^n(!YH`ws(l*!wm zL#t5_Hu>TioWH8zhYr)4GD>B}C*7pPsofz7O}>N+r5IINC@P^(483iyf_X5r#;s7a z)A2N99)`$IK3abvgIk_Fhb)5xZ+?!44vHG&$QYL8k9iyS%gS{!an}uH)KJz|&M|4b zn_2_8FWoX%(>%x6#j|&P3|(Wm@t;X;M~UDTYw&!3uR!jyBvhn^YWZimGnC*}zaqr-%> za*R+2<)_h|bbweBW-`KKgo%j$?l=c;QVhCLGCe;8N_)mKd>f79@>T0iB0_XtDJ|MW zwa|Dq`ca0NW#|s768+Wgiy&v!sPbuoAnk8f?rk9B+X9^vlrU8tE34tq)@DwM82}A_ zP20E3Xxl7YB^nt^#uO*S9ta7NI2A}D$`s68%|T=w!&{di5a=zP3-Ha$3U8lc@Va|R zX##*V3*0lFrwrrW^gd((k>%$m5`jPmlO0ek$=9`ck#Nq(_PGck0Ysdcy_W^>uIOkG`F+tieojg8GM@3B~MRh6ib5MqO`&h7)MrEltDjRVphlJBK}Fuh9g zyXWOGFyk5IY(Och<&<1*<_v;V*y8`#kQ?E(rXN!m9YJ}#GT7G{%a|S1ltAFBK%}O# zdP}eVX3@2fDN|l7C@ki#V3t2C#A$NJv4Q)AFfI_1^(k(SYZx#^U++{eMZQBY8%Xj~*S zUafS3|2g#2_RsHR=&?9~4tNfe*lbca>HFvm^A&jTqI^ zKJ>GQ4j!}0w6{gZa(=xR&WgX6-D7R__qMR059#)55Xf08hnxfimov+}Gp`Vnaa0$59cpGiEzM2U|ZE8=FO8$Lxg0ul&7jO{8=C zt89XP&dHypHy?32HgnTJO?S9xa??me&dbi4Z#EZop1=ZHlE8HI=Q%;3A*VUC z(tV~?X?-=f2B6eMG++Nm97N-M63a6=Vz^9i+@14jlo_wvnU^}ngKuXFFzM>*%5j}( z($>*owQ7$_P~~sbjk-A-_VB_^Dxnb+HX{dczYk=0n~_4p&2OPhiZhK;fD|tcybj-Z z>F&H32kGm2#>=LAsLU6fwN_h_Kw(EqMWRl#@=A-F4jlG(s+)Pi4>h#8un?Hn_f|CS zdDj)wM<>E(&(TwG zCx)P~v_=um>|c2BdIkEOFJ*&s!2kaG(xDZ9jO;XKq>H_wPO#bH!-J#$D690jGyE z*$cs@0ejc+Cdr`DF?n|Fv1F#a%neq>BcOnZzqfyUxLR!k`pQkE&ui6!NfxF+^Yrx_HMPHe%J8NI;j?hX^=r8HA2gqvu}!}D^OrM>wT zCuD3%C&wh{8QOaG4N)!oVQD59gqt9ZEiG6)l70Fk8h6Wo{}uy0=eXIq6A8t-J@BXb ze>=PvBJhxlSlf>c+l)A+`smn!0c|TOb+NYv1#wwfEZNQ+x?5r+(XLqGe+}qi-<%Ef z^pw54YISvWudF%crXMJNUS4+K{BoNXX?dou?FCAnRV`748J#$QtD-^1Mlh6r z21he~++|pO0!7UB=FkD?;#Lp=1)ScZPL~X>iC27Ai2?5_$tH>jA<#Zy$APq@@?`HJ z;40_a7*N1baoU749-Geqv&gF$=O!Hghy(>&F)2O>9R0v+0wy8-y2p$eoC>kS213Fg zbEv=^*q0>`LTDa>oQr>Yo>m1E+^uIn1gzRRw3M#RZx;{emD~2FaXO9^#`LC3bOSxb z=F3Z2v-PFH@R=sR`YE^eYn#ylM{;E6&)an1nFT$5jx9FJ`A+%mPH%n|>I*v%qi6;? zVDn~|SEULg@TKAyrMx@361eA69E)Q@8NG32WMuBe^IcDXjfn%k-F(`E-WY-*tTDJ^VCMCjU>P$SEn8?z=6dd#V8I&Kdw`Rbry@X+cSo5irBJ2mr#q?M$s#`)FlEJxb=}aq>+! z7DO4f{PQLhmROQDx7ess#ieQgM<368Rybd*Rh0Ps^}b<_L2hpD`3RxjUk9$ssiMxY z+{HC4-d%6^-A<2pl{B~C*fcQ*0kxMQjKe zA!lt}#EXoiRv!S1QN_?OInC=wvUtxU=U-LMlXJ7-{4dTiKEAg;@S`V{;~bN3X~b3W zfy((@qrR1rX1;RUzNi!YDkxfSjri2RHu_Wp-Ax&I!8DDcd0(l3S)g+L#E8)Dn?>nv zCW$!eZ=L%|Ol$JzJfn1@U1jE=ri>|o#iquR?fM*hxC3AEH?1j#OC;a~?O#gN5G6rv9~$ z=lRL+=PkR=tAs6N__%EG@odMx@aBj+kM35p~r3r}i zA|envs32W2bdqQ&p(OBD(0lGZ?~VI@-0|KSPtwujgRZeD$q zx#y#8md*UR!@5G@`_#4JI>`HD(jLSSAM&(I-#ST2^LC%& zks%C>35kWl^8~t>px03czM=eK2n-H|#q4E*GoAyG7#!x-kpLitF(H(hH|Jx5VQ`lE zMn1-Wf~l_H_g_>0(!gID`2V7TYq?t%7Z>&Q_4$(I-K5?W)9kH(jJ?Odefjbw#p9f% zy8NB*&*lb8_0DQ(g%LxEglHHXFWkQK&<<#d7lTS-gLiXC@&;f-OJL9x#P>@8VcXom zIP$+6e^D~eiMC(#i`~s7|76glMrI*sVA1));NZKQ@u~AK~i@tOE((RJ+ z>cUhS+n3`wT47s5clUUXm7_@mDQZdQ*aB+z|J(+2aIgU{IF%l3=&Jw>22 zWL#Kk#1LaayP>H>RB(sL5)0y5E)%jr;v6E)rRWHbd1K9KkX`Le1VQ=}sjg;pcUQj6QtOa@H@;0{M!VI=xi)Toc$w2IS zAv zh8T<65AT-a#nF(2NOkT=m(Ej$H9x`}r@H1(7TOQWlrQ$0Twj{&?_^43!bq-#O|ju2@l$ZuD9B`B0kN+}xyGCbZ)fJo3$9 z5iE!?rWa4tP{9_)n|c`#M|mKq1?!K*E1s{gHKX-T@*cjFi!*U3R~4`NUQZApxyaUT z(8=Sp`OY}7uD}XwjrYI{Zz{?8TbOK1NuT-C+)=Q5zBIE+cYGiVn(&sHC&s+m3s0wx zrkqq=y2nHN5`4wFJt6kcJf&4aZ++-g*m2jXbDpH~$p!cwZ48Q4dI zTm40ooowDZZ<={e_l6LwD!f@hN@1y)Bjzr-XD5 z1dW2_l2y#(%)M;qc@*Mv&8y`lcx2QttE1^@30REpZC+Fo8ZMfM9r~b4IzKr)n+Jt7xiOLY0Y9D zA;YpUl#MFVFwA}D3smckD7w2sQ4v?7ePwmoR(`8rY6YC5)xuOaerTfoX|%eix>8#A zU>5}ufmOy|cjbcr*s*PMZC}Kg`73vcC<~uO_R+%==3C^9F{GNAnHfTNJmChY&2C_0C#?HJ5z5$lHyt$OCTA>~W$7k=qMwp2b)M}_s>hyV8GW`HcgR5fXv)^qk z@>yFVm?__5X4M^WXxRl1hrvbbxyw9k$~>->m5%2b6$?co;kITtu-qpBTo&2R*^!;= zw<|Dscem;>2j_(KBUP-~TM=+u)gF??HIEU`Y}#sHt;745 z^6@KUm-L{Cg)I&P57Rv z^Qnd^%k^@WNW%&%8%Tjf33zvZ{Ql-*w2HY!#u=N0vKijU(|s$Q>QW)tm}kQt_%aE3 z7x-<9EnjNV8&8FH*6)G>v;Dz#kc8jV>`@xt8yUpHk-`_U*c1>Q)2-@#jN-i|hkeGK zKX~-s5kLC*0Gvhr5o5l965O^k<#qdFxK1_GDgtCOF3GRHH+txZalCNKP)qCzk2>b7 zHd}DVZs@fchDeWoP8U2p)k2%LgEhWGRItkJ({}E75OsL`xSUe!dO=b}CGE1m`(FAz0w&N*Xd+T_Qfc zD^a_qNY5Psb|m#PhOQ$UxKah7jKyf6S+X2Dhz&#>x6QZ@;@R(FWX2~hAe3SRx&JDR z;(=aI>J9LrswcsjU-AICxf!M5i|83qvYmB*`)JB*G#NXwZ0M>08ZR$I%)eePiQ~EuPPt8OVILY9yaL^ZAiTr z;W_Cau`CvSXaS|+RFY;Kj17Gswi(OH!3oxKzgN8f`yqpKyzrz&6$TxcWd^BbF9hOy z)^GQ*d^0dGJoxWC-0A+Tu<-ojBSw6!aT0O+4quWccU54|x;sWHnwaD4Cw(mxXL}Ey zFhT@i16tqJ-+IVx(!E|h ze~#ll^PssU_Ha5%qx2NH6+-LAei-pJc3+}fBcckVN}LWM^s!|Rc86h|1Iw*DxjmQq zt;fAb*OCwU(SA^tzeTh@K5CLtQ6Z1s=j4Y!eE4uWn+AH}gRGLE!kRA{+WYE0GQ;0# z7Z)e=ny(E27sDHEPk84iI~{+<`tRxiRom+AF!$Gftd3e)QS+mVMB7PL0&< zA=vGzy}h^DG{R3($)ggb!|qRa9+c4*uUQ^T(-@&lVksOjiL}YA5G z8210tu#SaLHtg0@XxY>@x4KfBC_+x8<+YjW+1uycE$;Ju@$zNMW`tQj{UTQ|s(tn= z6DwLo8&Q6<7gBoq z7d}YK$h1HtYmL%}Ig3|?(gf?6S;AB!z()`B;|4dfvlX9=HP~C%9F_HTQh#XuQE%~A zwYrKyckjVs8q?GM>Zh!2_qPxVBzKpCaH4vo*t&`jyH$7ZBcp4+WCbL6D zC498nK#_P}h%VC+?GdH~@2_yniHV76erb@C0ZLY+O2K7mG*TtRtH0=akvq{?&gqN4 zcJfIM=)iJLBr9}cCl@EHX6?Yr#MYj@EA{txWU5DSBOB^Qp}Y1>kchzWJL-Igf?k$x zk{BpURWU0!%@w$rq2;$qtFql}!91l;vQWX+188C0nA_hPc~N@iM%-sGzS9_#lCC7M zjZR=J!q53YboAxr{TF4eaacwUIE77h~U2lg*C?%<~H%DWF4sL6WYn5p0<89|^@`=SyXPOb# zEQr<)j<-bo5X+dW>z8kgY5WaP3grv7vY&UqV*8V+tLu``8aG5~2-P=FsbX-U+t6M{ zAYvW*6AFU=k*eZPSwysFTbV_uNs(2^5sMnrOSzZ3t9JaVnz+Pq#KBT6H@A|V+$S$T z*v<7);s)P#I5Goi>0o1n15C(te{ps$gGA8yu0tihwDLwxCKVNuW)<~QCRD2+P^;DJHF5gAO*pq8FMG0L;O3?lIV#v=Y`jlNa+>8{j!g-PR%3MC~alq+X4L8P7}KWhvp z_-f}lMu_XL#QHY6H@Z_aVGtqu#y7bvU*rT_JL_%SB~ z(qt5sI4GQ%naNScDbAY%nhcksS*pYlY4^@xg4=Hdo-v{G~&d~tN_{O{K>#jkUCi*vBO)8 zaJ-?4`narSX~0hZ!Ug`~8|_iW9r8Jzgn!brcruhSTcdoVsjwL6$m$sT;!93W&Zt`2 zDeon1m9GLluHIkovlGVSEFM>qDztVVI)RB6(uo3|hwryeIa*G-pl3|jB1xuCO@V4t z$gVG6XI0XZLbw6l8hoSUP`!95{*FjrrbnpKH*PA!5Pysfo9dAsK>@H(4( z(DaD;2TWzZzd2e@G5#)d?_!@t%8?g2InnXQtXqc4T?G4l?ze?mTE~IjmV=DB;e_IM z10y5PKx}^Xl5xDG<%2}$7E!!pqD|rzQq7tJ-Yun%<ZmjT;sJh60igl2t|#i}8gZIe$75l(*6Rt=&J-erKK1S=;UUho}QTb3jb8)EWZ z*v(ucO}(Zw+qDw40aK84GRZA>ZF$9i$-!eS5paI2)T*QsLCDl&*bP6+pPlS=)0vxU zTDr!b;f;uzt*J7*sK&Z?e#B?%nE9Ghfv4&1_xtcs^fhYh_pk4w*9jF8>5(o*TZR0w z+jo|A1vzSBK7IP+JRWP<(pR%oLRio{wHPxuP@>fs%E8b0vXVE{AC@DP1=yiSvphZv z=P6$CmG&c5a-IZvzxBI#Hw$m;)|J5^0JwQbo(gtaaL?(?xTx$y2_yH9U#&;POI?3x z>hyN&AHJ*zpkacUkuE36>@rC-542D&sD*I~f`hgpm|lWdFgW-Y@rAD@dx8}v(<2lU zG#XCH5=R22lONbrj8_zN^$m_RMR<~r7Amc(y2^eVfv-37M>I2{7*r57=u$} zLPROLbRdDd)*+Y>*Z990>1I*TEB9RTas6PuJGgLxv!5Qwl3fy?LO^%;&{{jtI z!;l002=ELofK4pKyQnc-T?l|~v}6$r<9Qn7iRojLp^O_D+Te1A&^0SGsilx-^f6V2FeB{A|j&E z(MA~)5fP^lK9uC(%AKyO?}&&HL^>!{(=hvu0*dU6o`cqC50S45cJh!1je-l}&g$@t zhz1nu?t@;H979$2Yns1bp@r}j`r`OlXnb0n8@=&Xm{N3My zBU_!dwk+AW;*A;42o>OCOI$oq=$zPO}@{P3c|P!BF|3t8-mx&6@PtD$0j7~Zhd(@ zno6(>1p$fKG9x2{WrjtEc^sxJy4A5u zyYw!{#AJ2u9#?PY{!D`J^h+ycspErt87@r`@~j_=RhEZg$fygkknObZWm?J3hrd<= z`_Pg4*_Vl3dJ|PE4GKhy*iMh%4mMaR7o79@P_*k#iBLJOhWVrY6L+5>VB`6O^Y_=7 zKP#0*Ki^5c*Di^TkGEEq{$KBq>exU{$NaIUNJmG9MaGxoWyr?$q2`~_jF*9&kLCV>uP6*~5zIJ`MDB4o)s-4N(mci3}nXkn! zpEyBr8Us6jnbBYvJs*M)ulZU54b#<^-Tc$lQngB|Mi^nW#IsiFdhmv)I=01Td7(_? zQ@!&iZhrN2x5CpCGM2-Jj91Va{`Q$ljqXqX8@hsWb%{2+?iol_FV62I%l?+vFfFR%f=+~s zb_7pp=+=znQ!_KO9aXpOQu8;aqJQwsCi?n`$EP2rLuW6oEiW$W(7Cy~Iv)So4x5W( zPDxASDKcZI?Dy?D`Q3DJpd)L#va+PBBpMO#*nAOd6K$FC zVQwhT#fKM^*e)b%4LS{^@q1+0y$Iwt$d|={l^*P3 zo)xujxxpHH`8pSVm{rXluO2pbFJ)lrxwqSmP71Qptm3%6>BF~Gq;!3cW7BpA+ob(M zs8RVL^z9E;=O-InEE}B3K7RVN*!{|-k2btY_orRwE$PBrt52KK&IU#5khUCkrv_*Z zjkx2jIBMoXGXvWr*o2~Q+Z_49eq3A}i(=U2P{ha)TCz*)FVBeoZ_j8a<1ygBpFC#9 z<=o0`aA|LA*5P#Q3Azjs!i^6*Jy>4bKq~+FP9ns-9m(T&?@#GajG!BUUoc@8*3zi~4N)?z+0V*j+VO1bw>t65vK zJ=V|Rsq+i^@qu`MI+W`|#81^<8(%Fp5ja|Y85!nB0*1XsXU87R-V+ABLxY2AyGz6G zbNTRBCdzdlm0!WxS>n^pkA80iEEj~bN#+Q`)Gu2z;0QkQ<6k~ffyCLFMjw7ET4+Kf zEB$7{-CJFSqP{)bJ6x;dm6c_A`t<3JK~0|TORD9<+qE4xb-%^&+y4+&cHRH=$*?SV zZ5%c$LSNY!D~p@93Y`vSuI)NKy!8l$V``;O_~ec+?Sv(x{TM2d`TJ5|Jd?i>_D|x! z_s<-P3%8Q0)q-OKM6V?;jB}auJIY?=PtUjLeHLzVBv$jd;o2YJN92#W$nYISI-5w_ zbOwIA;58ohA1)@6r$}*iOq(aUKX)Ly@p^DZ_)U+QNpC{@pH1X2xl5huVq8+i3j)tJ zGQUkw#J+5_&Oyb@)^hR$3}IgUP{hoWG_1kUU~bXVUNAT~c-j9u5{fGj`aI?hMKLRT zi`-0op4p0icHliT{>(#AWF8Lgb$2uS|%<-BJySwtj2UsqgN`5`JnNtuwc(BS_YF7Kf$xj6MUJjPTQ>;-;*Zf)Sq0P)=gMtSeUt5PlCGT%pVkZKz7XH|Y*o5~LFY)z8 zh>+-y<_kYSips2H>j~9PR0LteE0<$vmZ@~AP4?;|fn=Sz7bM#><0FPs4??fs5B0du zEL7{zWv`3tkAX;}JsxOi>(4zmS^o31P^Pf^dIWy*M8{@SYI~>GzVp^xrj75Ldt6bT zKSV2$=X$(+FGo9I>FMd;k$IW~Ee&7%Ig|VRCp@#DsQH;DLRpsI_f|2ILzem3Dm^n# zwKn@#+Ff^1L-o=xqOfXZFFg#4q|Gl1x~*@_4rh(K88o)oU(2IOl<)F5KSwruH6d#b z%P*1PQ(nm2T+%;~zO3Nz<#k*?_ajv|nQWPi*ENE^e-Cl)aorh>4|99l;`0^8#ALpe zL4*I4b}Q+_bdg}K*eg#aIJ~?j!i;Qi&Gp>)P2I2Jru1u`*5EVshtc4 zY8I6prc4Kn;a3mC0!}W7{)tE)b&_}$Es)s$@=gRbJ1=wR$Y666s1uCPah?Se`E;I z!3&{CEv~Yx6$#|x#Ok@TkiC*W?OmnU{j?OL z;&LX?h23ASzYOJ$@Qvb_h4g#QhEaTCNp;cJhrf0S6{f+l&ZeTCw8T&pnFSPgM#fXC zMI~|0GdHPDlKy%K!phA_``jfZ>eniU<=^AoS01W8eX@h-;vo&-cH0{aJCf-!NrRvh zACL`mBQe=;&B9F!yNyQ%Ep%eUN++254}bCf((Tub%jAD9&n&w_l;M$sAg0%(%YOeld%5teZF8RVV zZ%(_lU%Wl&Yj78=+xdQXQW0)STIOLZHly?rvW#LXyN04uRvbS!Aw7gt4ZoZOk1Kn^ zL)!XWUK1mj3m0|0NDRg4ztnX$LFft!iMZTFis|;hoyZvz53f;s|Ht;qdGXW}I_jgn z;g8$1HDR_H`Ekz$Okhh-U&jR;XhTJw(e?lC@H}qVptI7-|4lE^t(ajfR!~O0Sj$(b zNIG$KICgT|cH#;r7pI|W5sqVB4BLGlf7v!S{o$EgPnk(jNk?rrC$>@OjqwtFG4kEw znu0tTJ}>nn{Y6UDI_%Xu*^^(t+;pt6JF7|DL%zls8S*PD7v9ATOa1&Z;Od$Y@2IXx zPu?njYw6`cokGey|6CviZ*BEL#=U+P7ljKcm0b^nV@4IYDRviE>M36|!6wXbLN!rB zIg{y(_wa+TUk~2*_P*n*k)Jw;=4K3&3uX^R=q_uJEHe37)l`K9q9n;fS!=2c5b(#y z^$|%jXIpZ8CTyEqsEzMA;kh?)4KD&Em=#ja-1>OSu5h5=EPB$Lq74yhY9dL^xDVr6 zizLdPY-L4yvWvv&D~~ zyTmRA_q|z0e)jl1Vf(q0y80HyM;RgNgGKJZQNxEr8SUf&G^mKF;M>0Nk9um0c>9xe z?LFy~UFY2f30~qZpTeYXJ1|xkzo^&_bK^@XW_`IspNuOqE`S62ecHjg*vD1ET3RTx zn-20Nhxy_9j#0RGK#(>WS*h|{Y3Z83Vke{c4%`8CyxG15qo=<+(|LPH=0U>QReUir zvG|EVJOoptIK^?Mn3_2BsBwE`04}=qTtf_Xz9P6sM5$H!n>?^3qD%@goqOvtEOBe+EbQ}$$5`=-)2~^-L3Ywa&}k3bOh8(`+62)Y^*u56lmz?x5X411XQ4U zv@Pn%lN{g14`nYLy`Xjv`ts0M;$WxyP{wL|N315gz*Cqku=p}PyCgl@l$B>FfTC2n zXYB9kpByX~1uXQ_{o0!4!uMq1pW(A?1F3vFwlz0?ULPw*o_q6z7Yo6B+dYX36AI3Y zDBhSEX2w5Eh)SoKIrTB@_I6 zx=ho{-J+d`%$ZV?Qx#6RkenxuOZ)7zGd_A9gXQd>e75ZLrKi%(c+39{E$Z9Lmw11@ z@O@(q#t&kRl#dj)v~(xGoL?7utcMZQTzuw;=+GY(#!!wJ{3-VdepM8)qXW<-vxP(C zbMBjQ5DC(YPsXa%F`xUMEc4t_hcp~MYH>!*Mjc(6xwUrbU6G2zt55H- z08-p7Eo6a`8k6Pz=2L0J}W?1CVJjKaUfL98M>Sbr~RIXhRL z#Z7V&P=Qj|PeftVp_k(6pLx%*s*JQ>)xzqSC*5vpPGk@|@eKy? zCH45Y!Sq1yKO>rY-Zxi|LB5b3)W4s)h z$Vf2FxHDPr^ww%ZvAetbCm;^kBySU7{wifD5)m3bOEM&;dH0CrZFbk!aLGuY|k#Tfk-6uAbA*g)Lko zLFO|Yn{+?y@pS|x4rX+&yS@HK5ebx(=TJ~g((j#i`fMi18*dkNtUTE^7cmOVlsNz2 za4fgB5O`>E6l@c@ZqNk5V3T1qzjjtkTQ`yCvK~W4XcWO{zK|;tNRv}NM)nbIV?}2c6`4=NWZYwop+D9fIoFvd~)Ixr*<95VFgUf`@${4XH zvHArwd7$&Px%mp#qR$~>yXVQ1C$FX6E5?izD7au%E9ZezMlA%9q7f-2vyYuMwl1x* z%?XJHcXXsL35mKGk>iC#D~ZBP5!==@jfueW8 zuidncU4?wb!^6zh>A&JD#KqTF48*c{Jlwl8@!#4DVVwn9OH-K5e$n(rwb3rUodlQX`cX&+JLH9n*f1VyO^M!-vupQWy>WyK=@ik$@V z33nE&hzm|GRJ!f_=jR+Y$@h4J>AL$lGJK4ARFv}LYkBb{>i0?wiw@ zsk^+RK6>Y)IoagKUUOm>02a=Rf3Al{?B%S4)1o5DznR1UhPJx9TTQ=6nKOo*Mh!4^ z!vj^!nB(7h_HA_=bafl2uNJrC&k7(V$SWh0NEjBBRB=~*1}$=~TrLuXiL1DBMITEr ziCP+MWwwUzE|qvs)Urh@u9?n*(;|D7zNeN`+em{dE$QU^=$Cfa_k-Q@0gW5df;7n! zS7u1wpPHK}UCMx{BCOl$4I)8en{K)bk&q?{q2g<+AsVgx%`Nkkz3Q%7ihm$A&&}WS zStG5UY~p04RO})-`gb4_to84I+LH!$@Yzm(T7J$BZmLz#4HX$gB}8(~eLo02%BQDc zEJj{&a^ZhOWrVm$@|)HEO|^bRujdm$V6b3FvzWOTKQIeE)YmlMo%Pfe_;=HAtRdx)mYEN@Ug1 z##2!({oi7I9A9JAm(Qzb2x#x62*SqtE#E-EW0IwGURA|p=MoE0mfyOw5goyJulIw_Tyj^(o)k%X@cKl6SWvviwk0IZ%wAzE|iL@ZBU5e{9@jbntPQwlNbov5LCVIMUA|T5b{!I zI@WKC(#_afc7JPN2fjRe5*Dr@r*=0Za!6LZMJl97Mic$57fRDneCiQAY;CKiyE}So zUzcO$__jTX7lONkywqE`sZj?IX$6i2KcCANsCzcwQcuWa1&~nq`P6gzm1SE@Y4*)? zoV1~c)qvbomr-hgNF!Y(#&J(9*o2$*Mu)Y%LeZe1?YcNyb3CjQZ z6r(xeP?Q4FP(#Ri;{f($SWHtjLTdxl@wpuN*`)#;X6nyuaO}uC6T}p8Z$)LvO3B{d z3{7y`kdNT~>$OV$&vJpK#Q$Nfnv_1HiEDWOj6n;BXG<+@ni#ACkq2ev*PKy`{Cu_HBX|fT4I`3^>po@cHKr%)#s}%q*5;Vk=YSxceg5>sD8WpiHo0WRO}& zH@@^H@2m%HyBna_WvG0>M}|);Vu7L}O9lU) z6rO)qA!Plvo_J?088uVCF-SZFN}kZYL1}2nEgiR6J=(v&E4dbT%A*8vDA&pzE1wHE zs(_a`=$6#BC7}U53t)fXWS*pP0j2zl<83_6CzFC;8URKqTLI2CV5CDq<%dRM;t3Fe)PfaSv)7j(#`FfMNl56eS zjS(ZcL+jUwz{=)lxI%Gy${|}b4-a;iUAFN(jiwqJWaKn#v0bNob=dp&OOW}}Szq#q z5J-%y$N;#11U6xbK>cezWHYaWApDLDij(1axd74s zHec?h|IUI=v)3oh!jbDU$M5!EK4a4UBR+>c3gu{|a4i!pd1uvhzmuEzA9359wGe`) zs__jFA)|k;1fA3Bo)g+f?5EiR@7T1Qjs(ZsV=)mjnsXiu2vI!ec5Nu7O^ohErx2{* zsY@_3pk4Aau7JVHHE``<3xgxNq*0!VWKVn%R0AF6m_#XqF*r6#$ zpqX{0b0vk~@e6ZuK>RKUQc?JmGeS97%?yWsj?lFTzqG+G5Gki~+iko?3(TrB zwOmnql2KU|LD<9Z`c;~kY|`<>`Y&I;XlrX<<_(IGc+qa0{IY=p8KZkQrCy9UoO!vV zIsED2>l$u?Q{+;DHQbqdE<@<>lfnZD((L&(H^xzyWn2u@wEXVA;ZU6It9Z@*2@}gg&df)qPN7gt~A)c;EUtHu{6i)RRcuNc!aS^-q_ zaJ#Jo2HVx+bvFLg;Zp;6xmubcKg!jRfSJ*&foj;7&zB1_pY+gqww!f`OYYnRR$ zGDhh4|4$n;7*b|Z-jzpu*kyiQ>r6zYAFc3P5g(5cTG<4G!vaqj8Pr0qBRe8}Mos2a zAH`gQWiZ7h&bkq)is{1IuginE2jQyNK=PhzY^_(>LawG=J3x@-$hekDrnSRvdlWQT zgML@Wc__~6fD+58J*`T&Rq#{l{j8QtBk%3-mOx_gKho`d`5YM86FUV0~hfNk^nSxa!ivF z>k*zd!NkRNd-a zYg>$!Q(_XwOV9aA8uHgKm%8sA9xslH53_2KpyL#w?TmxTu01y%lpsZUw-Vz)I0&}{ zUEf&LMT2}x6Ft#Yb51q$hK2@n>3g3SpJqOH3s$$;{AOG?@UABz^&qRqJXGW6ZJjbL zZm-WzZtV>Z+Dua*mCP>0y2tah?yoq2_}8be;<=Wrx0XoBcHkwqhV8=58Q4V|oXzJ}TWC5-0Zg~pwfl$0jj;XIl1 zcI_d1*CxKxgPtr>2sUqN&3Yfwkk+!*@^@PmA|b{|=+{0=L}4udVZ~1~Nk|pLk_Cn0 zWHZe*OOZ9@8|{~%I2qo)bU@$gk&!ub=!x*65#}F%PcsoXdKXKWA9#VJe)mMN=qyqJ+ zptv?uV2kinWX}9Pvp#6VD^|J3q=0T02Dbk1O@Gyd&YV0we!NZ#^TcB%7Jxm5M*qj> z|Bkxm)a4~3Z5w5J_!vF>TRmOc)`!EJ=XHggt_QI~ah|1zcr@bhkCk;E1f2{%@81o8 zwBX@(55NiKHWCS0E;#lL(uYsYxiQ4ldW0w_RWlZ~QqW)c$^810u}A`7p- zdX+Yhm)CVNeJAkTz+h(ds!|x8ow(}w1Soe#^*(eT8d$mxv(+;6y7$^yzED-wPQW6! zy47OM%$(Hq&qevtC#9~izC;@%UeZb4F#u84;EuU@Xg81dcv!*Zt5T%KSrZK@u#VLH z*p)uRADTLtADaC{@*p+I6I)wNQzg_`%?Ec+zGKpkGJ>5J!jjG17FkmAs@eI;O4+$d zegLs30Jd(y0-}xX4_g}=I)6`jx1F6Hp-(EUHWJ$0srW?rNZ(6eQ;qV!{F0G*zP9ED z%EI)=1?azj9lCIOt8JpjDn_KX4H6I#@Y2L77O9-@uxDzLe8KjLR6ZglS^1EXK&Qzq=f?p34HZCcN(8d`O$ow_udX>h%`i>MG^W6MUlT* zecLy#9{2(2ho-73@zVREWJVG5&kAw;rv~eay@{+O+_2FixtmJ}5F`K@q7f7nbghV- zmg9-+Tg`Rk>eUb)KGJJdRj~bP(H0cS(M(P8H0T$L;)C1c{;GeWs)=i0`yYOINn`-) zPvTS{z{y|hUrfaCfKVYw<}yXC?h@1>;2|;+TK}AtX+BJw5Cb*Ah1l5G?_(AYJwUrA z!hD#Rc&^5-ohkHN0EPRzZ0@z=4Zr9|!N6jCQkl8ql-cYDXfq$V|*MU^Yh$d#S|o-_U7?<28+dRRyp`6 zY4a2?-4x;=07Zi!&5bV^NO=lbq5)=J^gGee(3pG~EJBdtXmHnRV5jfF2EO`rT{?re z$5C)a7&#O4Mg*B&(E23Z?fuOjzytYD2TwQwX#VFd*C`^D4;U&Ez5V?)v6be!(W*U) zxjma8Rw{^p!b?ie#gz;QEi0&YfAvelu^r*NQQ9N<(n!8hPOhEyPJN>*nhkfD81p;T$KxLJdmbO5vsonN+9Z2agAlIql7(-2iH>MaBLO0U` zVf1tp*HEn69>MvP0TQ>ygsmR?&A$56r*ewsZ3L~5%9ivCISI_XyW$BMOs$=^AAg{= z*`%xNRgAMLr)FfDC!^*`37&g z_R{R^>|Evhjk?L-EF3gus_TR<`QCn!O|hx9Uf13=RdBHLBWv$4^UsI}r9%1Hp3ok| zg7~GMfcG6&uRQmDwm_fG=obxZIny;8RE`v|pAa2Ay|<+r39Hx00_c91CWxhlQr_R) zB?!Mh>)Bx}=p@w6wk4kRvU?Iw1p~JOQKlgRI4LP9Sl(h}jC<#p-5SKqQFdf8h&4{(0jMW)Pmb z_y5D!q+M>{)ya!`d+}YP=qIEfN^8!kH#N~Y)SfX=TrX^t@ivgG^#g&4fZ66^HO56* zz?!gvY}|pel}@N(brCrS?d~co3($yC&NVg1I|MYyvavn5EhQOz-e!0*NT!!47bNlA zQ7ea0LJ}_v0ZAMn1^gl@<}>G4aiu1MR4xnQ#1FuGky6K@DSGT z(a>PY&dxheGcc%G*R6Iid+_GXAH6_HU%6oDa%MVhDN+o1@fG_*L4IGXWG$A9@?$ux{e(|%}>7L@%~0z z?t4B#=|V?-T4MF?j=vMSp@gKEAcC{L48>h(?xk4b@m4eu;gRR|b6-cEGBC#N(=)(q z&4QVFw*@|pwlteETN5;gL&7GA%#!ih6b2@qlQK_P2zV5$-4KL3KqL~6Y6BVwh&=bg z{e+qo>@}~~V4^M*&d?f%7-NL{M`^LE1fcziSI&uB(mRDC&&m~aX|rxPYE^vw;Wyw|t1mR`ZGbV@d9N|9F%X#jM+g==!dbbW z3kkRt&(Q+QiAxB)k|0`#$=17CUuJ?38&qMoV&)%m!Y|X}3U2nTbUsGr7joabgmRxN z5miB_Zf?N{?@*2BRA}E=GYSwp?ibj5idw!WuZK=E@{CI4aus?Vl#0J6(UP z`|<6x7WU!%gp@?~JW;$`V?6A!IVwnPrj4Y5=M!dv9ME3NTU}?$Exywa?p?S=3GiLZ z2uwf}K#u zU>l^NGJrn_pmVhIcBp|UJ%X5?G7Lg2o&p-3qSnn%W>7535wgOiV@S3? z8LG_Xcoe3pQPI%Q{J8)2&0nu!k>Oovv3A(D6tPo%UES`sfcX#4y)A}3M$S(6RTNKt zeae>cyC5I3?iqgMe#H`q(E*Tj+?aa4bIYVXWaD!9j%#^&dG=_2g~aae?)LAme)B`( zUFjR()e9&-i*CD8g%co~o!+ztm%dVmuBg*1SCTY{0f|A(*Pej&K)*UzVuv#`yL>Fy ziQ-M^=ukYkv-Cgs0{jR;3Jn@3klpm=v^C}PvEz(fyWr{Xrw6Z?rlJzGt zRNN+=37e;p>i$Z9bNXfQWQz|wkVq_C(YK!rD}wz^8aEp zSwxmrSSS{1+2ry5cY-&<`EkB%fZ8TY9P-xw=A}N5{Wbr@+&B0u^Uqpm9(?|6Dx?K8 zkd2!UfO=Wb;Zzvb@cKbdFb+!>sJhdt-VtkJtIsTR4m&#@6BA>ngfmn*frA}MYi&x1 zfdmB02L~5IM&sU34W0_Z0$M);D6#OgKsoZzlKL}#JpLRj&+M&~{QMgML%ml8X5Q6D z9)*M{USMr2FGuMHyuL%cBc^cyqfU5lqxR$)0>8<*UA$DR8uOvm&w<&kJJjMlm$K-V zC_`&hf(xMS!wwd+77N_RsOz=_HwE8tVik5=HN}NsQcrT>pJqvSw)?nT-rOalq@t2b z$POFtN%HVX)c<;3<@gS$dUWj1VDsLe9@7w%>&7V#1q&lWL&bMy9~dPYlk(KFGq~iqw<-dd=_#P-{-!EqItlc#Y=> z+**XU3Lz~365Mz*qNxMMy(Wme_m@+6!D&Je+kjKu;^ Date: Wed, 9 Nov 2016 15:15:56 +0000 Subject: [PATCH 05/37] bar: fix indentation of .enter() --- src/traces/bar/plot.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/traces/bar/plot.js b/src/traces/bar/plot.js index 0f4cb7f0558..98b85fd13ae 100644 --- a/src/traces/bar/plot.js +++ b/src/traces/bar/plot.js @@ -47,7 +47,7 @@ module.exports = function plot(gd, plotinfo, cdbar) { d3.select(this).selectAll('g.point') .data(Lib.identity) - .enter().append('g').classed('point', true) + .enter().append('g').classed('point', true) .each(function(di, i) { // now display the bar // clipped xf/yf (2nd arg true): non-positive From 4124e8a076c7668b9f2d6f6d398e454033b2ece6 Mon Sep 17 00:00:00 2001 From: Nicolas Riesco Date: Fri, 11 Nov 2016 13:05:39 +0000 Subject: [PATCH 06/37] bar: increase text padding to 3px --- src/traces/bar/plot.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/traces/bar/plot.js b/src/traces/bar/plot.js index 98b85fd13ae..b5d661b0320 100644 --- a/src/traces/bar/plot.js +++ b/src/traces/bar/plot.js @@ -276,9 +276,9 @@ function getTransformToMoveInsideBar(x0, x1, y0, y1, textBB) { textX = (textBB.left + textBB.right) / 2, textY = (textBB.top + textBB.bottom) / 2; - // apply target padding - var targetWidth = 0.95 * barWidth, - targetHeight = 0.95 * barHeight; + // apply 3px target padding + var targetWidth = barWidth - 6, + targetHeight = barHeight - 6; return getTransform( textX, textY, textWidth, textHeight, @@ -298,14 +298,14 @@ function getTransformToMoveOutsideBar(x0, x1, y0, y1, textBB, orientation) { if(orientation === 'h') { if(x1 < x0) { // bar end is on the left hand side - targetWidth = 2 + textWidth; // padding included - targetHeight = Math.abs(y1 - y0); + targetWidth = textWidth + 6; // 3px padding included + targetHeight = Math.abs(y1 - y0) - 6; targetX = x1 - targetWidth / 2; targetY = (y0 + y1) / 2; } else { - targetWidth = 2 + textWidth; // padding included - targetHeight = Math.abs(y1 - y0); + targetWidth = textWidth + 6; // padding included + targetHeight = Math.abs(y1 - y0) - 6; targetX = x1 + targetWidth / 2; targetY = (y0 + y1) / 2; } From d41b74dc7a0b3d63796de7e0767ed1d922c884ab Mon Sep 17 00:00:00 2001 From: Nicolas Riesco Date: Fri, 11 Nov 2016 13:24:55 +0000 Subject: [PATCH 07/37] test: bar text nodes exist --- test/jasmine/tests/bar_test.js | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/test/jasmine/tests/bar_test.js b/test/jasmine/tests/bar_test.js index 32f675f1307..bf4f77f2e3f 100644 --- a/test/jasmine/tests/bar_test.js +++ b/test/jasmine/tests/bar_test.js @@ -761,15 +761,21 @@ describe('A bar plot', function() { Plotly.plot(gd, data, layout).then(function() { var traceNodes = getAllTraceNodes(gd), - barNodes = getAllBarNodes(traceNodes[0]); + barNodes = getAllBarNodes(traceNodes[0]), + foundTextNodes; for(var i = 0; i < barNodes.length; i++) { var barNode = barNodes[i], pathNode = barNode.querySelector('path'), textNode = barNode.querySelector('text'); - if(textNode) assertTextIsInsidePath(textNode, pathNode); + if(textNode) { + foundTextNodes = true; + assertTextIsInsidePath(textNode, pathNode); + } } + expect(foundTextNodes).toBe(true); + done(); }); }); @@ -788,18 +794,22 @@ describe('A bar plot', function() { Plotly.plot(gd, data, layout).then(function() { var traceNodes = getAllTraceNodes(gd), - barNodes = getAllBarNodes(traceNodes[0]); + barNodes = getAllBarNodes(traceNodes[0]), + foundTextNodes; for(var i = 0; i < barNodes.length; i++) { var barNode = barNodes[i], pathNode = barNode.querySelector('path'), textNode = barNode.querySelector('text'); if(textNode) { + foundTextNodes = true; if(data[0].y[i] > 0) assertTextIsAbovePath(textNode, pathNode); else assertTextIsBelowPath(textNode, pathNode); } } + expect(foundTextNodes).toBe(true); + done(); }); }); @@ -817,18 +827,22 @@ describe('A bar plot', function() { Plotly.plot(gd, data, layout).then(function() { var traceNodes = getAllTraceNodes(gd), - barNodes = getAllBarNodes(traceNodes[0]); + barNodes = getAllBarNodes(traceNodes[0]), + foundTextNodes; for(var i = 0; i < barNodes.length; i++) { var barNode = barNodes[i], pathNode = barNode.querySelector('path'), textNode = barNode.querySelector('text'); if(textNode) { + foundTextNodes = true; if(data[0].x[i] > 0) assertTextIsAfterPath(textNode, pathNode); else assertTextIsBeforePath(textNode, pathNode); } } + expect(foundTextNodes).toBe(true); + done(); }); }); @@ -848,18 +862,22 @@ describe('A bar plot', function() { Plotly.plot(gd, data, layout).then(function() { var traceNodes = getAllTraceNodes(gd), - barNodes = getAllBarNodes(traceNodes[0]); + barNodes = getAllBarNodes(traceNodes[0]), + foundTextNodes; for(var i = 0; i < barNodes.length; i++) { var barNode = barNodes[i], pathNode = barNode.querySelector('path'), textNode = barNode.querySelector('text'); if(textNode) { + foundTextNodes = true; if(data[0].x[i] > 0) assertTextIsAfterPath(textNode, pathNode); else assertTextIsBeforePath(textNode, pathNode); } } + expect(foundTextNodes).toBe(true); + done(); }); }); From bddcaff16f261a419aa0aca332eb133efe20ac9a Mon Sep 17 00:00:00 2001 From: Nicolas Riesco Date: Fri, 11 Nov 2016 16:08:28 +0000 Subject: [PATCH 08/37] bar: change criteria for `textposition: 'auto'` * With the new criteria, bar texts are drawn inside bars except in those cases that would require scaling down. --- src/traces/bar/plot.js | 169 +++++++++++++++++++++++------------------ 1 file changed, 94 insertions(+), 75 deletions(-) diff --git a/src/traces/bar/plot.js b/src/traces/bar/plot.js index b5d661b0320..d6ca0710799 100644 --- a/src/traces/bar/plot.js +++ b/src/traces/bar/plot.js @@ -21,6 +21,8 @@ var ErrorBars = require('../../components/errorbars'); var arraysToCalcdata = require('./arrays_to_calcdata'); +// padding in pixels around text +var TEXTPAD = 3; module.exports = function plot(gd, plotinfo, cdbar) { var xa = plotinfo.xaxis, @@ -147,6 +149,8 @@ function appendBarText(gd, bar, calcTrace, i, x0, x1, y0, y1) { text = traceText; } + if(!text) return; + // get text position var traceTextPosition = trace.textposition, textPosition; @@ -160,21 +164,63 @@ function appendBarText(gd, bar, calcTrace, i, x0, x1, y0, y1) { if(textPosition === 'none') return; - var barWidth = Math.abs(x1 - x0), - barHeight = Math.abs(y1 - y0), - barIsTooSmall = (barWidth < 8 || barHeight < 8), + // get text font + var traceTextFont = trace.textfont, + textFont = (Array.isArray(traceTextFont)) ? + traceTextFont[i] : traceTextFont; + textFont = textFont || gd._fullLayout.font; + + // get outside text font + var traceOutsideTextFont = trace.outsidetextfont, + outsideTextFont = (Array.isArray(traceOutsideTextFont)) ? + traceOutsideTextFont[i] : traceOutsideTextFont; + outsideTextFont = outsideTextFont || textFont; + + // get inside text font + var traceInsideTextFont = trace.insidetextfont, + insideTextFont = (Array.isArray(traceInsideTextFont)) ? + traceInsideTextFont[i] : traceInsideTextFont; + insideTextFont = insideTextFont || textFont; + + // append text node + function appendTextNode(bar, text, textFont) { + var textSelection = bar.append('text') + // prohibit tex interpretation until we can handle + // tex and regular text together + .attr('data-notex', 1) + .text(text) + .attr({ + 'class': 'bartext', + transform: '', + 'data-bb': '', + 'text-anchor': 'middle', + x: 0, + y: 0 + }) + .call(Drawing.font, textFont); + + textSelection.call(svgTextUtils.convertToTspans); + textSelection.selectAll('tspan.line').attr({x: 0, y: 0}); + + return textSelection; + } - barmode = gd._fullLayout.barmode, + var barmode = gd._fullLayout.barmode, inStackMode = (barmode === 'stack'), inRelativeMode = (barmode === 'relative'), inStackOrRelativeMode = inStackMode || inRelativeMode, calcBar = calcTrace[i], - isOutmostBar = !inStackOrRelativeMode || calcBar._outmost; + isOutmostBar = !inStackOrRelativeMode || calcBar._outmost, - if(textPosition === 'auto') { - textPosition = (barIsTooSmall && isOutmostBar) ? 'outside' : 'inside'; - } + barWidth = Math.abs(x1 - x0) - 2 * TEXTPAD, // padding excluded + barHeight = Math.abs(y1 - y0) - 2 * TEXTPAD, // padding excluded + barIsTooSmall = (barWidth <= 0 || barHeight <= 0), + + textSelection, + textBB, + textWidth, + textHeight; if(textPosition === 'outside') { if(!isOutmostBar) textPosition = 'inside'; @@ -184,72 +230,46 @@ function appendBarText(gd, bar, calcTrace, i, x0, x1, y0, y1) { if(barIsTooSmall) return; } - - // get text font - var textFont; - - if(textPosition === 'outside') { - var traceOutsideTextFont = trace.outsidetextfont; - if(Array.isArray(traceOutsideTextFont)) { - if(i >= traceOutsideTextFont.length) return; - textFont = traceOutsideTextFont[i]; - } - else { - textFont = traceOutsideTextFont; - } - } - else { - var traceInsideTextFont = trace.insidetextfont; - if(Array.isArray(traceInsideTextFont)) { - if(i >= traceInsideTextFont.length) return; - textFont = traceInsideTextFont[i]; - } - else { - textFont = traceInsideTextFont; - } - } - - if(!textFont) { - var traceTextFont = trace.textfont; - if(Array.isArray(traceTextFont)) { - if(i >= traceTextFont.length) return; - textFont = traceTextFont[i]; - } - else { - textFont = traceTextFont; + if(textPosition === 'auto') { + if(isOutmostBar) { + // draw text using insideTextFont and check if it fits inside bar + textSelection = appendTextNode(bar, text, insideTextFont); + + textBB = Drawing.bBox(textSelection.node()), + textWidth = textBB.width, + textHeight = textBB.height; + + var textHasSize = (textWidth > 0 && textHeight > 0), + fitsInside = + (textWidth <= barWidth && textHeight <= barHeight), + fitsInsideIfRotated = + (textWidth <= barHeight && textHeight <= barWidth); + if(textHasSize && (fitsInside || fitsInsideIfRotated)) { + textPosition = 'inside'; + } + else { + textPosition = 'outside'; + textSelection.remove(); + textSelection = null; + } } + else if(!barIsTooSmall) textPosition = 'inside'; + else return; } - if(!textFont) { - textFont = gd._fullLayout.font; - } + if(!textSelection) { + textSelection = appendTextNode(bar, text, + (textPosition === 'outside') ? + outsideTextFont : insideTextFont); - // append bar text - var textSelection = bar.append('text') - // prohibit tex interpretation until we can handle - // tex and regular text together - .attr('data-notex', 1) - .text(text) - .attr({ - 'class': 'bartext', - transform: '', - 'data-bb': '', - 'text-anchor': 'middle', - x: 0, - y: 0 - }) - .call(Drawing.font, textFont); - - textSelection.call(svgTextUtils.convertToTspans); - textSelection.selectAll('tspan.line').attr({x: 0, y: 0}); - - // position bar text - var textBB = Drawing.bBox(textSelection.node()), + textBB = Drawing.bBox(textSelection.node()), textWidth = textBB.width, textHeight = textBB.height; - if(!textWidth || !textHeight) { - textSelection.remove(); - return; + + if(textWidth <= 0 || textHeight <= 0) { + textSelection.remove(); + return; + } } // compute translate transform @@ -277,8 +297,8 @@ function getTransformToMoveInsideBar(x0, x1, y0, y1, textBB) { textY = (textBB.top + textBB.bottom) / 2; // apply 3px target padding - var targetWidth = barWidth - 6, - targetHeight = barHeight - 6; + var targetWidth = barWidth - 2 * TEXTPAD, + targetHeight = barHeight - 2 * TEXTPAD; return getTransform( textX, textY, textWidth, textHeight, @@ -286,7 +306,6 @@ function getTransformToMoveInsideBar(x0, x1, y0, y1, textBB) { } function getTransformToMoveOutsideBar(x0, x1, y0, y1, textBB, orientation) { - // compute text and target positions var textWidth = textBB.width, textHeight = textBB.height, @@ -298,14 +317,14 @@ function getTransformToMoveOutsideBar(x0, x1, y0, y1, textBB, orientation) { if(orientation === 'h') { if(x1 < x0) { // bar end is on the left hand side - targetWidth = textWidth + 6; // 3px padding included - targetHeight = Math.abs(y1 - y0) - 6; + targetWidth = textWidth + 2 * TEXTPAD; // padding included + targetHeight = Math.abs(y1 - y0) - 2 * TEXTPAD; targetX = x1 - targetWidth / 2; targetY = (y0 + y1) / 2; } else { - targetWidth = textWidth + 6; // padding included - targetHeight = Math.abs(y1 - y0) - 6; + targetWidth = textWidth + 2 * TEXTPAD; // padding included + targetHeight = Math.abs(y1 - y0) - 2 * TEXTPAD; targetX = x1 + targetWidth / 2; targetY = (y0 + y1) / 2; } From 2cbf72553d9dcec9bde5b4b8d5412713fac3ea1d Mon Sep 17 00:00:00 2001 From: Nicolas Riesco Date: Sat, 12 Nov 2016 01:30:14 +0000 Subject: [PATCH 09/37] test: update mock bar_text_relative * Added rotated and shrunk inside text. * Shortened outside texts. --- test/image/mocks/bar_text_relative.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/image/mocks/bar_text_relative.json b/test/image/mocks/bar_text_relative.json index 0ddf91ceaa9..a7612c87ed6 100644 --- a/test/image/mocks/bar_text_relative.json +++ b/test/image/mocks/bar_text_relative.json @@ -2,14 +2,14 @@ "data":[ { "width":[1,0.8,0.6,0.4], - "text":[1,2,3,4], + "text":[1,2,3333333333,4], "textposition":"outside", "y":[1,2,3,4], "x":[1,2,3,4], "type":"bar" }, { "width":[0.4,0.6,0.8,1], - "text":["Three",2,"This bar is size one",0], + "text":["Three",2,"outside text",0], "textposition":"auto", "y":[3,2,1,0], "x":[1,2,3,4], @@ -22,7 +22,7 @@ "x":[1,2,3,4], "type":"bar" }, { - "text":[0,"very small size",-3,-2], + "text":[0,"outside text",-3,-2], "textposition":"auto", "y":[0,-0.25,-3,-2], "x":[1,2,3,4], From 1fd202c6cb80071b3ae2551fc0d8373cee6b345f Mon Sep 17 00:00:00 2001 From: Nicolas Riesco Date: Sat, 12 Nov 2016 01:34:08 +0000 Subject: [PATCH 10/37] bar: update algorithm to position bar texts * Inside texts are rotated only if the rotation helps shrink the text as little as possible. * Outside texts are rotated only if the rotation doesn't leave the text perpendicular to the bar. --- src/traces/bar/plot.js | 233 ++++++++++++++++++++++++++--------------- 1 file changed, 147 insertions(+), 86 deletions(-) diff --git a/src/traces/bar/plot.js b/src/traces/bar/plot.js index d6ca0710799..05ee5a7c666 100644 --- a/src/traces/bar/plot.js +++ b/src/traces/bar/plot.js @@ -272,136 +272,197 @@ function appendBarText(gd, bar, calcTrace, i, x0, x1, y0, y1) { } } - // compute translate transform + // set text transform var transform; if(textPosition === 'outside') { transform = getTransformToMoveOutsideBar(x0, x1, y0, y1, textBB, trace.orientation); } else { - transform = getTransformToMoveInsideBar(x0, x1, y0, y1, textBB); + transform = getTransformToMoveInsideBar(x0, x1, y0, y1, textBB, + trace.orientation); } textSelection.attr('transform', transform); } -function getTransformToMoveInsideBar(x0, x1, y0, y1, textBB) { +function getTransformToMoveInsideBar(x0, x1, y0, y1, textBB, orientation) { // compute text and target positions - var barWidth = Math.abs(x1 - x0), - barHeight = Math.abs(y1 - y0), - textWidth = textBB.width, + var textWidth = textBB.width, textHeight = textBB.height, - barX = (x0 + x1) / 2, - barY = (y0 + y1) / 2, textX = (textBB.left + textBB.right) / 2, - textY = (textBB.top + textBB.bottom) / 2; - - // apply 3px target padding - var targetWidth = barWidth - 2 * TEXTPAD, - targetHeight = barHeight - 2 * TEXTPAD; - - return getTransform( - textX, textY, textWidth, textHeight, - barX, barY, targetWidth, targetHeight); -} + textY = (textBB.top + textBB.bottom) / 2, + barWidth = Math.abs(x1 - x0) - 2 * TEXTPAD, + barHeight = Math.abs(y1 - y0) - 2 * TEXTPAD, + targetWidth, + targetHeight, + targetX, + targetY; + + // compute rotation and scale + var needsRotating, + scale; + + if(textWidth <= barWidth && textHeight <= barHeight) { + // no scale or rotation is required + needsRotating = false; + scale = 1; + } + else if(textWidth <= barHeight && textHeight <= barWidth) { + // only rotation is required + needsRotating = true; + scale = 1; + } + else if((textWidth < textHeight) === (barWidth < barHeight)) { + // only scale is required + needsRotating = false; + scale = Math.min(barWidth / textWidth, barHeight / textHeight); + } + else { + // both scale and rotation are required + needsRotating = true; + scale = Math.min(barHeight / textWidth, barWidth / textHeight); + } -function getTransformToMoveOutsideBar(x0, x1, y0, y1, textBB, orientation) { // compute text and target positions - var textWidth = textBB.width, - textHeight = textBB.height, - textX = (textBB.left + textBB.right) / 2, - textY = (textBB.top + textBB.bottom) / 2; + if(needsRotating) { + targetWidth = scale * textHeight; + targetHeight = scale * textWidth; + } + else { + targetWidth = scale * textWidth; + targetHeight = scale * textHeight; + } - var targetWidth, targetHeight, - targetX, targetY; if(orientation === 'h') { if(x1 < x0) { // bar end is on the left hand side - targetWidth = textWidth + 2 * TEXTPAD; // padding included - targetHeight = Math.abs(y1 - y0) - 2 * TEXTPAD; - targetX = x1 - targetWidth / 2; + targetX = x1 + TEXTPAD + targetWidth / 2; targetY = (y0 + y1) / 2; } else { - targetWidth = textWidth + 2 * TEXTPAD; // padding included - targetHeight = Math.abs(y1 - y0) - 2 * TEXTPAD; - targetX = x1 + targetWidth / 2; + targetX = x1 - TEXTPAD - targetWidth / 2; targetY = (y0 + y1) / 2; } } else { if(y1 > y0) { // bar end is on the bottom - targetWidth = Math.abs(x1 - x0); - targetHeight = 2 + textHeight; // padding included targetX = (x0 + x1) / 2; - targetY = y1 + targetHeight / 2; + targetY = y1 - TEXTPAD - targetHeight / 2; } else { - targetWidth = Math.abs(x1 - x0); - targetHeight = 2 + textHeight; // padding included targetX = (x0 + x1) / 2; - targetY = y1 - targetHeight / 2; + targetY = y1 + TEXTPAD + targetHeight / 2; } } - return getTransform( - textX, textY, textWidth, textHeight, - targetX, targetY, targetWidth, targetHeight); + return getTransform(textX, textY, targetX, targetY, scale, needsRotating); } -/** - * Compute SVG transform to move a text box into a target box - * - * @param {number} textX X pixel coord of the text box center - * @param {number} textY Y pixel coord of the text box center - * @param {number} textWidth text box width - * @param {number} textHeight text box height - * @param {number} targetX X pixel coord of the target box center - * @param {number} targetY Y pixel coord of the target box center - * @param {number} targetWidth target box width - * @param {number} targetHeight target box height - * - * @returns {string} SVG transform - */ -function getTransform( - textX, textY, textWidth, textHeight, - targetX, targetY, targetWidth, targetHeight) { - - // compute translate transform - var translateX = targetX - textX, - translateY = targetY - textY, - translate = 'translate(' + translateX + ' ' + translateY + ')'; - - // if bar text doesn't fit, compute rotate and scale transforms - var doesntFit = (textWidth > targetWidth || textHeight > targetHeight), - rotate, scale, scaleX, scaleY; - - if(doesntFit) { - var textIsHorizontal = (textWidth > textHeight), - targetIsHorizontal = (targetWidth > targetHeight); - if(textIsHorizontal !== targetIsHorizontal) { - rotate = 'rotate(-90 ' + textX + ' ' + textY + ')'; - scaleX = targetWidth / textHeight; - scaleY = targetHeight / textWidth; +function getTransformToMoveOutsideBar(x0, x1, y0, y1, textBB, orientation) { + // In order to handle both orientations with the same algorithm, + // *textWidth* is defined as the text length in the direction of *barWidth*. + var barWidth, + textWidth, + textHeight; + if(orientation === 'h') { + barWidth = Math.abs(y1 - y0) - 2 * TEXTPAD; + textWidth = textBB.height; + textHeight = textBB.width; + } + else { + barWidth = Math.abs(x1 - x0) - 2 * TEXTPAD; + textWidth = textBB.width; + textHeight = textBB.height; + } + + // compute rotation and scale + var needsRotating, + scale; + if(textWidth <= barWidth) { + // no scale or rotation + needsRotating = false; + scale = 1; + } + else if(textHeight <= textWidth) { + // only scale + // (don't rotate to prevent having text perpendicular to the bar) + needsRotating = false; + scale = barWidth / textWidth; + } + else if(textHeight <= barWidth) { + // only rotation + needsRotating = true; + scale = 1; + } + else { + // both scale and rotation + // (rotation prevents having text perpendicular to the bar) + needsRotating = true; + scale = barWidth / textHeight; + } + + // compute text and target positions + var textX = (textBB.left + textBB.right) / 2, + textY = (textBB.top + textBB.bottom) / 2, + targetWidth, + targetHeight, + targetX, + targetY; + if(needsRotating) { + targetWidth = scale * textBB.height; + targetHeight = scale * textBB.width; + } + else { + targetWidth = scale * textBB.width; + targetHeight = scale * textBB.height; + } + + if(orientation === 'h') { + if(x1 < x0) { + // bar end is on the left hand side + targetX = x1 - TEXTPAD - targetWidth / 2; + targetY = (y0 + y1) / 2; } else { - scaleX = targetWidth / textWidth; - scaleY = targetHeight / textHeight; + targetX = x1 + TEXTPAD + targetWidth / 2; + targetY = (y0 + y1) / 2; } + } + else { + if(y1 > y0) { + // bar end is on the bottom + targetX = (x0 + x1) / 2; + targetY = y1 + TEXTPAD + targetHeight / 2; + } + else { + targetX = (x0 + x1) / 2; + targetY = y1 - TEXTPAD - targetHeight / 2; + } + } - if(scaleX > 1) scaleX = 1; - if(scaleY > 1) scaleY = 1; + return getTransform(textX, textY, targetX, targetY, scale, needsRotating); +} - if(scaleX !== 1 || scaleY !== 1) { - scale = 'scale(' + scaleX + ' ' + scaleY + ')'; - } +function getTransform(textX, textY, targetX, targetY, scale, needsRotating) { + var transformScale, + transformRotate, + transformTranslate; + + if(scale < 1) transformScale = 'scale(' + scale + ') '; + else { + scale = 1; + transformScale = ''; } - // compute transform - var transform = translate; - if(scale) transform += ' ' + scale; - if(rotate) transform += ' ' + rotate; + transformRotate = (needsRotating) ? + 'rotate(-90 ' + textX + ' ' + textY + ') ' : ''; + + // Note that scaling also affects the center of the text box + var translateX = (targetX - scale * textX), + translateY = (targetY - scale * textY); + transformTranslate = 'translate(' + translateX + ' ' + translateY + ')'; - return transform; + return transformTranslate + transformScale + transformRotate; } From 6ab7a0b173a4567158d3b779abaa7efc9d1a3bcd Mon Sep 17 00:00:00 2001 From: Nicolas Riesco Date: Sat, 12 Nov 2016 02:12:48 +0000 Subject: [PATCH 11/37] bar: don't apply text padding if bar too small * This change ensures inside texts can be drawn, even inside bars smaller than the text padding. --- src/traces/bar/plot.js | 48 +++++++++++++++++++++++++----------------- 1 file changed, 29 insertions(+), 19 deletions(-) diff --git a/src/traces/bar/plot.js b/src/traces/bar/plot.js index 05ee5a7c666..def08cbb9cc 100644 --- a/src/traces/bar/plot.js +++ b/src/traces/bar/plot.js @@ -215,7 +215,6 @@ function appendBarText(gd, bar, calcTrace, i, x0, x1, y0, y1) { barWidth = Math.abs(x1 - x0) - 2 * TEXTPAD, // padding excluded barHeight = Math.abs(y1 - y0) - 2 * TEXTPAD, // padding excluded - barIsTooSmall = (barWidth <= 0 || barHeight <= 0), textSelection, textBB, @@ -226,10 +225,6 @@ function appendBarText(gd, bar, calcTrace, i, x0, x1, y0, y1) { if(!isOutmostBar) textPosition = 'inside'; } - if(textPosition === 'inside') { - if(barIsTooSmall) return; - } - if(textPosition === 'auto') { if(isOutmostBar) { // draw text using insideTextFont and check if it fits inside bar @@ -253,8 +248,7 @@ function appendBarText(gd, bar, calcTrace, i, x0, x1, y0, y1) { textSelection = null; } } - else if(!barIsTooSmall) textPosition = 'inside'; - else return; + else textPosition = 'inside'; } if(!textSelection) { @@ -292,13 +286,22 @@ function getTransformToMoveInsideBar(x0, x1, y0, y1, textBB, orientation) { textHeight = textBB.height, textX = (textBB.left + textBB.right) / 2, textY = (textBB.top + textBB.bottom) / 2, - barWidth = Math.abs(x1 - x0) - 2 * TEXTPAD, - barHeight = Math.abs(y1 - y0) - 2 * TEXTPAD, + barWidth = Math.abs(x1 - x0), + barHeight = Math.abs(y1 - y0), targetWidth, targetHeight, targetX, targetY; + // apply text padding + var textpad; + if(barWidth > 2 * TEXTPAD && barHeight > 2 * TEXTPAD) { + textpad = TEXTPAD; + barWidth -= 2 * textpad; + barHeight -= 2 * textpad; + } + else textpad = 0; + // compute rotation and scale var needsRotating, scale; @@ -337,11 +340,11 @@ function getTransformToMoveInsideBar(x0, x1, y0, y1, textBB, orientation) { if(orientation === 'h') { if(x1 < x0) { // bar end is on the left hand side - targetX = x1 + TEXTPAD + targetWidth / 2; + targetX = x1 + textpad + targetWidth / 2; targetY = (y0 + y1) / 2; } else { - targetX = x1 - TEXTPAD - targetWidth / 2; + targetX = x1 - textpad - targetWidth / 2; targetY = (y0 + y1) / 2; } } @@ -349,11 +352,11 @@ function getTransformToMoveInsideBar(x0, x1, y0, y1, textBB, orientation) { if(y1 > y0) { // bar end is on the bottom targetX = (x0 + x1) / 2; - targetY = y1 - TEXTPAD - targetHeight / 2; + targetY = y1 - textpad - targetHeight / 2; } else { targetX = (x0 + x1) / 2; - targetY = y1 + TEXTPAD + targetHeight / 2; + targetY = y1 + textpad + targetHeight / 2; } } @@ -367,16 +370,23 @@ function getTransformToMoveOutsideBar(x0, x1, y0, y1, textBB, orientation) { textWidth, textHeight; if(orientation === 'h') { - barWidth = Math.abs(y1 - y0) - 2 * TEXTPAD; + barWidth = Math.abs(y1 - y0); textWidth = textBB.height; textHeight = textBB.width; } else { - barWidth = Math.abs(x1 - x0) - 2 * TEXTPAD; + barWidth = Math.abs(x1 - x0); textWidth = textBB.width; textHeight = textBB.height; } + // apply text padding + var textpad; + if(barWidth > 2 * TEXTPAD) { + textpad = TEXTPAD; + barWidth -= 2 * textpad; + } + // compute rotation and scale var needsRotating, scale; @@ -422,11 +432,11 @@ function getTransformToMoveOutsideBar(x0, x1, y0, y1, textBB, orientation) { if(orientation === 'h') { if(x1 < x0) { // bar end is on the left hand side - targetX = x1 - TEXTPAD - targetWidth / 2; + targetX = x1 - textpad - targetWidth / 2; targetY = (y0 + y1) / 2; } else { - targetX = x1 + TEXTPAD + targetWidth / 2; + targetX = x1 + textpad + targetWidth / 2; targetY = (y0 + y1) / 2; } } @@ -434,11 +444,11 @@ function getTransformToMoveOutsideBar(x0, x1, y0, y1, textBB, orientation) { if(y1 > y0) { // bar end is on the bottom targetX = (x0 + x1) / 2; - targetY = y1 + TEXTPAD + targetHeight / 2; + targetY = y1 + textpad + targetHeight / 2; } else { targetX = (x0 + x1) / 2; - targetY = y1 - TEXTPAD - targetHeight / 2; + targetY = y1 - textpad - targetHeight / 2; } } From ffe5dae5214c7cfeb2992647e2fd78a7aa016857 Mon Sep 17 00:00:00 2001 From: Nicolas Riesco Date: Sat, 12 Nov 2016 19:25:32 +0000 Subject: [PATCH 12/37] test: remove mock bar_text_relative * Updated mock bar_attrs_relative to test the bar test functionality. --- test/image/mocks/bar_attrs_relative.json | 11 ++++++- test/image/mocks/bar_text_relative.json | 40 ------------------------ test/jasmine/tests/bar_test.js | 8 ++--- 3 files changed, 14 insertions(+), 45 deletions(-) delete mode 100644 test/image/mocks/bar_text_relative.json diff --git a/test/image/mocks/bar_attrs_relative.json b/test/image/mocks/bar_attrs_relative.json index a7e15e0ff96..a7612c87ed6 100644 --- a/test/image/mocks/bar_attrs_relative.json +++ b/test/image/mocks/bar_attrs_relative.json @@ -2,27 +2,36 @@ "data":[ { "width":[1,0.8,0.6,0.4], + "text":[1,2,3333333333,4], + "textposition":"outside", "y":[1,2,3,4], "x":[1,2,3,4], "type":"bar" }, { "width":[0.4,0.6,0.8,1], + "text":["Three",2,"outside text",0], + "textposition":"auto", "y":[3,2,1,0], "x":[1,2,3,4], "type":"bar" }, { "width":1, + "text":[-1,-3,-2,-4], + "textposition":"inside", "y":[-1,-3,-2,-4], "x":[1,2,3,4], "type":"bar" }, { - "y":[0,-1,-3,-2], + "text":[0,"outside text",-3,-2], + "textposition":"auto", + "y":[0,-0.25,-3,-2], "x":[1,2,3,4], "type":"bar" } ], "layout":{ "xaxis": {"showgrid":true}, + "yaxis": {"range":[-6,6]}, "height":400, "width":400, "barmode":"relative", diff --git a/test/image/mocks/bar_text_relative.json b/test/image/mocks/bar_text_relative.json deleted file mode 100644 index a7612c87ed6..00000000000 --- a/test/image/mocks/bar_text_relative.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "data":[ - { - "width":[1,0.8,0.6,0.4], - "text":[1,2,3333333333,4], - "textposition":"outside", - "y":[1,2,3,4], - "x":[1,2,3,4], - "type":"bar" - }, { - "width":[0.4,0.6,0.8,1], - "text":["Three",2,"outside text",0], - "textposition":"auto", - "y":[3,2,1,0], - "x":[1,2,3,4], - "type":"bar" - }, { - "width":1, - "text":[-1,-3,-2,-4], - "textposition":"inside", - "y":[-1,-3,-2,-4], - "x":[1,2,3,4], - "type":"bar" - }, { - "text":[0,"outside text",-3,-2], - "textposition":"auto", - "y":[0,-0.25,-3,-2], - "x":[1,2,3,4], - "type":"bar" - } - ], - "layout":{ - "xaxis": {"showgrid":true}, - "yaxis": {"range":[-6,6]}, - "height":400, - "width":400, - "barmode":"relative", - "barnorm":false - } -} diff --git a/test/jasmine/tests/bar_test.js b/test/jasmine/tests/bar_test.js index bf4f77f2e3f..eff091745cb 100644 --- a/test/jasmine/tests/bar_test.js +++ b/test/jasmine/tests/bar_test.js @@ -893,13 +893,13 @@ describe('A bar plot', function() { [1, 2, 3, 4], [1, 2, 3, 4]]); assertPointField(cd, 'y', [ [1, 2, 3, 4], [4, 4, 4, 4], - [-1, -3, -2, -4], [4, -4, -5, -6]]); + [-1, -3, -2, -4], [4, -3.25, -5, -6]]); assertPointField(cd, 'b', [ [0, 0, 0, 0], [1, 2, 3, 4], [0, 0, 0, 0], [4, -3, -2, -4]]); assertPointField(cd, 's', [ [1, 2, 3, 4], [3, 2, 1, 0], - [-1, -3, -2, -4], [0, -1, -3, -2]]); + [-1, -3, -2, -4], [0, -0.25, -3, -2]]); assertPointField(cd, 'p', [ [1, 2, 3, 4], [1, 2, 3, 4], [1, 2, 3, 4], [1, 2, 3, 4]]); @@ -921,13 +921,13 @@ describe('A bar plot', function() { [1.5, 2.5, 3.5, 4.5], [1.4, 2.4, 3.4, 4.4]]); assertPointField(cd, 'y', [ [1, 2, 3, 4], [4, 4, 4, 4], - [-1, -3, -2, -4], [4, -4, -5, -6]]); + [-1, -3, -2, -4], [4, -3.25, -5, -6]]); assertPointField(cd, 'b', [ [0, 0, 0, 0], [1, 2, 3, 4], [0, 0, 0, 0], [4, -3, -2, -4]]); assertPointField(cd, 's', [ [1, 2, 3, 4], [3, 2, 1, 0], - [-1, -3, -2, -4], [0, -1, -3, -2]]); + [-1, -3, -2, -4], [0, -0.25, -3, -2]]); assertPointField(cd, 'p', [ [1, 2, 3, 4], [1, 2, 3, 4], [1, 2, 3, 4], [1, 2, 3, 4]]); From 3455861022996376e8e6630b5bbdd36f49ac35a9 Mon Sep 17 00:00:00 2001 From: Nicolas Riesco Date: Sat, 12 Nov 2016 19:33:29 +0000 Subject: [PATCH 13/37] test: remove mock bar_text_horizontal_group * Updated mock bar_attrs_group_norm to test the bar text functionality. --- test/image/mocks/bar_attrs_group_norm.json | 4 ++++ .../mocks/bar_text_horizontal_group.json | 23 ------------------- 2 files changed, 4 insertions(+), 23 deletions(-) delete mode 100644 test/image/mocks/bar_text_horizontal_group.json diff --git a/test/image/mocks/bar_attrs_group_norm.json b/test/image/mocks/bar_attrs_group_norm.json index f08d1d8e8b5..400fa9b239a 100644 --- a/test/image/mocks/bar_attrs_group_norm.json +++ b/test/image/mocks/bar_attrs_group_norm.json @@ -1,10 +1,14 @@ { "data":[ { + "text":[100,125,150,175], + "textposition":"outside", "base":4, "x":[3,2,1,0], "type":"bar" }, { + "text":[100,125,150,175], + "textposition":"inside", "base":[7,6,5,4], "x":[1,2,3,4], "type":"bar" diff --git a/test/image/mocks/bar_text_horizontal_group.json b/test/image/mocks/bar_text_horizontal_group.json deleted file mode 100644 index 400fa9b239a..00000000000 --- a/test/image/mocks/bar_text_horizontal_group.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "data":[ - { - "text":[100,125,150,175], - "textposition":"outside", - "base":4, - "x":[3,2,1,0], - "type":"bar" - }, { - "text":[100,125,150,175], - "textposition":"inside", - "base":[7,6,5,4], - "x":[1,2,3,4], - "type":"bar" - } - ], - "layout":{ - "height":400, - "width":400, - "barmode":"group", - "barnorm":"percent" - } -} From a3fea79b91d72b7019c2fc2b605dfeb16b3aef2d Mon Sep 17 00:00:00 2001 From: Nicolas Riesco Date: Sat, 12 Nov 2016 19:44:34 +0000 Subject: [PATCH 14/37] test: remove bar_text_horizontal_relative_percent * Updated the jasmine tests to test whether outside bars are correctly identified when `barnorm: 'percent'`. --- .../bar_text_horizontal_relative_percent.json | 22 ------------------- test/jasmine/tests/bar_test.js | 2 +- 2 files changed, 1 insertion(+), 23 deletions(-) delete mode 100644 test/image/mocks/bar_text_horizontal_relative_percent.json diff --git a/test/image/mocks/bar_text_horizontal_relative_percent.json b/test/image/mocks/bar_text_horizontal_relative_percent.json deleted file mode 100644 index 5ac3c1825a2..00000000000 --- a/test/image/mocks/bar_text_horizontal_relative_percent.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "data":[ - { - "text":[1,1,-1,-1], - "textposition":"outside", - "x":[1,1,-1,-1], - "type":"bar" - }, { - "text":[-1,1,-1,1], - "textposition":"outside", - "x":[-1,1,-1,1], - "type":"bar" - } - ], - "layout":{ - "xaxis": {"range":[-150,150]}, - "height":400, - "width":400, - "barmode":"relative", - "barnorm":"percent" - } -} diff --git a/test/jasmine/tests/bar_test.js b/test/jasmine/tests/bar_test.js index eff091745cb..0f2657ed40f 100644 --- a/test/jasmine/tests/bar_test.js +++ b/test/jasmine/tests/bar_test.js @@ -857,7 +857,7 @@ describe('A bar plot', function() { }], layout = { barmode: 'relative', - barnorm: true + barnorm: 'percent' }; Plotly.plot(gd, data, layout).then(function() { From 31d389c9b15ca9afd1e3976f0caf2b15162bd7b4 Mon Sep 17 00:00:00 2001 From: Nicolas Riesco Date: Sat, 12 Nov 2016 19:48:46 +0000 Subject: [PATCH 15/37] test: remove mock bar_text_horizontal_relative --- .../mocks/bar_text_horizontal_relative.json | 20 ------------------- 1 file changed, 20 deletions(-) delete mode 100644 test/image/mocks/bar_text_horizontal_relative.json diff --git a/test/image/mocks/bar_text_horizontal_relative.json b/test/image/mocks/bar_text_horizontal_relative.json deleted file mode 100644 index 9b4731c564d..00000000000 --- a/test/image/mocks/bar_text_horizontal_relative.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "data":[ - { - "text":[3,2,1,-1], - "textposition":"outside", - "x":[3,2,1,-1], - "type":"bar" - }, { - "text":[-1,2,-3,4], - "textposition":"inside", - "x":[-1,2,-3,4], - "type":"bar" - } - ], - "layout":{ - "height":400, - "width":400, - "barmode":"relative" - } -} From af3f30b85ef52bd9c8e471042de7867a6b760ae5 Mon Sep 17 00:00:00 2001 From: Nicolas Riesco Date: Mon, 14 Nov 2016 11:56:27 +0000 Subject: [PATCH 16/37] test: update mock bar_attrs_relative * Reduced font size of text "Three" to ensure the baseline image also fits the text inside the bar. --- test/image/mocks/bar_attrs_relative.json | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/test/image/mocks/bar_attrs_relative.json b/test/image/mocks/bar_attrs_relative.json index a7612c87ed6..aef938a403e 100644 --- a/test/image/mocks/bar_attrs_relative.json +++ b/test/image/mocks/bar_attrs_relative.json @@ -3,27 +3,28 @@ { "width":[1,0.8,0.6,0.4], "text":[1,2,3333333333,4], - "textposition":"outside", + "textposition":"outside", "y":[1,2,3,4], "x":[1,2,3,4], "type":"bar" }, { "width":[0.4,0.6,0.8,1], "text":["Three",2,"outside text",0], - "textposition":"auto", + "textposition":"auto", + "textfont":[{"size":10}], "y":[3,2,1,0], "x":[1,2,3,4], "type":"bar" }, { "width":1, "text":[-1,-3,-2,-4], - "textposition":"inside", + "textposition":"inside", "y":[-1,-3,-2,-4], "x":[1,2,3,4], "type":"bar" }, { "text":[0,"outside text",-3,-2], - "textposition":"auto", + "textposition":"auto", "y":[0,-0.25,-3,-2], "x":[1,2,3,4], "type":"bar" From a0504d15767978f9595b7b611fed1e3e68b8b489 Mon Sep 17 00:00:00 2001 From: Nicolas Riesco Date: Mon, 14 Nov 2016 11:58:28 +0000 Subject: [PATCH 17/37] test: update mock bar_attrs_group_norm * Set outside font size to 32 to force rotation. This change will test that horizontal bars rotate the bar text clockwise or anti-clockwise as appropriate. --- test/image/mocks/bar_attrs_group_norm.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/image/mocks/bar_attrs_group_norm.json b/test/image/mocks/bar_attrs_group_norm.json index 400fa9b239a..b151e67fd26 100644 --- a/test/image/mocks/bar_attrs_group_norm.json +++ b/test/image/mocks/bar_attrs_group_norm.json @@ -2,13 +2,14 @@ "data":[ { "text":[100,125,150,175], - "textposition":"outside", + "textposition":"outside", + "outsidetextfont": {"size":32}, "base":4, "x":[3,2,1,0], "type":"bar" }, { "text":[100,125,150,175], - "textposition":"inside", + "textposition":"inside", "base":[7,6,5,4], "x":[1,2,3,4], "type":"bar" From 77dc2c3bcfb44ce1f67e3d387abab291cddb67d3 Mon Sep 17 00:00:00 2001 From: Nicolas Riesco Date: Mon, 14 Nov 2016 15:44:53 +0000 Subject: [PATCH 18/37] bar: fix normalised group bar plots * Fixed bug computing the normalisation of bar plots in group mode. * The bug was caused by calling `sieve.put` with bar sizes that didn't take into account the bar base. * Bar plots in stack and relative mode weren't affected by this bug, because traces that set a bar base are excluded from the stack/relative mode. --- src/traces/bar/set_positions.js | 6 +++--- test/jasmine/tests/bar_test.js | 21 +++++++++++++++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/src/traces/bar/set_positions.js b/src/traces/bar/set_positions.js index 62c112bd157..7ad3b655c1e 100644 --- a/src/traces/bar/set_positions.js +++ b/src/traces/bar/set_positions.js @@ -503,8 +503,8 @@ function stackBars(gd, sa, sieve) { if(!isNumeric(bar.s)) continue; // stack current bar and get previous sum - var barBase = sieve.put(bar.p, bar.s), - barTop = barBase + bar.s; + var barBase = sieve.put(bar.p, bar.b + bar.s), + barTop = barBase + bar.b + bar.s; // store the bar base and top in each calcdata item bar.b = barBase; @@ -537,7 +537,7 @@ function sieveBars(gd, sa, sieve) { for(var j = 0; j < trace.length; j++) { var bar = trace[j]; - if(isNumeric(bar.s)) sieve.put(bar.p, bar.s); + if(isNumeric(bar.s)) sieve.put(bar.p, bar.b + bar.s); } } } diff --git a/test/jasmine/tests/bar_test.js b/test/jasmine/tests/bar_test.js index 0f2657ed40f..c5e768b2c09 100644 --- a/test/jasmine/tests/bar_test.js +++ b/test/jasmine/tests/bar_test.js @@ -543,6 +543,27 @@ describe('Bar.setPositions', function() { assertPointField(cd, 'y', [[0.75, 0.50, 0.25], [0.25, 0.50, 0.75]]); }); + it('should honor barnorm (group+base case)', function() { + var gd = mockBarPlot([{ + base: [3, 2, 1], + y: [0, 0, 0] + }, { + y: [1, 2, 3] + }], { + bargap: 0, + barmode: 'group', + barnorm: 'fraction' + }); + + expect(gd._fullLayout.barnorm).toBe('fraction'); + + var cd = gd.calcdata; + assertPointField(cd, 'b', [[0.75, 0.50, 0.25], [0, 0, 0]]); + assertPointField(cd, 's', [[0, 0, 0], [0.25, 0.50, 0.75]]); + assertPointField(cd, 'x', [[-0.25, 0.75, 1.75], [0.25, 1.25, 2.25]]); + assertPointField(cd, 'y', [[0.75, 0.50, 0.25], [0.25, 0.50, 0.75]]); + }); + it('should honor barnorm (stack case)', function() { var gd = mockBarPlot([{ y: [3, 2, 1] From 6a78175793461e1a168ac5b861b5678f495b8067 Mon Sep 17 00:00:00 2001 From: Nicolas Riesco Date: Mon, 14 Nov 2016 15:54:00 +0000 Subject: [PATCH 19/37] test: update mock bar_attrs_group_norm * Updated the mock bar_attrs_group_norm so that it exercises the functionality to rotate bar texts. --- test/image/mocks/bar_attrs_group_norm.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/test/image/mocks/bar_attrs_group_norm.json b/test/image/mocks/bar_attrs_group_norm.json index b151e67fd26..8115afefd36 100644 --- a/test/image/mocks/bar_attrs_group_norm.json +++ b/test/image/mocks/bar_attrs_group_norm.json @@ -1,17 +1,17 @@ { "data":[ { - "text":[100,125,150,175], + "text":0.5, "textposition":"outside", "outsidetextfont": {"size":32}, - "base":4, - "x":[3,2,1,0], + "base":[100,40,25,10], + "x":[-50,10,25,40], "type":"bar" }, { - "text":[100,125,150,175], + "text":0.5, "textposition":"inside", - "base":[7,6,5,4], - "x":[1,2,3,4], + "base":[0,60,75,90], + "x":[50,-10,-25,-40], "type":"bar" } ], @@ -19,6 +19,6 @@ "height":400, "width":400, "barmode":"group", - "barnorm":"percent" + "barnorm":"fraction" } } From 0289c183da657369c4148021203038f6feab65e6 Mon Sep 17 00:00:00 2001 From: Nicolas Riesco Date: Mon, 14 Nov 2016 17:16:56 +0000 Subject: [PATCH 20/37] bar: fix hover label * Fix hover xLabelVal and yLabelVal so that it takes into account the bar base. --- src/traces/bar/hover.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/traces/bar/hover.js b/src/traces/bar/hover.js index 696ec89a55b..f1fe8d8cc82 100644 --- a/src/traces/bar/hover.js +++ b/src/traces/bar/hover.js @@ -67,7 +67,7 @@ module.exports = function hoverPoints(pointData, xval, yval, hovermode) { if(trace.orientation === 'h') { pointData.x0 = pointData.x1 = xa.c2p(di.x, true); - pointData.xLabelVal = di.s; + pointData.xLabelVal = di.b + di.s; pointData.y0 = ya.c2p(barPos(di) - barDelta, true); pointData.y1 = ya.c2p(barPos(di) + barDelta, true); @@ -75,7 +75,7 @@ module.exports = function hoverPoints(pointData, xval, yval, hovermode) { } else { pointData.y0 = pointData.y1 = ya.c2p(di.y, true); - pointData.yLabelVal = di.s; + pointData.yLabelVal = di.b + di.s; pointData.x0 = xa.c2p(barPos(di) - barDelta, true); pointData.x1 = xa.c2p(barPos(di) + barDelta, true); From 3b1f16263035532ed1f67c7882965d9ff4504e24 Mon Sep 17 00:00:00 2001 From: Nicolas Riesco Date: Mon, 14 Nov 2016 17:18:44 +0000 Subject: [PATCH 21/37] test: update hover tests in bar_test * Updated hover tests in bar_test to test that hover labels honor bar bases. --- test/jasmine/tests/bar_test.js | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/test/jasmine/tests/bar_test.js b/test/jasmine/tests/bar_test.js index c5e768b2c09..97ba5c9ff61 100644 --- a/test/jasmine/tests/bar_test.js +++ b/test/jasmine/tests/bar_test.js @@ -1044,17 +1044,30 @@ describe('bar hover', function() { }); it('should return the correct hover point data (case y)', function() { - var out = _hover(gd, 185.645, 0.15, 'y'); - - expect(out.style).toEqual([0, '#1f77b4', 75, 0]); - assertPos(out.pos, [182.88, 182.88, 214.5, 170.5]); + var out = _hover(gd, 0.75, 0.15, 'y'), + subplot = gd._fullLayout._plots.xy, + xa = subplot.xaxis, + ya = subplot.yaxis, + barDelta = 1 * 0.8 / 2; + + expect(out.style).toEqual([0, '#1f77b4', 0.5, 0]); + assertPos(out.pos, + [xa.c2p(0.5, true), xa.c2p(0.5, true), + ya.c2p(0 - barDelta, true), ya.c2p(0 + barDelta, true)]); }); it('should return the correct hover point data (case closest)', function() { - var out = _hover(gd, 135.88, -0.15, 'closest'); - - expect(out.style).toEqual([0, '#1f77b4', 75, 0]); - assertPos(out.pos, [182.88, 182.88, 214.5, 192.5]); + var out = _hover(gd, 0.75, -0.15, 'closest'), + subplot = gd._fullLayout._plots.xy, + xa = subplot.xaxis, + ya = subplot.yaxis, + barDelta = 1 * 0.8 / 2 / 2, + barPos = 0 - 1 * 0.8 / 2 + barDelta; + + expect(out.style).toEqual([0, '#1f77b4', 0.5, 0]); + assertPos(out.pos, + [xa.c2p(0.5, true), xa.c2p(0.5, true), + ya.c2p(barPos - barDelta, true), ya.c2p(barPos + barDelta, true)]); }); }); From 3c2ff5205cbe2c6719424db528ebd73249bbd782 Mon Sep 17 00:00:00 2001 From: Nicolas Riesco Date: Mon, 14 Nov 2016 18:00:27 +0000 Subject: [PATCH 22/37] bar: update text rotation algorithm * If rotation is needed: - inside texts are rotated counter-clockwise. - outside texts are rotated clockwise or counter-clockwise, so that the text is laid along the bar end. --- src/traces/bar/plot.js | 40 +++++++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/src/traces/bar/plot.js b/src/traces/bar/plot.js index def08cbb9cc..e2e2d9199cf 100644 --- a/src/traces/bar/plot.js +++ b/src/traces/bar/plot.js @@ -303,32 +303,34 @@ function getTransformToMoveInsideBar(x0, x1, y0, y1, textBB, orientation) { else textpad = 0; // compute rotation and scale - var needsRotating, + var rotate, scale; if(textWidth <= barWidth && textHeight <= barHeight) { // no scale or rotation is required - needsRotating = false; + rotate = false; scale = 1; } else if(textWidth <= barHeight && textHeight <= barWidth) { // only rotation is required - needsRotating = true; + rotate = true; scale = 1; } else if((textWidth < textHeight) === (barWidth < barHeight)) { // only scale is required - needsRotating = false; + rotate = false; scale = Math.min(barWidth / textWidth, barHeight / textHeight); } else { // both scale and rotation are required - needsRotating = true; + rotate = true; scale = Math.min(barHeight / textWidth, barWidth / textHeight); } + if(rotate) rotate = -90; // rotate counter-clockwise + // compute text and target positions - if(needsRotating) { + if(rotate) { targetWidth = scale * textHeight; targetHeight = scale * textWidth; } @@ -360,7 +362,7 @@ function getTransformToMoveInsideBar(x0, x1, y0, y1, textBB, orientation) { } } - return getTransform(textX, textY, targetX, targetY, scale, needsRotating); + return getTransform(textX, textY, targetX, targetY, scale, rotate); } function getTransformToMoveOutsideBar(x0, x1, y0, y1, textBB, orientation) { @@ -388,28 +390,28 @@ function getTransformToMoveOutsideBar(x0, x1, y0, y1, textBB, orientation) { } // compute rotation and scale - var needsRotating, + var rotate, scale; if(textWidth <= barWidth) { // no scale or rotation - needsRotating = false; + rotate = false; scale = 1; } else if(textHeight <= textWidth) { // only scale // (don't rotate to prevent having text perpendicular to the bar) - needsRotating = false; + rotate = false; scale = barWidth / textWidth; } else if(textHeight <= barWidth) { // only rotation - needsRotating = true; + rotate = true; scale = 1; } else { // both scale and rotation // (rotation prevents having text perpendicular to the bar) - needsRotating = true; + rotate = true; scale = barWidth / textHeight; } @@ -420,7 +422,7 @@ function getTransformToMoveOutsideBar(x0, x1, y0, y1, textBB, orientation) { targetHeight, targetX, targetY; - if(needsRotating) { + if(rotate) { targetWidth = scale * textBB.height; targetHeight = scale * textBB.width; } @@ -434,10 +436,12 @@ function getTransformToMoveOutsideBar(x0, x1, y0, y1, textBB, orientation) { // bar end is on the left hand side targetX = x1 - textpad - targetWidth / 2; targetY = (y0 + y1) / 2; + if(rotate) rotate = -90; // rotate counter-clockwise } else { targetX = x1 + textpad + targetWidth / 2; targetY = (y0 + y1) / 2; + if(rotate) rotate = 90; // rotate clockwise } } else { @@ -445,17 +449,19 @@ function getTransformToMoveOutsideBar(x0, x1, y0, y1, textBB, orientation) { // bar end is on the bottom targetX = (x0 + x1) / 2; targetY = y1 + textpad + targetHeight / 2; + if(rotate) rotate = -90; // rotate counter-clockwise } else { targetX = (x0 + x1) / 2; targetY = y1 - textpad - targetHeight / 2; + if(rotate) rotate = 90; // rotate clockwise } } - return getTransform(textX, textY, targetX, targetY, scale, needsRotating); + return getTransform(textX, textY, targetX, targetY, scale, rotate); } -function getTransform(textX, textY, targetX, targetY, scale, needsRotating) { +function getTransform(textX, textY, targetX, targetY, scale, rotate) { var transformScale, transformRotate, transformTranslate; @@ -466,8 +472,8 @@ function getTransform(textX, textY, targetX, targetY, scale, needsRotating) { transformScale = ''; } - transformRotate = (needsRotating) ? - 'rotate(-90 ' + textX + ' ' + textY + ') ' : ''; + transformRotate = (rotate) ? + 'rotate(' + rotate + ' ' + textX + ' ' + textY + ') ' : ''; // Note that scaling also affects the center of the text box var translateX = (targetX - scale * textX), From a9a13f156737f5792d681e187582fb70a6040c09 Mon Sep 17 00:00:00 2001 From: Nicolas Riesco Date: Mon, 14 Nov 2016 18:10:54 +0000 Subject: [PATCH 23/37] test: update baseline images * Removed the baselines for bar_test_* mocks. * Updated bar_attrs_relative and bar_attrs_group_norm that now exercise the new functionality to draw bar texts. --- test/image/baselines/bar_attrs_group_norm.png | Bin 10324 -> 12736 bytes test/image/baselines/bar_attrs_relative.png | Bin 11045 -> 15025 bytes .../baselines/bar_text_horizontal_group.png | Bin 13165 -> 0 bytes .../bar_text_horizontal_relative.png | Bin 10535 -> 0 bytes .../bar_text_horizontal_relative_percent.png | Bin 9499 -> 0 bytes test/image/baselines/bar_text_relative.png | Bin 15414 -> 0 bytes 6 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 test/image/baselines/bar_text_horizontal_group.png delete mode 100644 test/image/baselines/bar_text_horizontal_relative.png delete mode 100644 test/image/baselines/bar_text_horizontal_relative_percent.png delete mode 100644 test/image/baselines/bar_text_relative.png diff --git a/test/image/baselines/bar_attrs_group_norm.png b/test/image/baselines/bar_attrs_group_norm.png index f209c383e666dc85563201dbdb11ec4768443deb..01cc6532dad9e03da518d4610759c20acaf453c2 100644 GIT binary patch literal 12736 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 AXaE2J literal 11045 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 diff --git a/test/image/baselines/bar_text_horizontal_group.png b/test/image/baselines/bar_text_horizontal_group.png deleted file mode 100644 index 78c1b2c663c1a9c7c18f06a84387b20b2cd5608e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13165 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 Date: Mon, 14 Nov 2016 19:45:57 +0000 Subject: [PATCH 24/37] test: make linter happy --- test/jasmine/tests/bar_test.js | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/test/jasmine/tests/bar_test.js b/test/jasmine/tests/bar_test.js index 97ba5c9ff61..c5039b4d62b 100644 --- a/test/jasmine/tests/bar_test.js +++ b/test/jasmine/tests/bar_test.js @@ -1048,12 +1048,14 @@ describe('bar hover', function() { subplot = gd._fullLayout._plots.xy, xa = subplot.xaxis, ya = subplot.yaxis, - barDelta = 1 * 0.8 / 2; + barDelta = 1 * 0.8 / 2, + x0 = xa.c2p(0.5, true), + x1 = x0, + y0 = ya.c2p(0 - barDelta, true), + y1 = ya.c2p(0 + barDelta, true); expect(out.style).toEqual([0, '#1f77b4', 0.5, 0]); - assertPos(out.pos, - [xa.c2p(0.5, true), xa.c2p(0.5, true), - ya.c2p(0 - barDelta, true), ya.c2p(0 + barDelta, true)]); + assertPos(out.pos, [x0, x1, y0, y1]); }); it('should return the correct hover point data (case closest)', function() { @@ -1062,12 +1064,14 @@ describe('bar hover', function() { xa = subplot.xaxis, ya = subplot.yaxis, barDelta = 1 * 0.8 / 2 / 2, - barPos = 0 - 1 * 0.8 / 2 + barDelta; + barPos = 0 - 1 * 0.8 / 2 + barDelta, + x0 = xa.c2p(0.5, true), + x1 = x0, + y0 = ya.c2p(barPos - barDelta, true), + y1 = ya.c2p(barPos + barDelta, true); expect(out.style).toEqual([0, '#1f77b4', 0.5, 0]); - assertPos(out.pos, - [xa.c2p(0.5, true), xa.c2p(0.5, true), - ya.c2p(barPos - barDelta, true), ya.c2p(barPos + barDelta, true)]); + assertPos(out.pos, [x0, x1, y0, y1]); }); }); From aa8d68ea7db4882e070bbd36017379bbf2900bc8 Mon Sep 17 00:00:00 2001 From: Nicolas Riesco Date: Tue, 15 Nov 2016 16:23:05 +0000 Subject: [PATCH 25/37] bar: do not coerce elements of an array attribute --- src/traces/bar/defaults.js | 136 ++----------------------------------- 1 file changed, 6 insertions(+), 130 deletions(-) diff --git a/src/traces/bar/defaults.js b/src/traces/bar/defaults.js index 95f9fd56f86..e907b40ad06 100644 --- a/src/traces/bar/defaults.js +++ b/src/traces/bar/defaults.js @@ -9,9 +9,6 @@ 'use strict'; -var isNumeric = require('fast-isnumeric'); -var tinycolor = require('tinycolor2'); - var Lib = require('../../lib'); var Color = require('../../components/color'); @@ -26,124 +23,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout return Lib.coerce(traceIn, traceOut, attributes, attr, dflt); } - function coerceEnumerated(attributeDefinition, value, defaultValue) { - if(attributeDefinition.coerceNumber) value = +value; - - if(attributeDefinition.values.indexOf(value) !== -1) return value; - - return (defaultValue !== undefined) ? - defaultValue : - attributeDefinition.dflt; - } - - function coerceString(attributeDefinition, value, defaultValue) { - if(typeof value === 'string') { - if(value || !attributeDefinition.noBlank) return value; - } - else if(typeof value === 'number') { - if(!attributeDefinition.strict) return String(value); - } - - return (defaultValue !== undefined) ? - defaultValue : - attributeDefinition.dflt; - } - - function coerceNumber(attributeDefinition, value, defaultValue) { - if(isNumeric(value)) { - value = +value; - - var min = attributeDefinition.min, - max = attributeDefinition.max, - isOutOfBounds = (min !== undefined && value < min) || - (max !== undefined && value > max); - - if(!isOutOfBounds) return value; - } - - return (defaultValue !== undefined) ? - defaultValue : - attributeDefinition.dflt; - } - - function coerceColor(attributeDefinition, value, defaultValue) { - if(tinycolor(value).isValid()) return value; - - return (defaultValue !== undefined) ? - defaultValue : - attributeDefinition.dflt; - } - - function coerceFont(attributeDefinition, value, defaultValue) { - value = value || {}; - defaultValue = defaultValue || {}; - - return { - family: coerceString( - attributeDefinition.family, value.family, defaultValue.family), - size: coerceNumber( - attributeDefinition.size, value.size, defaultValue.size), - color: coerceColor( - attributeDefinition.color, value.color, defaultValue.color) - }; - } - - function coerceArray(attribute, coerceFunction, defaultValue, defaultValue2) { - var attributeDefinition = attributes[attribute], - arrayOk = attributeDefinition.arrayOk, - inValue = traceIn[attribute], - inValueIsArray = Array.isArray(inValue), - defaultValueIsArray = Array.isArray(defaultValue), - outValue, - i; - - // Case: inValue and defaultValue not treated as arrays - if(!arrayOk || (!inValueIsArray && !defaultValueIsArray)) { - outValue = coerceFunction( - attributeDefinition, inValue, defaultValue); - traceOut[attribute] = outValue; - return outValue; - } - - // Coerce into an array - outValue = []; - - // Case: defaultValue is an array and inValue isn't - if(!inValueIsArray) { - for(i = 0; i < defaultValue.length; i++) { - outValue.push( - coerceFunction( - attributeDefinition, inValue, defaultValue[i])); - } - } - - // Case: inValue is an array and defaultValue isn't - else if(!defaultValueIsArray) { - for(i = 0; i < inValue.length; i++) { - outValue.push( - coerceFunction( - attributeDefinition, inValue[i], defaultValue)); - } - } - - // Case: inValue and defaultValue are both arrays - else { - for(i = 0; i < defaultValue.length; i++) { - outValue.push( - coerceFunction( - attributeDefinition, inValue[i], defaultValue[i])); - } - for(; i < inValue.length; i++) { - outValue.push( - coerceFunction( - attributeDefinition, inValue[i], defaultValue2)); - } - } - - traceOut[attribute] = outValue; - - return outValue; - } + var coerceFont = Lib.coerceFont; var len = handleXYDefaults(traceIn, traceOut, coerce); if(!len) { @@ -156,21 +36,17 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout coerce('offset'); coerce('width'); - coerceArray('text', coerceString); + coerce('text'); - var textPosition = coerceArray('textposition', coerceEnumerated); + var textPosition = coerce('textposition'); var hasBoth = Array.isArray(textPosition) || textPosition === 'auto', hasInside = hasBoth || textPosition === 'inside', hasOutside = hasBoth || textPosition === 'outside'; if(hasInside || hasOutside) { - var textFont = coerceArray('textfont', coerceFont, layout.font); - if(hasInside) { - coerceArray('insidetextfont', coerceFont, textFont, layout.font); - } - if(hasOutside) { - coerceArray('outsidetextfont', coerceFont, textFont, layout.font); - } + var textFont = coerceFont(coerce, 'textfont', layout.font); + if(hasInside) coerceFont(coerce, 'insidetextfont', textFont); + if(hasOutside) coerceFont(coerce, 'outsidetextfont', textFont); } handleStyleDefaults(traceIn, traceOut, coerce, defaultColor, layout); From f8e9554825284bfb522ff49db8e1d5546ffff480 Mon Sep 17 00:00:00 2001 From: Nicolas Riesco Date: Tue, 15 Nov 2016 16:24:40 +0000 Subject: [PATCH 26/37] bar: accept font family, size and color arrays * Before this commit, font attributes accepted: [{family: family0, size: size0, color: color0}, {family: family1, size: size1, color: color1}, ...] * With this commit, font attributes accept: {family: [family0, family1, ...], size: [size0, size1, ...], color: [color0, color1, ...]} --- src/traces/bar/attributes.js | 15 ++-- src/traces/bar/plot.js | 170 ++++++++++++++++++++++++----------- 2 files changed, 129 insertions(+), 56 deletions(-) diff --git a/src/traces/bar/attributes.js b/src/traces/bar/attributes.js index bedb65f7bea..6c40cd4264b 100644 --- a/src/traces/bar/attributes.js +++ b/src/traces/bar/attributes.js @@ -12,6 +12,12 @@ var scatterAttrs = require('../scatter/attributes'); var colorAttributes = require('../../components/colorscale/color_attributes'); var fontAttrs = require('../../plots/font_attributes'); var extendFlat = require('../../lib/extend').extendFlat; +var extendDeep = require('../../lib/extend').extendDeep; + +var textFontAttrs = extendDeep({}, fontAttrs); +textFontAttrs.family.arrayOk = true; +textFontAttrs.size.arrayOk = true; +textFontAttrs.color.arrayOk = true; var scatterMarkerAttrs = scatterAttrs.marker; var scatterMarkerLineAttrs = scatterMarkerAttrs.line; @@ -50,18 +56,15 @@ module.exports = { ].join(' ') }, - textfont: extendFlat({}, fontAttrs, { - arrayOk: true, + textfont: extendFlat({}, textFontAttrs, { description: 'Sets the font used for `textinfo`.' }), - insidetextfont: extendFlat({}, fontAttrs, { - arrayOk: true, + insidetextfont: extendFlat({}, textFontAttrs, { description: 'Sets the font used for `textinfo` lying inside the bar.' }), - outsidetextfont: extendFlat({}, fontAttrs, { - arrayOk: true, + outsidetextfont: extendFlat({}, textFontAttrs, { description: 'Sets the font used for `textinfo` lying outside the bar.' }), diff --git a/src/traces/bar/plot.js b/src/traces/bar/plot.js index e2e2d9199cf..e15c36bbf5c 100644 --- a/src/traces/bar/plot.js +++ b/src/traces/bar/plot.js @@ -11,6 +11,7 @@ var d3 = require('d3'); var isNumeric = require('fast-isnumeric'); +var tinycolor = require('tinycolor2'); var Lib = require('../../lib'); var svgTextUtils = require('../../lib/svg_text_utils'); @@ -21,6 +22,13 @@ var ErrorBars = require('../../components/errorbars'); var arraysToCalcdata = require('./arrays_to_calcdata'); +var attributes = require('./attributes'), + attributeText = attributes.text, + attributeTextPosition = attributes.textposition, + attributeTextFont = attributes.textfont, + attributeInsideTextFont = attributes.insidetextfont, + attributeOutsideTextFont = attributes.outsidetextfont; + // padding in pixels around text var TEXTPAD = 3; @@ -134,55 +142,6 @@ module.exports = function plot(gd, plotinfo, cdbar) { }; function appendBarText(gd, bar, calcTrace, i, x0, x1, y0, y1) { - var trace = calcTrace[0].trace; - - // get bar text - var traceText = trace.text; - if(!traceText) return; - - var text; - if(Array.isArray(traceText)) { - if(i >= traceText.length) return; - text = traceText[i]; - } - else { - text = traceText; - } - - if(!text) return; - - // get text position - var traceTextPosition = trace.textposition, - textPosition; - if(Array.isArray(traceTextPosition)) { - if(i >= traceTextPosition.length) return; - textPosition = traceTextPosition[i]; - } - else { - textPosition = traceTextPosition; - } - - if(textPosition === 'none') return; - - // get text font - var traceTextFont = trace.textfont, - textFont = (Array.isArray(traceTextFont)) ? - traceTextFont[i] : traceTextFont; - textFont = textFont || gd._fullLayout.font; - - // get outside text font - var traceOutsideTextFont = trace.outsidetextfont, - outsideTextFont = (Array.isArray(traceOutsideTextFont)) ? - traceOutsideTextFont[i] : traceOutsideTextFont; - outsideTextFont = outsideTextFont || textFont; - - // get inside text font - var traceInsideTextFont = trace.insidetextfont, - insideTextFont = (Array.isArray(traceInsideTextFont)) ? - traceInsideTextFont[i] : traceInsideTextFont; - insideTextFont = insideTextFont || textFont; - - // append text node function appendTextNode(bar, text, textFont) { var textSelection = bar.append('text') // prohibit tex interpretation until we can handle @@ -205,6 +164,20 @@ function appendBarText(gd, bar, calcTrace, i, x0, x1, y0, y1) { return textSelection; } + // get trace attributes + var trace = calcTrace[0].trace; + + var text = getText(trace, i); + if(!text) return; + + var textPosition = getTextPosition(trace, i); + if(textPosition === 'none') return; + + var textFont = getTextFont(trace, i, gd._fullLayout.font), + insideTextFont = getInsideTextFont(trace, i, textFont), + outsideTextFont = getOutsideTextFont(trace, i, textFont); + + // compute text position var barmode = gd._fullLayout.barmode, inStackMode = (barmode === 'stack'), inRelativeMode = (barmode === 'relative'), @@ -266,7 +239,7 @@ function appendBarText(gd, bar, calcTrace, i, x0, x1, y0, y1) { } } - // set text transform + // compute text transform var transform; if(textPosition === 'outside') { transform = getTransformToMoveOutsideBar(x0, x1, y0, y1, textBB, @@ -482,3 +455,100 @@ function getTransform(textX, textY, targetX, targetY, scale, rotate) { return transformTranslate + transformScale + transformRotate; } + +function getText(trace, index) { + var value = getValue(trace.text, index); + return coerceString(attributeText, value); +} + +function getTextPosition(trace, index) { + var value = getValue(trace.textposition, index); + return coerceEnumerated(attributeTextPosition, value); +} + +function getTextFont(trace, index, defaultValue) { + return getFontValue( + attributeTextFont, trace.textfont, index, defaultValue); +} + +function getInsideTextFont(trace, index, defaultValue) { + return getFontValue( + attributeInsideTextFont, trace.insidetextfont, index, defaultValue); +} + +function getOutsideTextFont(trace, index, defaultValue) { + return getFontValue( + attributeOutsideTextFont, trace.outsidetextfont, index, defaultValue); +} + +function getFontValue(attributeDefinition, attributeValue, index, defaultValue) { + attributeValue = attributeValue || {}; + + var familyValue = getValue(attributeValue.family, index), + sizeValue = getValue(attributeValue.size, index), + colorValue = getValue(attributeValue.color, index); + + return { + family: coerceString( + attributeDefinition.family, familyValue, defaultValue.family), + size: coerceNumber( + attributeDefinition.size, sizeValue, defaultValue.size), + color: coerceColor( + attributeDefinition.color, colorValue, defaultValue.color) + }; +} + +function getValue(arrayOrScalar, index) { + var value; + if(!Array.isArray(arrayOrScalar)) value = arrayOrScalar; + else if(index < arrayOrScalar.length) value = arrayOrScalar[index]; + return value; +} + +function coerceString(attributeDefinition, value, defaultValue) { + if(typeof value === 'string') { + if(value || !attributeDefinition.noBlank) return value; + } + else if(typeof value === 'number') { + if(!attributeDefinition.strict) return String(value); + } + + return (defaultValue !== undefined) ? + defaultValue : + attributeDefinition.dflt; +} + +function coerceEnumerated(attributeDefinition, value, defaultValue) { + if(attributeDefinition.coerceNumber) value = +value; + + if(attributeDefinition.values.indexOf(value) !== -1) return value; + + return (defaultValue !== undefined) ? + defaultValue : + attributeDefinition.dflt; +} + +function coerceNumber(attributeDefinition, value, defaultValue) { + if(isNumeric(value)) { + value = +value; + + var min = attributeDefinition.min, + max = attributeDefinition.max, + isOutOfBounds = (min !== undefined && value < min) || + (max !== undefined && value > max); + + if(!isOutOfBounds) return value; + } + + return (defaultValue !== undefined) ? + defaultValue : + attributeDefinition.dflt; +} + +function coerceColor(attributeDefinition, value, defaultValue) { + if(tinycolor(value).isValid()) return value; + + return (defaultValue !== undefined) ? + defaultValue : + attributeDefinition.dflt; +} From 435b6140ac7ae77247a0b3e3aeb675825aeb1232 Mon Sep 17 00:00:00 2001 From: Nicolas Riesco Date: Tue, 15 Nov 2016 16:30:48 +0000 Subject: [PATCH 27/37] test: update bar font coercion tests --- test/jasmine/tests/bar_test.js | 155 +++++++++++++++++++++------------ 1 file changed, 97 insertions(+), 58 deletions(-) diff --git a/test/jasmine/tests/bar_test.js b/test/jasmine/tests/bar_test.js index c5039b4d62b..028ec6fb2e5 100644 --- a/test/jasmine/tests/bar_test.js +++ b/test/jasmine/tests/bar_test.js @@ -116,64 +116,6 @@ describe('Bar.supplyDefaults', function() { expect(traceOut.insidetextfont).not.toBe(traceOut.textfont); expect(traceOut.outsidetexfont).toBeUndefined(); }); - - it('should coerce text-related attributes', function() { - traceIn = { - y: [10, 20, 30, 40], - type: 'bar', - text: ['T1P1', 'T1P2', 'T1P3', 14], - textposition: ['inside', 'outside', 'BADVALUE'], - textfont: [{ - family: '"comic sans"', - color: '#02A' - }, { - color: '#A20' - }], - insidetextfont: [ - {size: 8}, - {size: 12}, - {size: 16} - ], - outsidetextfont: [ - null, - {size: 24}, - {size: 32} - ] - }; - - var layout = { - font: {family: 'arial', color: '#AAA', size: 13} - }; - - supplyDefaults(traceIn, traceOut, defaultColor, layout); - - var expected = { - y: [10, 20, 30, 40], - type: 'bar', - text: ['T1P1', 'T1P2', 'T1P3', '14'], - textposition: ['inside', 'outside', 'none'], - textfont: [ - {family: '"comic sans"', color: '#02A', size: 13}, - {family: 'arial', color: '#A20', size: 13} - ], - insidetextfont: [ - {family: '"comic sans"', color: '#02A', size: 8}, - {family: 'arial', color: '#A20', size: 12}, - {family: 'arial', color: '#AAA', size: 16} - ], - outsidetextfont: [ - {family: '"comic sans"', color: '#02A', size: 13}, - {family: 'arial', color: '#A20', size: 24}, - {family: 'arial', color: '#AAA', size: 32} - ] - }; - - expect(traceOut.text).toEqual(expected.text); - expect(traceOut.textposition).toEqual(expected.textposition); - expect(traceOut.textfont).toEqual(expected.textfont); - expect(traceOut.insidetextfont).toEqual(expected.insidetextfont); - expect(traceOut.outsidetextfont).toEqual(expected.outsidetextfont); - }); }); describe('heatmap calc / setPositions', function() { @@ -762,6 +704,21 @@ describe('A bar plot', function() { expect(pathBB.right).not.toBeGreaterThan(textBB.left); } + var colorMap = { + 'rgb(0, 0, 0)': 'black', + 'rgb(255, 0, 0)': 'red', + 'rgb(0, 128, 0)': 'green', + 'rgb(0, 0, 255)': 'blue' + }; + function assertTextFont(textNode, textFont, index) { + expect(textNode.style.fontFamily).toBe(textFont.family[index]); + expect(textNode.style.fontSize).toBe(textFont.size[index] + 'px'); + + var color = textNode.style.fill; + if(!colorMap[color]) colorMap[color] = color; + expect(colorMap[color]).toBe(textFont.color[index]); + } + function assertTextIsBeforePath(textNode, pathNode) { var textBB = textNode.getBoundingClientRect(), pathBB = pathNode.getBoundingClientRect(); @@ -965,6 +922,88 @@ describe('A bar plot', function() { done(); }); }); + + it('should coerce text-related attributes', function(done) { + var gd = createGraphDiv(), + data = [{ + y: [10, 20, 30, 40], + type: 'bar', + text: ['T1P1', 'T1P2', 13, 14], + textposition: ['inside', 'outside', 'auto', 'BADVALUE'], + textfont: { + family: ['"comic sans"'], + color: ['red', 'green'], + }, + insidetextfont: { + size: [8, 12, 16], + color: ['black'], + }, + outsidetextfont: { + size: [null, 24, 32] + } + }], + layout = { + font: {family: 'arial', color: 'blue', size: 13} + }; + + var expected = { + y: [10, 20, 30, 40], + type: 'bar', + text: ['T1P1', 'T1P2', '13', '14'], + textposition: ['inside', 'outside', 'none'], + textfont: { + family: ['"comic sans"', 'arial'], + color: ['red', 'green'], + size: [13, 13] + }, + insidetextfont: { + family: ['"comic sans"', 'arial', 'arial'], + color: ['black', 'green', 'blue'], + size: [8, 12, 16] + }, + outsidetextfont: { + family: ['"comic sans"', 'arial', 'arial'], + color: ['red', 'green', 'blue'], + size: [13, 24, 32] + } + }; + + Plotly.plot(gd, data, layout).then(function() { + var traceNodes = getAllTraceNodes(gd), + barNodes = getAllBarNodes(traceNodes[0]), + pathNodes = [ + barNodes[0].querySelector('path'), + barNodes[1].querySelector('path'), + barNodes[2].querySelector('path'), + barNodes[3].querySelector('path') + ], + textNodes = [ + barNodes[0].querySelector('text'), + barNodes[1].querySelector('text'), + barNodes[2].querySelector('text'), + barNodes[3].querySelector('text') + ], + i; + + // assert bar texts + for(i = 0; i < 3; i++) { + expect(textNodes[i].textContent).toBe(expected.text[i]); + } + + // assert bar positions + assertTextIsInsidePath(textNodes[0], pathNodes[0]); // inside + assertTextIsAbovePath(textNodes[1], pathNodes[1]); // outside + assertTextIsInsidePath(textNodes[2], pathNodes[2]); // auto -> inside + expect(textNodes[3]).toBe(null); // BADVALUE -> none + + // assert fonts + assertTextFont(textNodes[0], expected.insidetextfont, 0); + assertTextFont(textNodes[1], expected.outsidetextfont, 1); + assertTextFont(textNodes[2], expected.insidetextfont, 2); + + done(); + }); + }); }); describe('bar hover', function() { From c52f908e24dcf409452581149c9a53f73ed8d9a4 Mon Sep 17 00:00:00 2001 From: Nicolas Riesco Date: Tue, 15 Nov 2016 16:39:39 +0000 Subject: [PATCH 28/37] test: update mock bar_attrs_relative * Updated mock bar_attrs_relative to account for the change in the way to declare fonts. --- test/image/mocks/bar_attrs_relative.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/image/mocks/bar_attrs_relative.json b/test/image/mocks/bar_attrs_relative.json index aef938a403e..6bc0d9e1616 100644 --- a/test/image/mocks/bar_attrs_relative.json +++ b/test/image/mocks/bar_attrs_relative.json @@ -11,7 +11,7 @@ "width":[0.4,0.6,0.8,1], "text":["Three",2,"outside text",0], "textposition":"auto", - "textfont":[{"size":10}], + "textfont":{"size":[10]}, "y":[3,2,1,0], "x":[1,2,3,4], "type":"bar" From 57803a0d7a993a4dee9bc809e228a28912d8a851 Mon Sep 17 00:00:00 2001 From: Nicolas Riesco Date: Wed, 16 Nov 2016 16:24:34 +0000 Subject: [PATCH 29/37] test: update bar restyle test * Tested that `textposition` can also be restyled. --- test/jasmine/tests/bar_test.js | 76 ++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/test/jasmine/tests/bar_test.js b/test/jasmine/tests/bar_test.js index 028ec6fb2e5..36e24da3343 100644 --- a/test/jasmine/tests/bar_test.js +++ b/test/jasmine/tests/bar_test.js @@ -919,6 +919,82 @@ describe('A bar plot', function() { expect(cd[3][0].t.poffset).toBe(0); assertTraceField(cd, 't.bargroupwidth', [0.8, 0.8, 0.8, 0.8]); + var traceNodes = getAllTraceNodes(gd), + trace0Bar3 = getAllBarNodes(traceNodes[0])[3], + path03 = trace0Bar3.querySelector('path'), + text03 = trace0Bar3.querySelector('text'), + trace1Bar2 = getAllBarNodes(traceNodes[1])[2], + path12 = trace1Bar2.querySelector('path'), + text12 = trace1Bar2.querySelector('text'), + trace2Bar0 = getAllBarNodes(traceNodes[2])[0], + path20 = trace2Bar0.querySelector('path'), + text20 = trace2Bar0.querySelector('text'), + trace3Bar0 = getAllBarNodes(traceNodes[3])[0], + path30 = trace3Bar0.querySelector('path'), + text30 = trace3Bar0.querySelector('text'); + + expect(text03.textContent).toBe('4'); + expect(text12.textContent).toBe('outside text'); + expect(text20.textContent).toBe('-1'); + expect(text30.textContent).toBe('outside text'); + + assertTextIsAbovePath(text03, path03); // outside + assertTextIsAbovePath(text12, path12); // outside + assertTextIsInsidePath(text20, path20); // inside + assertTextIsBelowPath(text30, path30); // outside + + return Plotly.restyle(gd, 'textposition', 'inside'); + }).then(function() { + var cd = gd.calcdata; + assertPointField(cd, 'x', [ + [1.5, 2.4, 3.3, 4.2], [1.2, 2.3, 3.4, 4.5], + [1.5, 2.5, 3.5, 4.5], [1.4, 2.4, 3.4, 4.4]]); + assertPointField(cd, 'y', [ + [1, 2, 3, 4], [4, 4, 4, 4], + [-1, -3, -2, -4], [4, -3.25, -5, -6]]); + assertPointField(cd, 'b', [ + [0, 0, 0, 0], [1, 2, 3, 4], + [0, 0, 0, 0], [4, -3, -2, -4]]); + assertPointField(cd, 's', [ + [1, 2, 3, 4], [3, 2, 1, 0], + [-1, -3, -2, -4], [0, -0.25, -3, -2]]); + assertPointField(cd, 'p', [ + [1, 2, 3, 4], [1, 2, 3, 4], + [1, 2, 3, 4], [1, 2, 3, 4]]); + assertArrayField(cd[0][0], 't.barwidth', [1, 0.8, 0.6, 0.4]); + assertArrayField(cd[1][0], 't.barwidth', [0.4, 0.6, 0.8, 1]); + expect(cd[2][0].t.barwidth).toBe(1); + expect(cd[3][0].t.barwidth).toBe(0.8); + expect(cd[0][0].t.poffset).toBe(0); + expect(cd[1][0].t.poffset).toBe(0); + expect(cd[2][0].t.poffset).toBe(0); + expect(cd[3][0].t.poffset).toBe(0); + assertTraceField(cd, 't.bargroupwidth', [0.8, 0.8, 0.8, 0.8]); + + var traceNodes = getAllTraceNodes(gd), + trace0Bar3 = getAllBarNodes(traceNodes[0])[3], + path03 = trace0Bar3.querySelector('path'), + text03 = trace0Bar3.querySelector('text'), + trace1Bar2 = getAllBarNodes(traceNodes[1])[2], + path12 = trace1Bar2.querySelector('path'), + text12 = trace1Bar2.querySelector('text'), + trace2Bar0 = getAllBarNodes(traceNodes[2])[0], + path20 = trace2Bar0.querySelector('path'), + text20 = trace2Bar0.querySelector('text'), + trace3Bar0 = getAllBarNodes(traceNodes[3])[0], + path30 = trace3Bar0.querySelector('path'), + text30 = trace3Bar0.querySelector('text'); + + expect(text03.textContent).toBe('4'); + expect(text12.textContent).toBe('outside text'); + expect(text20.textContent).toBe('-1'); + expect(text30.textContent).toBe('outside text'); + + assertTextIsInsidePath(text03, path03); // inside + assertTextIsInsidePath(text12, path12); // inside + assertTextIsInsidePath(text20, path20); // inside + assertTextIsInsidePath(text30, path30); // inside + done(); }); }); From 3e53cefff9db27e5ff6112881f4979a157390d2a Mon Sep 17 00:00:00 2001 From: Nicolas Riesco Date: Wed, 16 Nov 2016 16:42:10 +0000 Subject: [PATCH 30/37] bar: bar texts only rotate clockwise --- src/traces/bar/plot.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/traces/bar/plot.js b/src/traces/bar/plot.js index e15c36bbf5c..01e4369a850 100644 --- a/src/traces/bar/plot.js +++ b/src/traces/bar/plot.js @@ -300,7 +300,7 @@ function getTransformToMoveInsideBar(x0, x1, y0, y1, textBB, orientation) { scale = Math.min(barHeight / textWidth, barWidth / textHeight); } - if(rotate) rotate = -90; // rotate counter-clockwise + if(rotate) rotate = 90; // rotate clockwise // compute text and target positions if(rotate) { @@ -388,6 +388,8 @@ function getTransformToMoveOutsideBar(x0, x1, y0, y1, textBB, orientation) { scale = barWidth / textHeight; } + if(rotate) rotate = 90; // rotate clockwise + // compute text and target positions var textX = (textBB.left + textBB.right) / 2, textY = (textBB.top + textBB.bottom) / 2, @@ -409,12 +411,10 @@ function getTransformToMoveOutsideBar(x0, x1, y0, y1, textBB, orientation) { // bar end is on the left hand side targetX = x1 - textpad - targetWidth / 2; targetY = (y0 + y1) / 2; - if(rotate) rotate = -90; // rotate counter-clockwise } else { targetX = x1 + textpad + targetWidth / 2; targetY = (y0 + y1) / 2; - if(rotate) rotate = 90; // rotate clockwise } } else { @@ -422,12 +422,10 @@ function getTransformToMoveOutsideBar(x0, x1, y0, y1, textBB, orientation) { // bar end is on the bottom targetX = (x0 + x1) / 2; targetY = y1 + textpad + targetHeight / 2; - if(rotate) rotate = -90; // rotate counter-clockwise } else { targetX = (x0 + x1) / 2; targetY = y1 - textpad - targetHeight / 2; - if(rotate) rotate = 90; // rotate clockwise } } From ce5d53f4d22fa9d35a209c05a5a88374e2d7b61c Mon Sep 17 00:00:00 2001 From: Nicolas Riesco Date: Wed, 16 Nov 2016 17:11:09 +0000 Subject: [PATCH 31/37] test: update mock bar_attrs_group_norm * Changed bar texts to "50%" to ensure the bounding box width is larger than its height (unfortunately, this wasn't case when the baseline images were generated; the height of the bounding box includes some white space). * Swapped the `texposition` of both traces so that illustrates the least favourable case for clockwise rotation. --- test/image/mocks/bar_attrs_group_norm.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/image/mocks/bar_attrs_group_norm.json b/test/image/mocks/bar_attrs_group_norm.json index 8115afefd36..0c4e252975f 100644 --- a/test/image/mocks/bar_attrs_group_norm.json +++ b/test/image/mocks/bar_attrs_group_norm.json @@ -1,15 +1,15 @@ { "data":[ { - "text":0.5, - "textposition":"outside", + "text":"50%", + "textposition":"inside", "outsidetextfont": {"size":32}, "base":[100,40,25,10], "x":[-50,10,25,40], "type":"bar" }, { - "text":0.5, - "textposition":"inside", + "text":"50%", + "textposition":"outside", "base":[0,60,75,90], "x":[50,-10,-25,-40], "type":"bar" From 8cefb5be17d5ac4b2a92f1e9759fec27f7102a97 Mon Sep 17 00:00:00 2001 From: Nicolas Riesco Date: Wed, 16 Nov 2016 17:16:25 +0000 Subject: [PATCH 32/37] test: update baseline images --- test/image/baselines/bar_attrs_group_norm.png | Bin 12736 -> 13236 bytes test/image/baselines/bar_attrs_relative.png | Bin 15025 -> 15036 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/test/image/baselines/bar_attrs_group_norm.png b/test/image/baselines/bar_attrs_group_norm.png index 01cc6532dad9e03da518d4610759c20acaf453c2..e40a162bb564960d124eab0e9e4deba17299c214 100644 GIT binary patch literal 13236 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 literal 12736 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%0A{$7$Y!c4XTu^5&bApXj;4^LhVg_b| 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 literal 15025 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 AXaE2J From 4bf9320ff479b2e15aebda05665fd02c993dc09b Mon Sep 17 00:00:00 2001 From: Nicolas Riesco Date: Wed, 16 Nov 2016 20:56:44 +0000 Subject: [PATCH 33/37] bar: simplify outside text positioning * Don't rotate outside labels. --- src/traces/bar/plot.js | 52 ++++++++---------------------------------- 1 file changed, 9 insertions(+), 43 deletions(-) diff --git a/src/traces/bar/plot.js b/src/traces/bar/plot.js index 01e4369a850..750acd661fd 100644 --- a/src/traces/bar/plot.js +++ b/src/traces/bar/plot.js @@ -339,56 +339,22 @@ function getTransformToMoveInsideBar(x0, x1, y0, y1, textBB, orientation) { } function getTransformToMoveOutsideBar(x0, x1, y0, y1, textBB, orientation) { - // In order to handle both orientations with the same algorithm, - // *textWidth* is defined as the text length in the direction of *barWidth*. - var barWidth, - textWidth, - textHeight; - if(orientation === 'h') { - barWidth = Math.abs(y1 - y0); - textWidth = textBB.height; - textHeight = textBB.width; - } - else { - barWidth = Math.abs(x1 - x0); - textWidth = textBB.width; - textHeight = textBB.height; - } + var barWidth = (orientation === 'h') ? + Math.abs(y1 - y0) : + Math.abs(x1 - x0), + textpad; - // apply text padding - var textpad; + // apply text padding if possible if(barWidth > 2 * TEXTPAD) { textpad = TEXTPAD; barWidth -= 2 * textpad; } // compute rotation and scale - var rotate, - scale; - if(textWidth <= barWidth) { - // no scale or rotation - rotate = false; - scale = 1; - } - else if(textHeight <= textWidth) { - // only scale - // (don't rotate to prevent having text perpendicular to the bar) - rotate = false; - scale = barWidth / textWidth; - } - else if(textHeight <= barWidth) { - // only rotation - rotate = true; - scale = 1; - } - else { - // both scale and rotation - // (rotation prevents having text perpendicular to the bar) - rotate = true; - scale = barWidth / textHeight; - } - - if(rotate) rotate = 90; // rotate clockwise + var rotate = false, + scale = (orientation === 'h') ? + Math.min(1, barWidth / textBB.height) : + Math.min(1, barWidth / textBB.width); // compute text and target positions var textX = (textBB.left + textBB.right) / 2, From 54272daafc1a2d4a4f5ff98bc490385d09ca7ba1 Mon Sep 17 00:00:00 2001 From: Nicolas Riesco Date: Wed, 16 Nov 2016 22:16:39 +0000 Subject: [PATCH 34/37] bar: update criteria for 'textposition: 'auto'` * 'auto' will draw a label inside a bar if larger than drawn outside. --- src/traces/bar/plot.js | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/traces/bar/plot.js b/src/traces/bar/plot.js index 750acd661fd..db68a618f50 100644 --- a/src/traces/bar/plot.js +++ b/src/traces/bar/plot.js @@ -165,7 +165,8 @@ function appendBarText(gd, bar, calcTrace, i, x0, x1, y0, y1) { } // get trace attributes - var trace = calcTrace[0].trace; + var trace = calcTrace[0].trace, + orientation = trace.orientation; var text = getText(trace, i); if(!text) return; @@ -211,8 +212,12 @@ function appendBarText(gd, bar, calcTrace, i, x0, x1, y0, y1) { fitsInside = (textWidth <= barWidth && textHeight <= barHeight), fitsInsideIfRotated = - (textWidth <= barHeight && textHeight <= barWidth); - if(textHasSize && (fitsInside || fitsInsideIfRotated)) { + (textWidth <= barHeight && textHeight <= barWidth), + fitsInsideIfShrunk = (orientation === 'h') ? + (barWidth >= textWidth * (barHeight / textHeight)) : + (barHeight >= textHeight * (barWidth / textWidth)); + if(textHasSize && + (fitsInside || fitsInsideIfRotated || fitsInsideIfShrunk)) { textPosition = 'inside'; } else { @@ -243,11 +248,11 @@ function appendBarText(gd, bar, calcTrace, i, x0, x1, y0, y1) { var transform; if(textPosition === 'outside') { transform = getTransformToMoveOutsideBar(x0, x1, y0, y1, textBB, - trace.orientation); + orientation); } else { transform = getTransformToMoveInsideBar(x0, x1, y0, y1, textBB, - trace.orientation); + orientation); } textSelection.attr('transform', transform); @@ -268,7 +273,7 @@ function getTransformToMoveInsideBar(x0, x1, y0, y1, textBB, orientation) { // apply text padding var textpad; - if(barWidth > 2 * TEXTPAD && barHeight > 2 * TEXTPAD) { + if(barWidth > (2 * TEXTPAD) && barHeight > (2 * TEXTPAD)) { textpad = TEXTPAD; barWidth -= 2 * textpad; barHeight -= 2 * textpad; From 97699c2d8d8d7de4c6b090418d717a2c6de21325 Mon Sep 17 00:00:00 2001 From: Nicolas Riesco Date: Wed, 16 Nov 2016 22:22:40 +0000 Subject: [PATCH 35/37] test: update bar tests * Updated bar tests to account for the change in criteria for `textposition:'auto'`. --- test/jasmine/tests/bar_test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/jasmine/tests/bar_test.js b/test/jasmine/tests/bar_test.js index 36e24da3343..a43a93b9028 100644 --- a/test/jasmine/tests/bar_test.js +++ b/test/jasmine/tests/bar_test.js @@ -939,7 +939,7 @@ describe('A bar plot', function() { expect(text30.textContent).toBe('outside text'); assertTextIsAbovePath(text03, path03); // outside - assertTextIsAbovePath(text12, path12); // outside + assertTextIsInsidePath(text12, path12); // inside assertTextIsInsidePath(text20, path20); // inside assertTextIsBelowPath(text30, path30); // outside From fea2b0520a8815f8dec0995ba946460de71657ee Mon Sep 17 00:00:00 2001 From: Nicolas Riesco Date: Wed, 16 Nov 2016 22:39:34 +0000 Subject: [PATCH 36/37] bar: describe `textposition` options --- src/traces/bar/attributes.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/traces/bar/attributes.js b/src/traces/bar/attributes.js index 6c40cd4264b..fa6059ddc06 100644 --- a/src/traces/bar/attributes.js +++ b/src/traces/bar/attributes.js @@ -52,20 +52,26 @@ module.exports = { dflt: 'none', arrayOk: true, description: [ - 'Specifies the location of the `textinfo`.' + 'Specifies the location of the `text`.', + '*inside* positions `text` inside, next to the bar end', + '(rotated and scaled if needed).', + '*outside* positions `text` outside, next to the bar end', + '(scaled if needed).', + '*auto* positions `text` inside or outside', + 'so that `text` size is maximized.' ].join(' ') }, textfont: extendFlat({}, textFontAttrs, { - description: 'Sets the font used for `textinfo`.' + description: 'Sets the font used for `text`.' }), insidetextfont: extendFlat({}, textFontAttrs, { - description: 'Sets the font used for `textinfo` lying inside the bar.' + description: 'Sets the font used for `text` lying inside the bar.' }), outsidetextfont: extendFlat({}, textFontAttrs, { - description: 'Sets the font used for `textinfo` lying outside the bar.' + description: 'Sets the font used for `text` lying outside the bar.' }), orientation: { From cdc298da36c454dbdfed287a596986a1d2e4ba78 Mon Sep 17 00:00:00 2001 From: Nicolas Riesco Date: Wed, 16 Nov 2016 23:49:07 +0000 Subject: [PATCH 37/37] test: update baseline images --- test/image/baselines/bar_attrs_group_norm.png | Bin 13236 -> 13794 bytes test/image/baselines/bar_attrs_relative.png | Bin 15036 -> 15133 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/test/image/baselines/bar_attrs_group_norm.png b/test/image/baselines/bar_attrs_group_norm.png index e40a162bb564960d124eab0e9e4deba17299c214..330d6607ce7eff85ad321ef3e2e66a13a9fa35f2 100644 GIT binary patch literal 13794 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 literal 13236 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 diff --git a/test/image/baselines/bar_attrs_relative.png b/test/image/baselines/bar_attrs_relative.png index 9cd4af74981afd179429c17c6b479c3658e3a1a2..385c60ead297608aa96bc4d555f0734dd3ec371e 100644 GIT binary patch literal 15133 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 z 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%0A{$7$Y!c4XTu^5&bApXj;4^LhVg_b| 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