From 98adfaee3ed79c6f23a7fb8bbe1fe27474229f23 Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Tue, 24 Jan 2017 17:42:03 -0500 Subject: [PATCH 1/9] IE9 fixes --- src/components/drawing/index.js | 1 + src/components/modebar/modebar.js | 2 +- src/lib/index.js | 8 ++++++++ src/lib/loggers.js | 29 +++++++++++++++++---------- src/plots/cartesian/graph_interact.js | 4 +++- 5 files changed, 31 insertions(+), 13 deletions(-) diff --git a/src/components/drawing/index.js b/src/components/drawing/index.js index 368365db908..241a2fff914 100644 --- a/src/components/drawing/index.js +++ b/src/components/drawing/index.js @@ -112,6 +112,7 @@ drawing.lineGroupStyle = function(s, lw, lc, ld) { }; drawing.dashLine = function(s, dash, lineWidth) { + lineWidth = lineWidth || 0; var dlw = Math.max(lineWidth, 3); if(dash === 'solid') dash = ''; diff --git a/src/components/modebar/modebar.js b/src/components/modebar/modebar.js index cf6e7fbb5c1..054a8a91928 100644 --- a/src/components/modebar/modebar.js +++ b/src/components/modebar/modebar.js @@ -147,7 +147,7 @@ proto.createButton = function(config) { } button.setAttribute('data-toggle', config.toggle || false); - if(config.toggle) button.classList.add('active'); + if(config.toggle) d3.select(button).classed('active', true); button.appendChild(this.createIcon(config.icon || Icons.question)); button.setAttribute('data-gravity', config.gravity || 'n'); diff --git a/src/lib/index.js b/src/lib/index.js index b7d1bdefbd2..01bace71b4c 100644 --- a/src/lib/index.js +++ b/src/lib/index.js @@ -556,6 +556,14 @@ lib.isIE = function() { return typeof window.navigator.msSaveBlob !== 'undefined'; }; +/** + * Duck typing to recognize a d3 selection, mostly for IE9's benefit + * because it doesn't handle instanceof like modern browsers + */ +lib.isD3Selection = function(obj) { + return obj && (typeof obj.classed === 'function'); +}; + /** * Converts a string path to an object. diff --git a/src/lib/loggers.js b/src/lib/loggers.js index 02793c74439..428f053e000 100644 --- a/src/lib/loggers.js +++ b/src/lib/loggers.js @@ -28,11 +28,7 @@ loggers.log = function() { messages.push(arguments[i]); } - if(console.trace) { - console.trace.apply(console, messages); - } else { - console.log.apply(console, messages); - } + apply(console.trace || console.log, messages); } }; @@ -44,11 +40,7 @@ loggers.warn = function() { messages.push(arguments[i]); } - if(console.trace) { - console.trace.apply(console, messages); - } else { - console.log.apply(console, messages); - } + apply(console.trace || console.log, messages); } }; @@ -60,6 +52,21 @@ loggers.error = function() { messages.push(arguments[i]); } - console.error.apply(console, arguments); + apply(console.error, messages); } }; + +/* + * Robust apply, for IE9 where console.log doesn't support + * apply like other functions do + */ +function apply(f, args) { + if(f.apply) { + f.apply(f, args); + } + else { + for(var i = 0; i < args.length; i++) { + f(args[i]); + } + } +} diff --git a/src/plots/cartesian/graph_interact.js b/src/plots/cartesian/graph_interact.js index 9a7cff983bf..7fea9cb893e 100644 --- a/src/plots/cartesian/graph_interact.js +++ b/src/plots/cartesian/graph_interact.js @@ -792,7 +792,9 @@ fx.loneHover = function(hoverItem, opts) { }; fx.loneUnhover = function(containerOrSelection) { - var selection = containerOrSelection instanceof d3.selection ? + // duck type whether the arg is a d3 selection because ie9 doesn't + // handle instanceof like modern browsers do. + var selection = Lib.isD3Selection(containerOrSelection) ? containerOrSelection : d3.select(containerOrSelection); From ebc1a102b0ad28fcd838c4edf6e678cb351af7fa Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Tue, 24 Jan 2017 22:01:29 -0500 Subject: [PATCH 2/9] explicitly, rather than implicitly, default choropleth linewidth to 1 --- src/components/drawing/index.js | 2 +- src/traces/choropleth/attributes.js | 2 +- src/traces/choropleth/plot.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/drawing/index.js b/src/components/drawing/index.js index 241a2fff914..927a79dab2d 100644 --- a/src/components/drawing/index.js +++ b/src/components/drawing/index.js @@ -112,7 +112,7 @@ drawing.lineGroupStyle = function(s, lw, lc, ld) { }; drawing.dashLine = function(s, dash, lineWidth) { - lineWidth = lineWidth || 0; + lineWidth = +lineWidth || 0; var dlw = Math.max(lineWidth, 3); if(dash === 'solid') dash = ''; diff --git a/src/traces/choropleth/attributes.js b/src/traces/choropleth/attributes.js index 63ab5c2b859..85523db3ed5 100644 --- a/src/traces/choropleth/attributes.js +++ b/src/traces/choropleth/attributes.js @@ -37,7 +37,7 @@ module.exports = extendFlat({}, { marker: { line: { color: ScatterGeoMarkerLineAttrs.color, - width: ScatterGeoMarkerLineAttrs.width + width: extendFlat({}, ScatterGeoMarkerLineAttrs.width, {dflt: 1}) } }, hoverinfo: extendFlat({}, plotAttrs.hoverinfo, { diff --git a/src/traces/choropleth/plot.js b/src/traces/choropleth/plot.js index 0cb143e688d..86d947a6fce 100644 --- a/src/traces/choropleth/plot.js +++ b/src/traces/choropleth/plot.js @@ -165,7 +165,7 @@ plotChoropleth.style = function(geo) { d3.select(this) .attr('fill', function(pt) { return sclFunc(pt.z); }) .call(Color.stroke, pt.mlc || markerLine.color) - .call(Drawing.dashLine, '', pt.mlw || markerLine.width); + .call(Drawing.dashLine, '', pt.mlw || markerLine.width || 0); }); }); }; From 12aeb9d6f8dd9b3085a8ff1d08f713846cbea965 Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Wed, 25 Jan 2017 00:21:04 -0500 Subject: [PATCH 3/9] tests for IE9 fixes --- tasks/test_syntax.js | 22 ++++- test/jasmine/tests/lib_test.js | 167 +++++++++++++++++++++++++++++++++ 2 files changed, 184 insertions(+), 5 deletions(-) diff --git a/tasks/test_syntax.js b/tasks/test_syntax.js index 4f2326c92cb..6561bd24ff9 100644 --- a/tasks/test_syntax.js +++ b/tasks/test_syntax.js @@ -13,7 +13,7 @@ var bundleTestGlob = path.join(constants.pathToJasmineBundleTests, '**/*.js'); // main assertJasmineSuites(); -assertHeaders(); +assertSrcContents(); assertFileNames(); assertCircularDeps(); @@ -44,8 +44,12 @@ function assertJasmineSuites() { }); } -// check for header in src and lib files -function assertHeaders() { +/* + * tests about the contents of source (and lib) files: + * - check for header comment + * - check that we don't have .classList + */ +function assertSrcContents() { var licenseSrc = constants.licenseSrc; var licenseStr = licenseSrc.substring(2, licenseSrc.length - 2); var logs = []; @@ -56,7 +60,15 @@ function assertHeaders() { // parse through code string while keeping track of comments var comments = []; - falafel(code, {onComment: comments, locations: true}, function() {}); + falafel(code, {onComment: comments, locations: true}, function(node) { + // look for .classList + if(node.type === 'MemberExpression') { + var parts = node.source().split('.'); + if(parts[parts.length - 1] === 'classList') { + logs.push(file + ' : contains .classList (IE failure)'); + } + } + }); var header = comments[0]; @@ -70,7 +82,7 @@ function assertHeaders() { } }); - log('correct headers in lib/ and src/', logs); + log('correct headers and contents in lib/ and src/', logs); }); } diff --git a/test/jasmine/tests/lib_test.js b/test/jasmine/tests/lib_test.js index 3ef05bfd90a..a68ad041540 100644 --- a/test/jasmine/tests/lib_test.js +++ b/test/jasmine/tests/lib_test.js @@ -1,6 +1,7 @@ var Lib = require('@src/lib'); var setCursor = require('@src/lib/setcursor'); var overrideCursor = require('@src/lib/override_cursor'); +var config = require('@src/plot_api/plot_config'); var d3 = require('d3'); var Plotly = require('@lib'); @@ -1599,6 +1600,172 @@ describe('Test lib.js:', function() { expect(Lib.isPlotDiv({})).toBe(false); }); }); + + describe('isD3Selection', function() { + var gd; + + beforeEach(function() { + gd = createGraphDiv(); + }); + + afterEach(function() { + destroyGraphDiv(); + Plotly.setPlotConfig({ queueLength: 0 }); + }); + + it('recognizes real and duck typed selections', function() { + var yesSelections = [ + d3.select(gd), + // this is what got us into trouble actually - d3 selections can + // contain non-nodes - say for example d3 selections! then they + // don't work correctly. But it makes a convenient test! + d3.select(1), + // just showing what we actually do in this function: duck type + // using the `classed` method. + {classed: function(v) { return !!v; }} + ]; + + yesSelections.forEach(function(v) { + expect(Lib.isD3Selection(v)).toBe(true, v); + }); + }); + + it('rejects non-selections', function() { + var notSelections = [ + 1, + 'path', + [1, 2], + [[1, 2]], + {classed: 1}, + gd + ]; + + notSelections.forEach(function(v) { + expect(Lib.isD3Selection(v)).toBe(false, v); + }); + }); + }); + + describe('loggers', function() { + var stashConsole, + stashLogLevel; + + function consoleFn(name, hasApply, messages) { + var out = function() { + var args = []; + for(var i = 0; i < arguments.length; i++) args.push(arguments[i]); + messages.push([name, args]); + }; + + if(!hasApply) out.apply = undefined; + + return out; + } + + function mockConsole(hasApply, hasTrace) { + var out = { + MESSAGES: [] + }; + out.log = consoleFn('log', hasApply, out.MESSAGES); + out.error = consoleFn('error', hasApply, out.MESSAGES); + + if(hasTrace) out.trace = consoleFn('trace', hasApply, out.MESSAGES); + + return out; + } + + beforeEach(function() { + stashConsole = window.console; + stashLogLevel = config.logging; + }); + + afterEach(function() { + window.console = stashConsole; + config.logging = stashLogLevel; + }); + + it('emits one console message if apply is available', function() { + var c = window.console = mockConsole(true, true); + config.logging = 2; + + Lib.log('tick', 'tock', 'tick', 'tock', 1); + Lib.warn('I\'m', 'a', 'little', 'cuckoo', 'clock', [1, 2]); + Lib.error('cuckoo!', 'cuckoo!!!', {a: 1, b: 2}); + + expect(c.MESSAGES).toEqual([ + ['trace', ['LOG:', 'tick', 'tock', 'tick', 'tock', 1]], + ['trace', ['WARN:', 'I\'m', 'a', 'little', 'cuckoo', 'clock', [1, 2]]], + ['error', ['ERROR:', 'cuckoo!', 'cuckoo!!!', {a: 1, b: 2}]] + ]); + }); + + it('falls back on console.log if no trace', function() { + var c = window.console = mockConsole(true, false); + config.logging = 2; + + Lib.log('Hi'); + Lib.warn(42); + + expect(c.MESSAGES).toEqual([ + ['log', ['LOG:', 'Hi']], + ['log', ['WARN:', 42]] + ]); + }); + + it('falls back on separate calls if no apply', function() { + var c = window.console = mockConsole(false, false); + config.logging = 2; + + Lib.log('tick', 'tock', 'tick', 'tock', 1); + Lib.warn('I\'m', 'a', 'little', 'cuckoo', 'clock', [1, 2]); + Lib.error('cuckoo!', 'cuckoo!!!', {a: 1, b: 2}); + + expect(c.MESSAGES).toEqual([ + ['log', ['LOG:']], + ['log', ['tick']], + ['log', ['tock']], + ['log', ['tick']], + ['log', ['tock']], + ['log', [1]], + ['log', ['WARN:']], + ['log', ['I\'m']], + ['log', ['a']], + ['log', ['little']], + ['log', ['cuckoo']], + ['log', ['clock']], + ['log', [[1, 2]]], + ['error', ['ERROR:']], + ['error', ['cuckoo!']], + ['error', ['cuckoo!!!']], + ['error', [{a: 1, b: 2}]] + ]); + }); + + it('omits .log at log level 1', function() { + var c = window.console = mockConsole(true, true); + config.logging = 1; + + Lib.log(1); + Lib.warn(2); + Lib.error(3); + + expect(c.MESSAGES).toEqual([ + ['trace', ['WARN:', 2]], + ['error', ['ERROR:', 3]] + ]); + }); + + it('logs nothing at log level 0', function() { + var c = window.console = mockConsole(true, true); + config.logging = 0; + + Lib.log(1); + Lib.warn(2); + Lib.error(3); + + expect(c.MESSAGES).toEqual([]); + }); + }); }); describe('Queue', function() { From 4aa1a4535a63633f38256159fc751ca59dd7b2bd Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Tue, 31 Jan 2017 00:39:52 -0500 Subject: [PATCH 4/9] strict-d3: prevent stuff like 'nanpx' from selection.style --- devtools/test_dashboard/index.html | 1 + test/image/index.html | 1 + test/image/strict-d3.js | 74 ++++++++++++++++++++++++++++++ 3 files changed, 76 insertions(+) create mode 100644 test/image/strict-d3.js diff --git a/devtools/test_dashboard/index.html b/devtools/test_dashboard/index.html index f553c983514..d52115330d7 100644 --- a/devtools/test_dashboard/index.html +++ b/devtools/test_dashboard/index.html @@ -21,6 +21,7 @@ + diff --git a/test/image/index.html b/test/image/index.html index efa5c6d2edc..9907839a5d5 100644 --- a/test/image/index.html +++ b/test/image/index.html @@ -5,6 +5,7 @@ + diff --git a/test/image/strict-d3.js b/test/image/strict-d3.js new file mode 100644 index 00000000000..28ba52c4e74 --- /dev/null +++ b/test/image/strict-d3.js @@ -0,0 +1,74 @@ +/* + * strict-d3: wrap selection.style to prohibit specific incorrect style values + * that are known to cause problems in IE (at least IE9) + */ + +/* global Plotly:false */ +(function() { + 'use strict'; + + var selProto = Plotly.d3.selection.prototype; + + var originalSelStyle = selProto.style; + + selProto.style = function() { + var sel = this, + obj = arguments[0]; + + if(sel.size()) { + if(typeof obj === 'string') { + checkVal(obj, arguments[1]); + } + else { + Object.keys(obj).forEach(function(key) { checkVal(key, obj[key]); }); + } + } + + return originalSelStyle.apply(sel, arguments); + }; + + function checkVal(key, val) { + if(typeof val === 'string') { + // in case of multipart styles (stroke-dasharray, margins, etc) + // test each part separately + val.split(/[, ]/g).forEach(function(valPart) { + var pxSplit = valPart.length - 2; + if(valPart.substr(pxSplit) === 'px' && !isNumeric(valPart.substr(0, pxSplit))) { + throw new Error('d3 selection.style called with value: ' + val); + } + }); + } + + } + + // below ripped from fast-isnumeric so I don't need to build this file + + function allBlankCharCodes(str) { + var l = str.length, + a; + for(var i = 0; i < l; i++) { + a = str.charCodeAt(i); + if((a < 9 || a > 13) && (a !== 32) && (a !== 133) && (a !== 160) && + (a !== 5760) && (a !== 6158) && (a < 8192 || a > 8205) && + (a !== 8232) && (a !== 8233) && (a !== 8239) && (a !== 8287) && + (a !== 8288) && (a !== 12288) && (a !== 65279)) { + return false; + } + } + return true; + } + + function isNumeric(n) { + var type = typeof n; + if(type === 'string') { + var original = n; + n = +n; + // whitespace strings cast to zero - filter them out + if(n === 0 && allBlankCharCodes(original)) return false; + } + else if(type !== 'number') return false; + + return n - n < 1; + } + +})(); From 61ade949e1c35ed2513551bac07c7eddbeac59e3 Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Tue, 31 Jan 2017 12:15:55 -0500 Subject: [PATCH 5/9] fix #1247 - titles avoid labels in IE IE puts a space between numbers in transforms, even if you set them with a comma --- src/components/titles/index.js | 10 ++++++---- src/lib/index.js | 6 +++++- src/plots/cartesian/axes.js | 10 +++------- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/components/titles/index.js b/src/components/titles/index.js index c8d5a8f1a14..c4b18c7505e 100644 --- a/src/components/titles/index.js +++ b/src/components/titles/index.js @@ -151,10 +151,12 @@ Titles.draw = function(gd, titleClass, options) { else { // so we don't have to offset each avoided element, // give the title the opposite offset - titlebb.left -= avoid.offsetLeft; - titlebb.right -= avoid.offsetLeft; - titlebb.top -= avoid.offsetTop; - titlebb.bottom -= avoid.offsetTop; + var offsetLeft = avoid.offsetLeft || 0, + offsetTop = avoid.offsetTop || 0; + titlebb.left -= offsetLeft; + titlebb.right -= offsetLeft; + titlebb.top -= offsetTop; + titlebb.bottom -= offsetTop; // iterate over a set of elements (avoid.selection) // to avoid collisions with diff --git a/src/lib/index.js b/src/lib/index.js index 01bace71b4c..f078d15f93e 100644 --- a/src/lib/index.js +++ b/src/lib/index.js @@ -451,8 +451,12 @@ lib.addStyleRule = function(selector, styleString) { else lib.warn('addStyleRule failed'); }; -lib.getTranslate = function(element) { +// TODO (alexcjohnson): the following translate/scale functions should +// get moved to components/drawing rather than the generic lib. +lib.getTranslate = function(element) { + // Note the separator [^\d] between x and y in this regex + // We generally use ',' but IE will convert it to ' ' var re = /.*\btranslate\((\d*\.?\d*)[^\d]*(\d*\.?\d*)[^\d].*/, getter = element.attr ? 'attr' : 'getAttribute', transform = element[getter]('transform') || ''; diff --git a/src/plots/cartesian/axes.js b/src/plots/cartesian/axes.js index f93fbd660fa..321b7aa8edb 100644 --- a/src/plots/cartesian/axes.js +++ b/src/plots/cartesian/axes.js @@ -1897,13 +1897,9 @@ axes.doTicks = function(gd, axid, skipTitle) { x, y; if(avoidSelection.size()) { - var avoidTransform = d3.select(avoidSelection.node().parentNode) - .attr('transform') - .match(/translate\(([-\.\d]+),([-\.\d]+)\)/); - if(avoidTransform) { - avoid.offsetLeft = +avoidTransform[1]; - avoid.offsetTop = +avoidTransform[2]; - } + var translation = Lib.getTranslate(avoidSelection.node().parentNode); + avoid.offsetLeft = translation.x; + avoid.offsetTop = translation.y; } if(axLetter === 'x') { From 21059303ee919812e4b2e281df5f71f7c77cf7be Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Tue, 31 Jan 2017 15:14:01 -0500 Subject: [PATCH 6/9] fix #746 issue with newline characters in text in IE --- src/lib/svg_text_utils.js | 9 +++++++++ test/image/mocks/annotations.json | 2 +- test/image/mocks/axes_enumerated_ticks.json | 4 ++-- test/image/mocks/text_chart_arrays.json | 8 ++++---- 4 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/lib/svg_text_utils.js b/src/lib/svg_text_utils.js index 10df69e8679..a55334d5157 100644 --- a/src/lib/svg_text_utils.js +++ b/src/lib/svg_text_utils.js @@ -250,6 +250,8 @@ var UNICODE_TO_ENTITY = Object.keys(stringMappings.unicodeToEntity).map(function }; }); +var NEWLINES = /(\r\n?|\n)/g; + exports.plainText = function(_str) { // strip out our pseudo-html so we have a readable // version to put into text fields @@ -278,6 +280,13 @@ function encodeForHTML(_str) { function convertToSVG(_str) { _str = convertEntities(_str); + // normalize behavior between IE and others wrt newlines and whitespace:pre + // this combination makes IE barf https://github.com/plotly/plotly.js/issues/746 + // Chrome and FF display \n, \r, or \r\n as a space in this mode. + // I feel like at some point we turned these into
but currently we don't so + // I'm just going to cement what we do now in Chrome and FF + _str = _str.replace(NEWLINES, ' '); + var result = _str .split(/(<[^<>]*>)/).map(function(d) { var match = d.match(/<(\/?)([^ >]*)\s*(.*)>/i), diff --git a/test/image/mocks/annotations.json b/test/image/mocks/annotations.json index ed767593165..98d39081075 100644 --- a/test/image/mocks/annotations.json +++ b/test/image/mocks/annotations.json @@ -21,7 +21,7 @@ {"text":"left
justified","showarrow":false,"align":"left","x":1,"y":4}, {"text":"center
justified","showarrow":false,"x":2,"y":4}, {"text":"right
justified","showarrow":false,"align":"right","x":3,"y":4}, - {"text":"no arrow
page auto TR","showarrow":false,"xref":"paper","yref":"paper","x":0.75,"y":0.75}, + {"text":"no\narrow
page\rauto\r\nTR","showarrow":false,"xref":"paper","yref":"paper","x":0.75,"y":0.75}, {"text":"no arrow
page auto ML","showarrow":false,"xref":"paper","yref":"paper","x":0.25,"y":0.5}, {"text":"no arrow
page auto BC","showarrow":false,"xref":"paper","yref":"paper","x":0.5,"y":0.25}, {"text":"default"}, diff --git a/test/image/mocks/axes_enumerated_ticks.json b/test/image/mocks/axes_enumerated_ticks.json index a5016608717..91bb9c35050 100644 --- a/test/image/mocks/axes_enumerated_ticks.json +++ b/test/image/mocks/axes_enumerated_ticks.json @@ -32,8 +32,8 @@ "width": 400, "xaxis": { "ticktext": [ - "green eggs", - "& ham", + "green\neggs", + "&\r\nham", "H2O", "Gorgonzola" ], diff --git a/test/image/mocks/text_chart_arrays.json b/test/image/mocks/text_chart_arrays.json index 3fa728c52ea..90af525162c 100644 --- a/test/image/mocks/text_chart_arrays.json +++ b/test/image/mocks/text_chart_arrays.json @@ -12,11 +12,11 @@ 1 ], "mode": "lines+markers+text", - "name": "Lines, Markers and Text", + "name": "Lines,\nMarkers\rand\r\nText", "text": [ - "Text A", - "Text B", - "Text C" + "Text\nA", + "Text\rB", + "Text\r\nC" ], "textfont": { "family": [ From 0d555b4682d46d76bce463e9e6c7fdcac1464f32 Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Tue, 31 Jan 2017 15:40:16 -0500 Subject: [PATCH 7/9] test that svg text output has no newlines in it --- test/jasmine/tests/hover_label_test.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/test/jasmine/tests/hover_label_test.js b/test/jasmine/tests/hover_label_test.js index 064025b7449..2e76a3ab0e5 100644 --- a/test/jasmine/tests/hover_label_test.js +++ b/test/jasmine/tests/hover_label_test.js @@ -103,7 +103,9 @@ describe('hover info', function() { var mockCopy = Lib.extendDeep({}, mock); mockCopy.data[0].text = []; - mockCopy.data[0].text[17] = 'hover text'; + // we convert newlines to spaces + // see https://github.com/plotly/plotly.js/issues/746 + mockCopy.data[0].text[17] = 'hover\ntext\n\rwith\r\nspaces\n\nnot\rnewlines'; mockCopy.data[0].hoverinfo = 'text'; beforeEach(function(done) { @@ -123,7 +125,8 @@ describe('hover info', function() { expect(d3.selectAll('g.axistext').size()).toEqual(0); expect(d3.selectAll('g.hovertext').size()).toEqual(1); - expect(d3.selectAll('g.hovertext').select('text').html()).toEqual('hover text'); + expect(d3.selectAll('g.hovertext').select('text').html()) + .toEqual('hover text with spaces not newlines'); }); }); From 66baf516524d114ba2044b2f432a3c3441c5ea43 Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Tue, 31 Jan 2017 16:42:43 -0500 Subject: [PATCH 8/9] move (set|get)(translate|scale|pointgroupscale) into components/drawing --- src/components/annotations/draw.js | 6 +- src/components/drawing/index.js | 102 +++++++++ src/components/legend/draw.js | 22 +- src/components/sliders/draw.js | 15 +- src/components/updatemenus/draw.js | 7 +- src/lib/index.js | 105 ---------- src/plot_api/subroutines.js | 2 +- src/plots/cartesian/axes.js | 2 +- src/plots/cartesian/dragbox.js | 10 +- src/plots/cartesian/transition_axes.js | 22 +- test/jasmine/tests/click_test.js | 5 +- test/jasmine/tests/drawing_test.js | 280 +++++++++++++++++++++---- test/jasmine/tests/lib_test.js | 204 ------------------ 13 files changed, 390 insertions(+), 392 deletions(-) diff --git a/src/components/annotations/draw.js b/src/components/annotations/draw.js index 815701a4ddf..f6cb37b8515 100644 --- a/src/components/annotations/draw.js +++ b/src/components/annotations/draw.js @@ -480,7 +480,7 @@ function drawOne(gd, index, opt, value) { annTextBG.call(Drawing.setRect, borderwidth / 2, borderwidth / 2, outerwidth - borderwidth, outerheight - borderwidth); - annTextGroupInner.call(Lib.setTranslate, + annTextGroupInner.call(Drawing.setTranslate, Math.round(annPosPx.x.text - outerwidth / 2), Math.round(annPosPx.y.text - outerheight / 2)); @@ -601,7 +601,7 @@ function drawOne(gd, index, opt, value) { dragElement.init({ element: arrowDrag.node(), prepFn: function() { - var pos = Lib.getTranslate(annTextGroupInner); + var pos = Drawing.getTranslate(annTextGroupInner); annx0 = pos.x; anny0 = pos.y; @@ -617,7 +617,7 @@ function drawOne(gd, index, opt, value) { var annxy0 = applyTransform(annx0, anny0), xcenter = annxy0[0] + dx, ycenter = annxy0[1] + dy; - annTextGroupInner.call(Lib.setTranslate, xcenter, ycenter); + annTextGroupInner.call(Drawing.setTranslate, xcenter, ycenter); update[annbase + '.x'] = xa ? xa.p2r(xa.r2p(options.x) + dx) : diff --git a/src/components/drawing/index.js b/src/components/drawing/index.js index 927a79dab2d..6059ce377ef 100644 --- a/src/components/drawing/index.js +++ b/src/components/drawing/index.js @@ -576,3 +576,105 @@ drawing.setClipUrl = function(s, localId) { s.attr('clip-path', 'url(' + url + ')'); }; + +drawing.getTranslate = function(element) { + // Note the separator [^\d] between x and y in this regex + // We generally use ',' but IE will convert it to ' ' + var re = /.*\btranslate\((\d*\.?\d*)[^\d]*(\d*\.?\d*)[^\d].*/, + getter = element.attr ? 'attr' : 'getAttribute', + transform = element[getter]('transform') || ''; + + var translate = transform.replace(re, function(match, p1, p2) { + return [p1, p2].join(' '); + }) + .split(' '); + + return { + x: +translate[0] || 0, + y: +translate[1] || 0 + }; +}; + +drawing.setTranslate = function(element, x, y) { + + var re = /(\btranslate\(.*?\);?)/, + getter = element.attr ? 'attr' : 'getAttribute', + setter = element.attr ? 'attr' : 'setAttribute', + transform = element[getter]('transform') || ''; + + x = x || 0; + y = y || 0; + + transform = transform.replace(re, '').trim(); + transform += ' translate(' + x + ', ' + y + ')'; + transform = transform.trim(); + + element[setter]('transform', transform); + + return transform; +}; + +drawing.getScale = function(element) { + + var re = /.*\bscale\((\d*\.?\d*)[^\d]*(\d*\.?\d*)[^\d].*/, + getter = element.attr ? 'attr' : 'getAttribute', + transform = element[getter]('transform') || ''; + + var translate = transform.replace(re, function(match, p1, p2) { + return [p1, p2].join(' '); + }) + .split(' '); + + return { + x: +translate[0] || 1, + y: +translate[1] || 1 + }; +}; + +drawing.setScale = function(element, x, y) { + + var re = /(\bscale\(.*?\);?)/, + getter = element.attr ? 'attr' : 'getAttribute', + setter = element.attr ? 'attr' : 'setAttribute', + transform = element[getter]('transform') || ''; + + x = x || 1; + y = y || 1; + + transform = transform.replace(re, '').trim(); + transform += ' scale(' + x + ', ' + y + ')'; + transform = transform.trim(); + + element[setter]('transform', transform); + + return transform; +}; + +drawing.setPointGroupScale = function(selection, x, y) { + var t, scale, re; + + x = x || 1; + y = y || 1; + + if(x === 1 && y === 1) { + scale = ''; + } else { + // The same scale transform for every point: + scale = ' scale(' + x + ',' + y + ')'; + } + + // A regex to strip any existing scale: + re = /\s*sc.*/; + + selection.each(function() { + // Get the transform: + t = (this.getAttribute('transform') || '').replace(re, ''); + t += scale; + t = t.trim(); + + // Append the scale transform + this.setAttribute('transform', t); + }); + + return scale; +}; diff --git a/src/components/legend/draw.js b/src/components/legend/draw.js index 0fbab3a3e13..c51e0f8d80b 100644 --- a/src/components/legend/draw.js +++ b/src/components/legend/draw.js @@ -196,7 +196,7 @@ module.exports = function draw(gd) { // Set size and position of all the elements that make up a legend: // legend, background and border, scroll box and scroll bar - Lib.setTranslate(legend, lx, ly); + Drawing.setTranslate(legend, lx, ly); var scrollBarYMax = legendHeight - constants.scrollBarHeight - @@ -214,7 +214,7 @@ module.exports = function draw(gd) { y: opts.borderwidth / 2 }); - Lib.setTranslate(scrollBox, 0, 0); + Drawing.setTranslate(scrollBox, 0, 0); clipPath.select('rect').attr({ width: legendWidth - 2 * opts.borderwidth, @@ -289,7 +289,7 @@ module.exports = function draw(gd) { function scrollHandler(scrollBarY, scrollBoxY) { scrollBox .attr('data-scroll', scrollBoxY) - .call(Lib.setTranslate, 0, scrollBoxY); + .call(Drawing.setTranslate, 0, scrollBoxY); scrollBar.call( Drawing.setRect, @@ -311,7 +311,7 @@ module.exports = function draw(gd) { dragElement.init({ element: legend.node(), prepFn: function() { - var transform = Lib.getTranslate(legend); + var transform = Drawing.getTranslate(legend); x0 = transform.x; y0 = transform.y; @@ -320,7 +320,7 @@ module.exports = function draw(gd) { var newX = x0 + dx, newY = y0 + dy; - Lib.setTranslate(legend, newX, newY); + Drawing.setTranslate(legend, newX, newY); xf = dragElement.align(newX, 0, gs.l, gs.l + gs.w, opts.xanchor); yf = dragElement.align(newY, 0, gs.t + gs.h, gs.t, opts.yanchor); @@ -464,7 +464,7 @@ function computeTextDimensions(g, gd) { height = mathjaxBB.height; width = mathjaxBB.width; - Lib.setTranslate(mathjaxGroup, 0, (height / 4)); + Drawing.setTranslate(mathjaxGroup, 0, (height / 4)); } else { var text = g.selectAll('.legendtext'), @@ -496,7 +496,7 @@ function computeLegendDimensions(gd, groups, traces) { if(helpers.isVertical(opts)) { if(isGrouped) { groups.each(function(d, i) { - Lib.setTranslate(this, 0, i * opts.tracegroupgap); + Drawing.setTranslate(this, 0, i * opts.tracegroupgap); }); } @@ -508,7 +508,7 @@ function computeLegendDimensions(gd, groups, traces) { textHeight = legendItem.height, textWidth = legendItem.width; - Lib.setTranslate(this, + Drawing.setTranslate(this, borderwidth, (5 + borderwidth + opts.height + textHeight / 2)); @@ -559,7 +559,7 @@ function computeLegendDimensions(gd, groups, traces) { } groups.each(function(d, i) { - Lib.setTranslate(this, groupXOffsets[i], 0); + Drawing.setTranslate(this, groupXOffsets[i], 0); }); groups.each(function() { @@ -571,7 +571,7 @@ function computeLegendDimensions(gd, groups, traces) { var legendItem = d[0], textHeight = legendItem.height; - Lib.setTranslate(this, + Drawing.setTranslate(this, 0, (5 + borderwidth + groupHeight + textHeight / 2)); @@ -626,7 +626,7 @@ function computeLegendDimensions(gd, groups, traces) { maxTraceHeight = 0; } - Lib.setTranslate(this, + Drawing.setTranslate(this, (borderwidth + offsetX), (5 + borderwidth + legendItem.height / 2) + rowHeight); diff --git a/src/components/sliders/draw.js b/src/components/sliders/draw.js index a60a8f7139e..50c2ef0f35e 100644 --- a/src/components/sliders/draw.js +++ b/src/components/sliders/draw.js @@ -12,7 +12,6 @@ var d3 = require('d3'); var Plots = require('../../plots/plots'); -var Lib = require('../../lib'); var Color = require('../color'); var Drawing = require('../drawing'); var svgTextUtils = require('../../lib/svg_text_utils'); @@ -252,7 +251,7 @@ function drawSlider(gd, sliderGroup, sliderOpts) { .call(drawGrip, gd, sliderOpts); // Position the rectangle: - Lib.setTranslate(sliderGroup, sliderOpts.lx + sliderOpts.pad.l, sliderOpts.ly + sliderOpts.pad.t); + Drawing.setTranslate(sliderGroup, sliderOpts.lx + sliderOpts.pad.l, sliderOpts.ly + sliderOpts.pad.t); sliderGroup.call(setGripPosition, sliderOpts, sliderOpts.active / (sliderOpts.steps.length - 1), false); sliderGroup.call(drawCurrentValue, sliderOpts); @@ -305,7 +304,7 @@ function drawCurrentValue(sliderGroup, sliderOpts, valueOverride) { .text(str) .call(svgTextUtils.convertToTspans); - Lib.setTranslate(text, x0, sliderOpts.currentValueHeight); + Drawing.setTranslate(text, x0, sliderOpts.currentValueHeight); return text; } @@ -366,7 +365,7 @@ function drawLabelGroup(sliderGroup, sliderOpts) { item.call(drawLabel, d, sliderOpts); - Lib.setTranslate(item, + Drawing.setTranslate(item, normalizedValueToPosition(sliderOpts, d.fraction), constants.tickOffset + sliderOpts.ticklen + sliderOpts.labelHeight + constants.labelOffset + sliderOpts.currentValueTotalHeight ); @@ -489,7 +488,7 @@ function drawTicks(sliderGroup, sliderOpts) { .attr({height: isMajor ? sliderOpts.ticklen : sliderOpts.minorticklen}) .call(Color.fill, isMajor ? sliderOpts.tickcolor : sliderOpts.tickcolor); - Lib.setTranslate(item, + Drawing.setTranslate(item, normalizedValueToPosition(sliderOpts, i / (sliderOpts.steps.length - 1)) - 0.5 * sliderOpts.tickwidth, (isMajor ? constants.tickOffset : constants.minorTickOffset) + sliderOpts.currentValueTotalHeight ); @@ -526,7 +525,7 @@ function setGripPosition(sliderGroup, sliderOpts, position, doTransition) { .ease(sliderOpts.transition.easing); } - // Lib.setTranslate doesn't work here becasue of the transition duck-typing. + // Drawing.setTranslate doesn't work here becasue of the transition duck-typing. // It's also not necessary because there are no other transitions to preserve. el.attr('transform', 'translate(' + (x - constants.gripWidth * 0.5) + ',' + (sliderOpts.currentValueTotalHeight) + ')'); } @@ -558,7 +557,7 @@ function drawTouchRect(sliderGroup, gd, sliderOpts) { .call(Color.fill, sliderOpts.bgcolor) .attr('opacity', 0); - Lib.setTranslate(rect, 0, sliderOpts.currentValueTotalHeight); + Drawing.setTranslate(rect, 0, sliderOpts.currentValueTotalHeight); } function drawRail(sliderGroup, sliderOpts) { @@ -581,7 +580,7 @@ function drawRail(sliderGroup, sliderOpts) { .call(Color.fill, sliderOpts.bgcolor) .style('stroke-width', sliderOpts.borderwidth + 'px'); - Lib.setTranslate(rect, + Drawing.setTranslate(rect, constants.railInset, (sliderOpts.inputAreaWidth - constants.railWidth) * 0.5 + sliderOpts.currentValueTotalHeight ); diff --git a/src/components/updatemenus/draw.js b/src/components/updatemenus/draw.js index f7da4871ff2..e5996da3dad 100644 --- a/src/components/updatemenus/draw.js +++ b/src/components/updatemenus/draw.js @@ -12,7 +12,6 @@ var d3 = require('d3'); var Plots = require('../../plots/plots'); -var Lib = require('../../lib'); var Color = require('../color'); var Drawing = require('../drawing'); var svgTextUtils = require('../../lib/svg_text_utils'); @@ -220,7 +219,7 @@ function drawHeader(gd, gHeader, gButton, menuOpts) { }); // translate header group - Lib.setTranslate(gHeader, menuOpts.lx, menuOpts.ly); + Drawing.setTranslate(gHeader, menuOpts.lx, menuOpts.ly); } function drawButtons(gd, gHeader, gButton, menuOpts) { @@ -316,7 +315,7 @@ function drawButtons(gd, gHeader, gButton, menuOpts) { buttons.call(styleButtons, menuOpts); // translate button group - Lib.setTranslate(gButton, menuOpts.lx, menuOpts.ly); + Drawing.setTranslate(gButton, menuOpts.lx, menuOpts.ly); } function setActive(gd, menuOpts, buttonOpts, gHeader, gButton, buttonIndex, isSilentUpdate) { @@ -531,7 +530,7 @@ function setItemPosition(item, menuOpts, posOpts, overrideOpts) { borderWidth = menuOpts.borderwidth, index = posOpts.index; - Lib.setTranslate(item, borderWidth + posOpts.x, borderWidth + posOpts.y); + Drawing.setTranslate(item, borderWidth + posOpts.x, borderWidth + posOpts.y); var isVertical = ['up', 'down'].indexOf(menuOpts.direction) !== -1; diff --git a/src/lib/index.js b/src/lib/index.js index f078d15f93e..9544f4b3794 100644 --- a/src/lib/index.js +++ b/src/lib/index.js @@ -451,111 +451,6 @@ lib.addStyleRule = function(selector, styleString) { else lib.warn('addStyleRule failed'); }; -// TODO (alexcjohnson): the following translate/scale functions should -// get moved to components/drawing rather than the generic lib. - -lib.getTranslate = function(element) { - // Note the separator [^\d] between x and y in this regex - // We generally use ',' but IE will convert it to ' ' - var re = /.*\btranslate\((\d*\.?\d*)[^\d]*(\d*\.?\d*)[^\d].*/, - getter = element.attr ? 'attr' : 'getAttribute', - transform = element[getter]('transform') || ''; - - var translate = transform.replace(re, function(match, p1, p2) { - return [p1, p2].join(' '); - }) - .split(' '); - - return { - x: +translate[0] || 0, - y: +translate[1] || 0 - }; -}; - -lib.setTranslate = function(element, x, y) { - - var re = /(\btranslate\(.*?\);?)/, - getter = element.attr ? 'attr' : 'getAttribute', - setter = element.attr ? 'attr' : 'setAttribute', - transform = element[getter]('transform') || ''; - - x = x || 0; - y = y || 0; - - transform = transform.replace(re, '').trim(); - transform += ' translate(' + x + ', ' + y + ')'; - transform = transform.trim(); - - element[setter]('transform', transform); - - return transform; -}; - -lib.getScale = function(element) { - - var re = /.*\bscale\((\d*\.?\d*)[^\d]*(\d*\.?\d*)[^\d].*/, - getter = element.attr ? 'attr' : 'getAttribute', - transform = element[getter]('transform') || ''; - - var translate = transform.replace(re, function(match, p1, p2) { - return [p1, p2].join(' '); - }) - .split(' '); - - return { - x: +translate[0] || 1, - y: +translate[1] || 1 - }; -}; - -lib.setScale = function(element, x, y) { - - var re = /(\bscale\(.*?\);?)/, - getter = element.attr ? 'attr' : 'getAttribute', - setter = element.attr ? 'attr' : 'setAttribute', - transform = element[getter]('transform') || ''; - - x = x || 1; - y = y || 1; - - transform = transform.replace(re, '').trim(); - transform += ' scale(' + x + ', ' + y + ')'; - transform = transform.trim(); - - element[setter]('transform', transform); - - return transform; -}; - -lib.setPointGroupScale = function(selection, x, y) { - var t, scale, re; - - x = x || 1; - y = y || 1; - - if(x === 1 && y === 1) { - scale = ''; - } else { - // The same scale transform for every point: - scale = ' scale(' + x + ',' + y + ')'; - } - - // A regex to strip any existing scale: - re = /\s*sc.*/; - - selection.each(function() { - // Get the transform: - t = (this.getAttribute('transform') || '').replace(re, ''); - t += scale; - t = t.trim(); - - // Append the scale transform - this.setAttribute('transform', t); - }); - - return scale; -}; - lib.isIE = function() { return typeof window.navigator.msSaveBlob !== 'undefined'; }; diff --git a/src/plot_api/subroutines.js b/src/plot_api/subroutines.js index 64b82fa349e..67d4e12d270 100644 --- a/src/plot_api/subroutines.js +++ b/src/plot_api/subroutines.js @@ -81,7 +81,7 @@ exports.lsInner = function(gd) { }); - plotinfo.plot.call(Lib.setTranslate, xa._offset, ya._offset); + plotinfo.plot.call(Drawing.setTranslate, xa._offset, ya._offset); plotinfo.plot.call(Drawing.setClipUrl, plotinfo.clipId); var xlw = Drawing.crispRound(gd, xa.linewidth, 1), diff --git a/src/plots/cartesian/axes.js b/src/plots/cartesian/axes.js index 321b7aa8edb..f32baadd470 100644 --- a/src/plots/cartesian/axes.js +++ b/src/plots/cartesian/axes.js @@ -1897,7 +1897,7 @@ axes.doTicks = function(gd, axid, skipTitle) { x, y; if(avoidSelection.size()) { - var translation = Lib.getTranslate(avoidSelection.node().parentNode); + var translation = Drawing.getTranslate(avoidSelection.node().parentNode); avoid.offsetLeft = translation.x; avoid.offsetTop = translation.y; } diff --git a/src/plots/cartesian/dragbox.js b/src/plots/cartesian/dragbox.js index b50dc98cf3a..c0ca83a10c9 100644 --- a/src/plots/cartesian/dragbox.js +++ b/src/plots/cartesian/dragbox.js @@ -702,18 +702,18 @@ module.exports = function dragBox(gd, plotinfo, x, y, w, h, ns, ew) { plotDy = ya2._offset - fracDy; fullLayout._defs.selectAll('#' + subplot.clipId) - .call(Lib.setTranslate, clipDx, clipDy) - .call(Lib.setScale, 1 / xScaleFactor, 1 / yScaleFactor); + .call(Drawing.setTranslate, clipDx, clipDy) + .call(Drawing.setScale, 1 / xScaleFactor, 1 / yScaleFactor); subplot.plot - .call(Lib.setTranslate, plotDx, plotDy) - .call(Lib.setScale, xScaleFactor, yScaleFactor) + .call(Drawing.setTranslate, plotDx, plotDy) + .call(Drawing.setScale, xScaleFactor, yScaleFactor) // This is specifically directed at scatter traces, applying an inverse // scale to individual points to counteract the scale of the trace // as a whole: .selectAll('.points').selectAll('.point') - .call(Lib.setPointGroupScale, 1 / xScaleFactor, 1 / yScaleFactor); + .call(Drawing.setPointGroupScale, 1 / xScaleFactor, 1 / yScaleFactor); } } diff --git a/src/plots/cartesian/transition_axes.js b/src/plots/cartesian/transition_axes.js index 8b3e211d1e5..b41c50b8cc3 100644 --- a/src/plots/cartesian/transition_axes.js +++ b/src/plots/cartesian/transition_axes.js @@ -13,7 +13,7 @@ var d3 = require('d3'); var Plotly = require('../../plotly'); var Registry = require('../../registry'); -var Lib = require('../../lib'); +var Drawing = require('../../components/drawing'); var Axes = require('./axes'); var axisRegex = /((x|y)([2-9]|[1-9][0-9]+)?)axis$/; @@ -138,18 +138,18 @@ module.exports = function transitionAxes(gd, newLayout, transitionOpts, makeOnCo var ya2 = subplot.yaxis; fullLayout._defs.selectAll('#' + subplot.clipId) - .call(Lib.setTranslate, 0, 0) - .call(Lib.setScale, 1, 1); + .call(Drawing.setTranslate, 0, 0) + .call(Drawing.setScale, 1, 1); subplot.plot - .call(Lib.setTranslate, xa2._offset, ya2._offset) - .call(Lib.setScale, 1, 1) + .call(Drawing.setTranslate, xa2._offset, ya2._offset) + .call(Drawing.setScale, 1, 1) // This is specifically directed at scatter traces, applying an inverse // scale to individual points to counteract the scale of the trace // as a whole: .selectAll('.points').selectAll('.point') - .call(Lib.setPointGroupScale, 1, 1); + .call(Drawing.setPointGroupScale, 1, 1); } @@ -216,18 +216,18 @@ module.exports = function transitionAxes(gd, newLayout, transitionOpts, makeOnCo plotDy = ya2._offset - fracDy; fullLayout._defs.selectAll('#' + subplot.clipId) - .call(Lib.setTranslate, clipDx, clipDy) - .call(Lib.setScale, 1 / xScaleFactor, 1 / yScaleFactor); + .call(Drawing.setTranslate, clipDx, clipDy) + .call(Drawing.setScale, 1 / xScaleFactor, 1 / yScaleFactor); subplot.plot - .call(Lib.setTranslate, plotDx, plotDy) - .call(Lib.setScale, xScaleFactor, yScaleFactor) + .call(Drawing.setTranslate, plotDx, plotDy) + .call(Drawing.setScale, xScaleFactor, yScaleFactor) // This is specifically directed at scatter traces, applying an inverse // scale to individual points to counteract the scale of the trace // as a whole: .selectAll('.points').selectAll('.point') - .call(Lib.setPointGroupScale, 1 / xScaleFactor, 1 / yScaleFactor); + .call(Drawing.setPointGroupScale, 1 / xScaleFactor, 1 / yScaleFactor); } diff --git a/test/jasmine/tests/click_test.js b/test/jasmine/tests/click_test.js index d124fa17fca..fc2425e1b0e 100644 --- a/test/jasmine/tests/click_test.js +++ b/test/jasmine/tests/click_test.js @@ -1,5 +1,6 @@ var Plotly = require('@lib/index'); var Lib = require('@src/lib'); +var Drawing = require('@src/components/drawing'); var DBLCLICKDELAY = require('@src/plots/cartesian/constants').DBLCLICKDELAY; var createGraphDiv = require('../assets/create_graph_div'); @@ -760,8 +761,8 @@ describe('Test click interactions:', function() { } }; - var translate = Lib.getTranslate(mockEl), - scale = Lib.getScale(mockEl); + var translate = Drawing.getTranslate(mockEl), + scale = Drawing.getScale(mockEl); expect([translate.x, translate.y]).toBeCloseToArray([61.070, 97.712]); expect([scale.x, scale.y]).toBeCloseToArray([1.221, 1.221]); diff --git a/test/jasmine/tests/drawing_test.js b/test/jasmine/tests/drawing_test.js index 7930007a72b..61671060119 100644 --- a/test/jasmine/tests/drawing_test.js +++ b/test/jasmine/tests/drawing_test.js @@ -2,68 +2,274 @@ var Drawing = require('@src/components/drawing'); var d3 = require('d3'); - -describe('Drawing.setClipUrl', function() { +describe('Drawing', function() { 'use strict'; - beforeEach(function() { - this.svg = d3.select('body').append('svg'); - this.g = this.svg.append('g'); + describe('setClipUrl', function() { + + beforeEach(function() { + this.svg = d3.select('body').append('svg'); + this.g = this.svg.append('g'); + }); + + afterEach(function() { + this.svg.remove(); + this.g.remove(); + }); + + it('should set the clip-path attribute', function() { + expect(this.g.attr('clip-path')).toBe(null); + + Drawing.setClipUrl(this.g, 'id1'); + + expect(this.g.attr('clip-path')).toEqual('url(#id1)'); + }); + + it('should unset the clip-path if arg is falsy', function() { + this.g.attr('clip-path', 'url(#id2)'); + + Drawing.setClipUrl(this.g, false); + + expect(this.g.attr('clip-path')).toBe(null); + }); + + it('should append window URL to clip-path if is present', function() { + + // append with href + var base = d3.select('body') + .append('base') + .attr('href', 'https://plot.ly'); + + // grab window URL + var href = window.location.href; + + Drawing.setClipUrl(this.g, 'id3'); + + expect(this.g.attr('clip-path')) + .toEqual('url(' + href + '#id3)'); + + base.remove(); + }); + + it('should append window URL w/o hash to clip-path if is present', function() { + var base = d3.select('body') + .append('base') + .attr('href', 'https://plot.ly/#hash'); + + window.location.hash = 'hash'; + + Drawing.setClipUrl(this.g, 'id4'); + + var expected = 'url(' + window.location.href.split('#')[0] + '#id4)'; + + expect(this.g.attr('clip-path')).toEqual(expected); + + base.remove(); + window.location.hash = ''; + }); }); - afterEach(function() { - this.svg.remove(); - this.g.remove(); + describe('getTranslate', function() { + + it('should work with regular DOM elements', function() { + var el = document.createElement('div'); + + expect(Drawing.getTranslate(el)).toEqual({ x: 0, y: 0 }); + + el.setAttribute('transform', 'translate(123.45px, 67)'); + expect(Drawing.getTranslate(el)).toEqual({ x: 123.45, y: 67 }); + + el.setAttribute('transform', 'translate(123.45)'); + expect(Drawing.getTranslate(el)).toEqual({ x: 123.45, y: 0 }); + + el.setAttribute('transform', 'translate(1 2)'); + expect(Drawing.getTranslate(el)).toEqual({ x: 1, y: 2 }); + + el.setAttribute('transform', 'translate(1 2); rotate(20deg)'); + expect(Drawing.getTranslate(el)).toEqual({ x: 1, y: 2 }); + + el.setAttribute('transform', 'rotate(20deg) translate(1 2);'); + expect(Drawing.getTranslate(el)).toEqual({ x: 1, y: 2 }); + + el.setAttribute('transform', 'rotate(20deg)'); + expect(Drawing.getTranslate(el)).toEqual({ x: 0, y: 0 }); + }); + + it('should work with d3 elements', function() { + var el = d3.select(document.createElement('div')); + + el.attr('transform', 'translate(123.45px, 67)'); + expect(Drawing.getTranslate(el)).toEqual({ x: 123.45, y: 67 }); + + el.attr('transform', 'translate(123.45)'); + expect(Drawing.getTranslate(el)).toEqual({ x: 123.45, y: 0 }); + + el.attr('transform', 'translate(1 2)'); + expect(Drawing.getTranslate(el)).toEqual({ x: 1, y: 2 }); + + el.attr('transform', 'translate(1 2); rotate(20)'); + expect(Drawing.getTranslate(el)).toEqual({ x: 1, y: 2 }); + + el.attr('transform', 'rotate(20)'); + expect(Drawing.getTranslate(el)).toEqual({ x: 0, y: 0 }); + }); }); - it('should set the clip-path attribute', function() { - expect(this.g.attr('clip-path')).toBe(null); + describe('setTranslate', function() { + + it('should work with regular DOM elements', function() { + var el = document.createElement('div'); + + Drawing.setTranslate(el, 5); + expect(el.getAttribute('transform')).toBe('translate(5, 0)'); + + Drawing.setTranslate(el, 10, 20); + expect(el.getAttribute('transform')).toBe('translate(10, 20)'); + + Drawing.setTranslate(el); + expect(el.getAttribute('transform')).toBe('translate(0, 0)'); - Drawing.setClipUrl(this.g, 'id1'); + el.setAttribute('transform', 'translate(0, 0); rotate(30)'); + Drawing.setTranslate(el, 30, 40); + expect(el.getAttribute('transform')).toBe('rotate(30) translate(30, 40)'); + }); - expect(this.g.attr('clip-path')).toEqual('url(#id1)'); + it('should work with d3 elements', function() { + var el = d3.select(document.createElement('div')); + + Drawing.setTranslate(el, 5); + expect(el.attr('transform')).toBe('translate(5, 0)'); + + Drawing.setTranslate(el, 30, 40); + expect(el.attr('transform')).toBe('translate(30, 40)'); + + Drawing.setTranslate(el); + expect(el.attr('transform')).toBe('translate(0, 0)'); + + el.attr('transform', 'translate(0, 0); rotate(30)'); + Drawing.setTranslate(el, 30, 40); + expect(el.attr('transform')).toBe('rotate(30) translate(30, 40)'); + }); }); - it('should unset the clip-path if arg is falsy', function() { - this.g.attr('clip-path', 'url(#id2)'); + describe('getScale', function() { + + it('should work with regular DOM elements', function() { + var el = document.createElement('div'); + + expect(Drawing.getScale(el)).toEqual({ x: 1, y: 1 }); + + el.setAttribute('transform', 'scale(1.23, 45)'); + expect(Drawing.getScale(el)).toEqual({ x: 1.23, y: 45 }); - Drawing.setClipUrl(this.g, false); + el.setAttribute('transform', 'scale(123.45)'); + expect(Drawing.getScale(el)).toEqual({ x: 123.45, y: 1 }); - expect(this.g.attr('clip-path')).toBe(null); + el.setAttribute('transform', 'scale(0.1 2)'); + expect(Drawing.getScale(el)).toEqual({ x: 0.1, y: 2 }); + + el.setAttribute('transform', 'scale(0.1 2); rotate(20deg)'); + expect(Drawing.getScale(el)).toEqual({ x: 0.1, y: 2 }); + + el.setAttribute('transform', 'rotate(20deg) scale(0.1 2);'); + expect(Drawing.getScale(el)).toEqual({ x: 0.1, y: 2 }); + + el.setAttribute('transform', 'rotate(20deg)'); + expect(Drawing.getScale(el)).toEqual({ x: 1, y: 1 }); + }); + + it('should work with d3 elements', function() { + var el = d3.select(document.createElement('div')); + + el.attr('transform', 'scale(1.23, 45)'); + expect(Drawing.getScale(el)).toEqual({ x: 1.23, y: 45 }); + + el.attr('transform', 'scale(123.45)'); + expect(Drawing.getScale(el)).toEqual({ x: 123.45, y: 1 }); + + el.attr('transform', 'scale(0.1 2)'); + expect(Drawing.getScale(el)).toEqual({ x: 0.1, y: 2 }); + + el.attr('transform', 'scale(0.1 2); rotate(20)'); + expect(Drawing.getScale(el)).toEqual({ x: 0.1, y: 2 }); + + el.attr('transform', 'rotate(20)'); + expect(Drawing.getScale(el)).toEqual({ x: 1, y: 1 }); + }); }); - it('should append window URL to clip-path if is present', function() { + describe('setScale', function() { + + it('should work with regular DOM elements', function() { + var el = document.createElement('div'); + + Drawing.setScale(el, 5); + expect(el.getAttribute('transform')).toBe('scale(5, 1)'); + + Drawing.setScale(el, 30, 40); + expect(el.getAttribute('transform')).toBe('scale(30, 40)'); - // append with href - var base = d3.select('body') - .append('base') - .attr('href', 'https://plot.ly'); + Drawing.setScale(el); + expect(el.getAttribute('transform')).toBe('scale(1, 1)'); - // grab window URL - var href = window.location.href; + el.setAttribute('transform', 'scale(1, 1); rotate(30)'); + Drawing.setScale(el, 30, 40); + expect(el.getAttribute('transform')).toBe('rotate(30) scale(30, 40)'); + }); - Drawing.setClipUrl(this.g, 'id3'); + it('should work with d3 elements', function() { + var el = d3.select(document.createElement('div')); - expect(this.g.attr('clip-path')) - .toEqual('url(' + href + '#id3)'); + Drawing.setScale(el, 5); + expect(el.attr('transform')).toBe('scale(5, 1)'); - base.remove(); + Drawing.setScale(el, 30, 40); + expect(el.attr('transform')).toBe('scale(30, 40)'); + + Drawing.setScale(el); + expect(el.attr('transform')).toBe('scale(1, 1)'); + + el.attr('transform', 'scale(0, 0); rotate(30)'); + Drawing.setScale(el, 30, 40); + expect(el.attr('transform')).toBe('rotate(30) scale(30, 40)'); + }); }); - it('should append window URL w/o hash to clip-path if is present', function() { - var base = d3.select('body') - .append('base') - .attr('href', 'https://plot.ly/#hash'); + describe('setPointGroupScale', function() { + var el, sel; + + beforeEach(function() { + el = document.createElement('div'); + sel = d3.select(el); + }); - window.location.hash = 'hash'; + it('sets the scale of a point', function() { + Drawing.setPointGroupScale(sel, 2, 2); + expect(el.getAttribute('transform')).toBe('scale(2,2)'); + }); - Drawing.setClipUrl(this.g, 'id4'); + it('appends the scale of a point', function() { + el.setAttribute('transform', 'translate(1,2)'); + Drawing.setPointGroupScale(sel, 2, 2); + expect(el.getAttribute('transform')).toBe('translate(1,2) scale(2,2)'); + }); - var expected = 'url(' + window.location.href.split('#')[0] + '#id4)'; + it('modifies the scale of a point', function() { + el.setAttribute('transform', 'translate(1,2) scale(3,4)'); + Drawing.setPointGroupScale(sel, 2, 2); + expect(el.getAttribute('transform')).toBe('translate(1,2) scale(2,2)'); + }); - expect(this.g.attr('clip-path')).toEqual(expected); + it('does not apply the scale of a point if scale (1, 1)', function() { + el.setAttribute('transform', 'translate(1,2)'); + Drawing.setPointGroupScale(sel, 1, 1); + expect(el.getAttribute('transform')).toBe('translate(1,2)'); + }); - base.remove(); - window.location.hash = ''; + it('removes the scale of a point if scale (1, 1)', function() { + el.setAttribute('transform', 'translate(1,2) scale(3,4)'); + Drawing.setPointGroupScale(sel, 1, 1); + expect(el.getAttribute('transform')).toBe('translate(1,2)'); + }); }); }); diff --git a/test/jasmine/tests/lib_test.js b/test/jasmine/tests/lib_test.js index a68ad041540..7181510d4cc 100644 --- a/test/jasmine/tests/lib_test.js +++ b/test/jasmine/tests/lib_test.js @@ -1243,210 +1243,6 @@ describe('Test lib.js:', function() { }); }); - describe('getTranslate', function() { - - it('should work with regular DOM elements', function() { - var el = document.createElement('div'); - - expect(Lib.getTranslate(el)).toEqual({ x: 0, y: 0 }); - - el.setAttribute('transform', 'translate(123.45px, 67)'); - expect(Lib.getTranslate(el)).toEqual({ x: 123.45, y: 67 }); - - el.setAttribute('transform', 'translate(123.45)'); - expect(Lib.getTranslate(el)).toEqual({ x: 123.45, y: 0 }); - - el.setAttribute('transform', 'translate(1 2)'); - expect(Lib.getTranslate(el)).toEqual({ x: 1, y: 2 }); - - el.setAttribute('transform', 'translate(1 2); rotate(20deg)'); - expect(Lib.getTranslate(el)).toEqual({ x: 1, y: 2 }); - - el.setAttribute('transform', 'rotate(20deg) translate(1 2);'); - expect(Lib.getTranslate(el)).toEqual({ x: 1, y: 2 }); - - el.setAttribute('transform', 'rotate(20deg)'); - expect(Lib.getTranslate(el)).toEqual({ x: 0, y: 0 }); - }); - - it('should work with d3 elements', function() { - var el = d3.select(document.createElement('div')); - - el.attr('transform', 'translate(123.45px, 67)'); - expect(Lib.getTranslate(el)).toEqual({ x: 123.45, y: 67 }); - - el.attr('transform', 'translate(123.45)'); - expect(Lib.getTranslate(el)).toEqual({ x: 123.45, y: 0 }); - - el.attr('transform', 'translate(1 2)'); - expect(Lib.getTranslate(el)).toEqual({ x: 1, y: 2 }); - - el.attr('transform', 'translate(1 2); rotate(20)'); - expect(Lib.getTranslate(el)).toEqual({ x: 1, y: 2 }); - - el.attr('transform', 'rotate(20)'); - expect(Lib.getTranslate(el)).toEqual({ x: 0, y: 0 }); - }); - }); - - describe('setTranslate', function() { - - it('should work with regular DOM elements', function() { - var el = document.createElement('div'); - - Lib.setTranslate(el, 5); - expect(el.getAttribute('transform')).toBe('translate(5, 0)'); - - Lib.setTranslate(el, 10, 20); - expect(el.getAttribute('transform')).toBe('translate(10, 20)'); - - Lib.setTranslate(el); - expect(el.getAttribute('transform')).toBe('translate(0, 0)'); - - el.setAttribute('transform', 'translate(0, 0); rotate(30)'); - Lib.setTranslate(el, 30, 40); - expect(el.getAttribute('transform')).toBe('rotate(30) translate(30, 40)'); - }); - - it('should work with d3 elements', function() { - var el = d3.select(document.createElement('div')); - - Lib.setTranslate(el, 5); - expect(el.attr('transform')).toBe('translate(5, 0)'); - - Lib.setTranslate(el, 30, 40); - expect(el.attr('transform')).toBe('translate(30, 40)'); - - Lib.setTranslate(el); - expect(el.attr('transform')).toBe('translate(0, 0)'); - - el.attr('transform', 'translate(0, 0); rotate(30)'); - Lib.setTranslate(el, 30, 40); - expect(el.attr('transform')).toBe('rotate(30) translate(30, 40)'); - }); - }); - - describe('getScale', function() { - - it('should work with regular DOM elements', function() { - var el = document.createElement('div'); - - expect(Lib.getScale(el)).toEqual({ x: 1, y: 1 }); - - el.setAttribute('transform', 'scale(1.23, 45)'); - expect(Lib.getScale(el)).toEqual({ x: 1.23, y: 45 }); - - el.setAttribute('transform', 'scale(123.45)'); - expect(Lib.getScale(el)).toEqual({ x: 123.45, y: 1 }); - - el.setAttribute('transform', 'scale(0.1 2)'); - expect(Lib.getScale(el)).toEqual({ x: 0.1, y: 2 }); - - el.setAttribute('transform', 'scale(0.1 2); rotate(20deg)'); - expect(Lib.getScale(el)).toEqual({ x: 0.1, y: 2 }); - - el.setAttribute('transform', 'rotate(20deg) scale(0.1 2);'); - expect(Lib.getScale(el)).toEqual({ x: 0.1, y: 2 }); - - el.setAttribute('transform', 'rotate(20deg)'); - expect(Lib.getScale(el)).toEqual({ x: 1, y: 1 }); - }); - - it('should work with d3 elements', function() { - var el = d3.select(document.createElement('div')); - - el.attr('transform', 'scale(1.23, 45)'); - expect(Lib.getScale(el)).toEqual({ x: 1.23, y: 45 }); - - el.attr('transform', 'scale(123.45)'); - expect(Lib.getScale(el)).toEqual({ x: 123.45, y: 1 }); - - el.attr('transform', 'scale(0.1 2)'); - expect(Lib.getScale(el)).toEqual({ x: 0.1, y: 2 }); - - el.attr('transform', 'scale(0.1 2); rotate(20)'); - expect(Lib.getScale(el)).toEqual({ x: 0.1, y: 2 }); - - el.attr('transform', 'rotate(20)'); - expect(Lib.getScale(el)).toEqual({ x: 1, y: 1 }); - }); - }); - - describe('setScale', function() { - - it('should work with regular DOM elements', function() { - var el = document.createElement('div'); - - Lib.setScale(el, 5); - expect(el.getAttribute('transform')).toBe('scale(5, 1)'); - - Lib.setScale(el, 30, 40); - expect(el.getAttribute('transform')).toBe('scale(30, 40)'); - - Lib.setScale(el); - expect(el.getAttribute('transform')).toBe('scale(1, 1)'); - - el.setAttribute('transform', 'scale(1, 1); rotate(30)'); - Lib.setScale(el, 30, 40); - expect(el.getAttribute('transform')).toBe('rotate(30) scale(30, 40)'); - }); - - it('should work with d3 elements', function() { - var el = d3.select(document.createElement('div')); - - Lib.setScale(el, 5); - expect(el.attr('transform')).toBe('scale(5, 1)'); - - Lib.setScale(el, 30, 40); - expect(el.attr('transform')).toBe('scale(30, 40)'); - - Lib.setScale(el); - expect(el.attr('transform')).toBe('scale(1, 1)'); - - el.attr('transform', 'scale(0, 0); rotate(30)'); - Lib.setScale(el, 30, 40); - expect(el.attr('transform')).toBe('rotate(30) scale(30, 40)'); - }); - }); - - describe('setPointGroupScale', function() { - var el, sel; - - beforeEach(function() { - el = document.createElement('div'); - sel = d3.select(el); - }); - - it('sets the scale of a point', function() { - Lib.setPointGroupScale(sel, 2, 2); - expect(el.getAttribute('transform')).toBe('scale(2,2)'); - }); - - it('appends the scale of a point', function() { - el.setAttribute('transform', 'translate(1,2)'); - Lib.setPointGroupScale(sel, 2, 2); - expect(el.getAttribute('transform')).toBe('translate(1,2) scale(2,2)'); - }); - - it('modifies the scale of a point', function() { - el.setAttribute('transform', 'translate(1,2) scale(3,4)'); - Lib.setPointGroupScale(sel, 2, 2); - expect(el.getAttribute('transform')).toBe('translate(1,2) scale(2,2)'); - }); - - it('does not apply the scale of a point if scale (1, 1)', function() { - el.setAttribute('transform', 'translate(1,2)'); - Lib.setPointGroupScale(sel, 1, 1); - expect(el.getAttribute('transform')).toBe('translate(1,2)'); - }); - - it('removes the scale of a point if scale (1, 1)', function() { - el.setAttribute('transform', 'translate(1,2) scale(3,4)'); - Lib.setPointGroupScale(sel, 1, 1); - expect(el.getAttribute('transform')).toBe('translate(1,2)'); - }); - }); - describe('pushUnique', function() { beforeEach(function() { From fe127290b7fdf8863a0a58dcbdd35c81307bea69 Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Tue, 31 Jan 2017 18:03:14 -0500 Subject: [PATCH 9/9] one more Lib->Drawing --- test/jasmine/tests/cartesian_test.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/jasmine/tests/cartesian_test.js b/test/jasmine/tests/cartesian_test.js index 0f89dfa0acc..a4f87c68242 100644 --- a/test/jasmine/tests/cartesian_test.js +++ b/test/jasmine/tests/cartesian_test.js @@ -2,6 +2,7 @@ var d3 = require('d3'); var Plotly = require('@lib/index'); var Lib = require('@src/lib'); +var Drawing = require('@src/components/drawing'); var createGraphDiv = require('../assets/create_graph_div'); var destroyGraphDiv = require('../assets/destroy_graph_div'); @@ -283,7 +284,7 @@ describe('relayout', function() { expect(points.attr('y')).toBe(null); expect(texts.attr('transform')).toBe(null); - var translate = Lib.getTranslate(points); + var translate = Drawing.getTranslate(points); expect(Math.abs(translate.x - pointT[0])).toBeLessThan(TOLERANCE); expect(Math.abs(translate.y - pointT[1])).toBeLessThan(TOLERANCE);