diff --git a/build/plotcss.js b/build/plotcss.js index 169edfce295..556adf30e3b 100644 --- a/build/plotcss.js +++ b/build/plotcss.js @@ -1,6 +1,5 @@ 'use strict'; -var Plotly = require('../src/plotly'); var rules = { "X,X div": "font-family:'Open Sans', verdana, arial, sans-serif;margin:0;padding:0;", "X input,X button": "font-family:'Open Sans', verdana, arial, sans-serif;", @@ -54,9 +53,4 @@ var rules = { "Y .notifier-close:hover": "color:#444;text-decoration:none;cursor:pointer;" }; -for(var selector in rules) { - var fullSelector = selector.replace(/^,/,' ,') - .replace(/X/g, '.js-plotly-plot .plotly') - .replace(/Y/g, '.plotly-notifier'); - Plotly.Lib.addStyleRule(fullSelector, rules[selector]); -} +module.exports = rules; diff --git a/src/components/dragelement/index.js b/src/components/dragelement/index.js index a57a0038248..44a7687fa62 100644 --- a/src/components/dragelement/index.js +++ b/src/components/dragelement/index.js @@ -86,7 +86,7 @@ dragElement.init = function init(options) { if(options.prepFn) options.prepFn(e, startX, startY); - dragCover = coverSlip(); + dragCover = coverSlip(gd); dragCover.onmousemove = onMove; dragCover.onmouseup = onDone; @@ -139,7 +139,7 @@ dragElement.init = function init(options) { if(options.doneFn) options.doneFn(gd._dragged, numClicks); if(!gd._dragged) { - var e2 = document.createEvent('MouseEvents'); + var e2 = gd._document.createEvent('MouseEvents'); e2.initEvent('click', true, true); initialTarget.dispatchEvent(e2); } @@ -159,8 +159,8 @@ dragElement.init = function init(options) { options.element.style.pointerEvents = 'all'; }; -function coverSlip() { - var cover = document.createElement('div'); +function coverSlip(gd) { + var cover = gd._document.createElement('div'); cover.className = 'dragcover'; var cStyle = cover.style; @@ -172,7 +172,7 @@ function coverSlip() { cStyle.zIndex = 999999999; cStyle.background = 'none'; - document.body.appendChild(cover); + gd._document.body.appendChild(cover); return cover; } diff --git a/src/components/modebar/buttons.js b/src/components/modebar/buttons.js index 23b6ae8a72c..a43279afdef 100644 --- a/src/components/modebar/buttons.js +++ b/src/components/modebar/buttons.js @@ -50,19 +50,19 @@ modeBarButtons.toImage = { click: function(gd) { var format = 'png'; - Lib.notifier('Taking snapshot - this may take a few seconds', 'long'); + Lib.notifier(gd, 'Taking snapshot - this may take a few seconds', 'long'); if(Lib.isIE()) { - Lib.notifier('IE only supports svg. Changing format to svg.', 'long'); + Lib.notifier(gd, 'IE only supports svg. Changing format to svg.', 'long'); format = 'svg'; } downloadImage(gd, {'format': format}) .then(function(filename) { - Lib.notifier('Snapshot succeeded - ' + filename, 'long'); + Lib.notifier(gd, 'Snapshot succeeded - ' + filename, 'long'); }) .catch(function() { - Lib.notifier('Sorry there was a problem downloading your snapshot!', 'long'); + Lib.notifier(gd, 'Sorry there was a problem downloading your snapshot!', 'long'); }); } }; diff --git a/src/components/rangeslider/create_slider.js b/src/components/rangeslider/create_slider.js index 83caa2ad7eb..00a9b3d06b1 100644 --- a/src/components/rangeslider/create_slider.js +++ b/src/components/rangeslider/create_slider.js @@ -33,7 +33,7 @@ module.exports = function createSlider(gd) { var minStart = 0, maxStart = width; - var slider = document.createElementNS(svgNS, 'g'); + var slider = gd._document.createElementNS(svgNS, 'g'); helpers.setAttributes(slider, { 'class': 'range-slider', 'data-min': minStart, @@ -43,7 +43,7 @@ module.exports = function createSlider(gd) { }); - var sliderBg = document.createElementNS(svgNS, 'rect'), + var sliderBg = gd._document.createElementNS(svgNS, 'rect'), borderCorrect = options.borderwidth % 2 === 0 ? options.borderwidth : options.borderwidth - 1; helpers.setAttributes(sliderBg, { 'fill': options.bgcolor, @@ -56,7 +56,7 @@ module.exports = function createSlider(gd) { }); - var maskMin = document.createElementNS(svgNS, 'rect'); + var maskMin = gd._document.createElementNS(svgNS, 'rect'); helpers.setAttributes(maskMin, { 'x': 0, 'width': minStart, @@ -65,7 +65,7 @@ module.exports = function createSlider(gd) { }); - var maskMax = document.createElementNS(svgNS, 'rect'); + var maskMax = gd._document.createElementNS(svgNS, 'rect'); helpers.setAttributes(maskMax, { 'x': maxStart, 'width': width - maxStart, @@ -74,9 +74,9 @@ module.exports = function createSlider(gd) { }); - var grabberMin = document.createElementNS(svgNS, 'g'), - grabAreaMin = document.createElementNS(svgNS, 'rect'), - handleMin = document.createElementNS(svgNS, 'rect'); + var grabberMin = gd._document.createElementNS(svgNS, 'g'), + grabAreaMin = gd._document.createElementNS(svgNS, 'rect'), + handleMin = gd._document.createElementNS(svgNS, 'rect'); helpers.setAttributes(grabberMin, { 'transform': 'translate(' + (minStart - handleWidth - 1) + ')' }); helpers.setAttributes(grabAreaMin, { 'width': 10, @@ -97,9 +97,9 @@ module.exports = function createSlider(gd) { helpers.appendChildren(grabberMin, [handleMin, grabAreaMin]); - var grabberMax = document.createElementNS(svgNS, 'g'), - grabAreaMax = document.createElementNS(svgNS, 'rect'), - handleMax = document.createElementNS(svgNS, 'rect'); + var grabberMax = gd._document.createElementNS(svgNS, 'g'), + grabAreaMax = gd._document.createElementNS(svgNS, 'rect'), + handleMax = gd._document.createElementNS(svgNS, 'rect'); helpers.setAttributes(grabberMax, { 'transform': 'translate(' + maxStart + ')' }); helpers.setAttributes(grabAreaMax, { 'width': 10, @@ -120,7 +120,7 @@ module.exports = function createSlider(gd) { helpers.appendChildren(grabberMax, [handleMax, grabAreaMax]); - var slideBox = document.createElementNS(svgNS, 'rect'); + var slideBox = gd._document.createElementNS(svgNS, 'rect'); helpers.setAttributes(slideBox, { 'x': minStart, 'width': maxStart - minStart, @@ -137,8 +137,8 @@ module.exports = function createSlider(gd) { minVal = slider.getAttribute('data-min'), maxVal = slider.getAttribute('data-max'); - window.addEventListener('mousemove', mouseMove); - window.addEventListener('mouseup', mouseUp); + gd._document.defaultView.addEventListener('mousemove', mouseMove); + gd._document.defaultView.addEventListener('mouseup', mouseUp); function mouseMove(e) { var delta = +e.clientX - startX, @@ -189,8 +189,8 @@ module.exports = function createSlider(gd) { } function mouseUp() { - window.removeEventListener('mousemove', mouseMove); - window.removeEventListener('mouseup', mouseUp); + gd._document.defaultView.removeEventListener('mousemove', mouseMove); + gd._document.defaultView.removeEventListener('mouseup', mouseUp); slider.style.cursor = 'auto'; } }); @@ -222,8 +222,8 @@ module.exports = function createSlider(gd) { function setDataRange(dataMin, dataMax) { - if(window.requestAnimationFrame) { - window.requestAnimationFrame(function() { + if(gd._document.defaultView.requestAnimationFrame) { + gd._document.defaultView.requestAnimationFrame(function() { Plotly.relayout(gd, 'xaxis.range', [dataMin, dataMax]); }); } else { diff --git a/src/css/helpers.js b/src/css/helpers.js new file mode 100644 index 00000000000..ebf4c6470a0 --- /dev/null +++ b/src/css/helpers.js @@ -0,0 +1,38 @@ +/** +* Copyright 2012-2016, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +// expands a plotcss selector +exports.buildFullSelector = function buildFullSelector(selector) { + var fullSelector = selector.replace(/,/, ', ') + .replace(/:after/g, '::after') + .replace(/:before/g, '::before') + .replace(/X/g, '.js-plotly-plot .plotly') + .replace(/Y/g, '.plotly-notifier'); + + return fullSelector; +}; + +// Gets all the rules currently attached to the document +exports.getAllRuleSelectors = function getAllRuleSelectors(sourceDocument) { + var allSelectors = []; + + for(var i = 0; i < sourceDocument.styleSheets.length; i++) { + var styleSheet = sourceDocument.styleSheets[i]; + + for(var j = 0; j < styleSheet.cssRules.length; j++) { + var cssRule = styleSheet.cssRules[j]; + + allSelectors.push(cssRule.selectorText); + } + } + + return allSelectors; +}; diff --git a/src/css/plotcss_injector.js b/src/css/plotcss_injector.js new file mode 100644 index 00000000000..4f4e54051ec --- /dev/null +++ b/src/css/plotcss_injector.js @@ -0,0 +1,52 @@ +/** +* Copyright 2012-2016, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var helpers = require('./helpers'); +var lib = require('../lib'); +var plotcss = require('../../build/plotcss'); + +// Inject styling information into the document containing the graph div +module.exports = function injectStyles(gd) { + // If the graph div has already been styled, bail + if(gd._plotCSSLoaded) return; + + var targetSelectors = helpers.getAllRuleSelectors(gd._document); + var targetStyleSheet = null; + + if(gd._document.getElementsByTagName('style').length === 0) { + var style = gd._document.createElement('style'); + // WebKit hack :( + style.appendChild(gd._document.createTextNode('')); + gd._document.head.appendChild(style); + targetStyleSheet = style.sheet; + } + else { + // Just grab the first style element to append to + targetStyleSheet = gd._document.getElementsByTagName('style')[0].sheet; + } + + for(var selector in plotcss) { + var fullSelector = helpers.buildFullSelector(selector); + + // Don't duplicate selectors + if(targetSelectors.indexOf(fullSelector) === -1) { + if(targetStyleSheet.insertRule) { + targetStyleSheet.insertRule(fullSelector + '{' + plotcss[selector] + '}', 0); + } + else if(targetStyleSheet.addRule) { + targetStyleSheet.addRule(fullSelector, plotcss[selector], 0); + } + else lib.warn('injectStyles failed'); + } + } + + gd._plotCSSLoaded = true; +}; diff --git a/src/lib/index.js b/src/lib/index.js index 0a3d8bf7854..7a46be4e59c 100644 --- a/src/lib/index.js +++ b/src/lib/index.js @@ -388,30 +388,6 @@ lib.removeElement = function(el) { if(elParent) elParent.removeChild(el); }; -/** - * for dynamically adding style rules - * makes one stylesheet that contains all rules added - * by all calls to this function - */ -lib.addStyleRule = function(selector, styleString) { - if(!lib.styleSheet) { - var style = document.createElement('style'); - // WebKit hack :( - style.appendChild(document.createTextNode('')); - document.head.appendChild(style); - lib.styleSheet = style.sheet; - } - var styleSheet = lib.styleSheet; - - if(styleSheet.insertRule) { - styleSheet.insertRule(selector + '{' + styleString + '}', 0); - } - else if(styleSheet.addRule) { - styleSheet.addRule(selector, styleString, 0); - } - else lib.warn('addStyleRule failed'); -}; - lib.getTranslate = function(element) { var re = /.*\btranslate\((\d*\.?\d*)[^\d]*(\d*\.?\d*)[^\d].*/, diff --git a/src/lib/notifier.js b/src/lib/notifier.js index a1bfbfcc14f..ae6a741783f 100644 --- a/src/lib/notifier.js +++ b/src/lib/notifier.js @@ -16,12 +16,13 @@ var NOTEDATA = []; /** * notifier + * @param {object} gd figure Object * @param {String} text The person's user name * @param {Number} [delay=1000] The delay time in milliseconds * or 'long' which provides 2000 ms delay time. * @return {undefined} this function does not return a value */ -module.exports = function(text, displayLength) { +module.exports = function(gd, text, displayLength) { if(NOTEDATA.indexOf(text) !== -1) return; NOTEDATA.push(text); @@ -30,7 +31,7 @@ module.exports = function(text, displayLength) { if(isNumeric(displayLength)) ts = displayLength; else if(displayLength === 'long') ts = 3000; - var notifierContainer = d3.select('body') + var notifierContainer = d3.select(gd._document.body) .selectAll('.plotly-notifier') .data([0]); notifierContainer.enter() diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index 3cf9366e167..24f981a5abf 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -18,6 +18,8 @@ var Lib = require('../lib'); var Events = require('../lib/events'); var Queue = require('../lib/queue'); +var injectStyles = require('../css/plotcss_injector'); + var Plots = require('../plots/plots'); var Fx = require('../plots/cartesian/graph_interact'); @@ -54,6 +56,14 @@ Plotly.plot = function(gd, data, layout, config) { gd = getGraphDiv(gd); + // Get the document the graph div lives in, so we can make sure things like + // drag covers are attached to the correct document + gd._document = gd.ownerDocument || window.document; + + // Inject the plot styles into the document where we're plotting, bails if + // already styled + injectStyles(gd); + // Events.init is idempotent and bails early if gd has already been init'd Events.init(gd); @@ -2541,12 +2551,12 @@ function plotAutoSize(gd, aobj) { // embedded in an iframe - just take the full iframe size // if we get to this point, with no aspect ratio restrictions if(gd._context.fillFrame) { - newWidth = window.innerWidth; - newHeight = window.innerHeight; + newWidth = gd._document.defaultView.innerWidth; + newHeight = gd._document.defaultView.innerHeight; // somehow we get a few extra px height sometimes... // just hide it - document.body.style.overflow = 'hidden'; + gd._document.body.style.overflow = 'hidden'; } else if(isNumeric(context.frameMargins) && context.frameMargins > 0) { var reservedMargins = calculateReservedMargins(gd._boundingBoxMargins), @@ -2563,7 +2573,7 @@ function plotAutoSize(gd, aobj) { // provide height and width for the container div, // specify size in layout, or take the defaults, // but don't enforce any ratio restrictions - computedStyle = window.getComputedStyle(gd); + computedStyle = gd._document.defaultView.getComputedStyle(gd); newHeight = parseFloat(computedStyle.height) || fullLayout.height; newWidth = parseFloat(computedStyle.width) || fullLayout.width; } diff --git a/src/plotly.js b/src/plotly.js index 5ceb2019839..491c0ed5a77 100644 --- a/src/plotly.js +++ b/src/plotly.js @@ -26,9 +26,6 @@ exports.Lib = require('./lib'); exports.util = require('./lib/svg_text_utils'); exports.Queue = require('./lib/queue'); -// plot css -require('../build/plotcss'); - // configuration exports.MathJaxConfig = require('./fonts/mathjax_config'); exports.defaultConfig = require('./plot_api/plot_config'); diff --git a/src/plots/cartesian/dragbox.js b/src/plots/cartesian/dragbox.js index ff6f7bc52bf..8e99adb1ab0 100644 --- a/src/plots/cartesian/dragbox.js +++ b/src/plots/cartesian/dragbox.js @@ -305,7 +305,7 @@ module.exports = function dragBox(gd, plotinfo, x, y, w, h, ns, ew) { dragTail(zoomMode); if(SHOWZOOMOUTTIP && gd.data && gd._context.showTips) { - Lib.notifier('Double-click to
zoom back out', 'long'); + Lib.notifier(gd, 'Double-click to
zoom back out', 'long'); SHOWZOOMOUTTIP = false; } } diff --git a/src/plots/cartesian/set_convert.js b/src/plots/cartesian/set_convert.js index 565c4ce53b3..5f34cde15e9 100644 --- a/src/plots/cartesian/set_convert.js +++ b/src/plots/cartesian/set_convert.js @@ -114,6 +114,7 @@ module.exports = function setConvert(ax) { if(!isFinite(ax._m) || !isFinite(ax._b)) { Lib.notifier( + ax._gd, 'Something went wrong with axis scaling', 'long'); ax._gd._replotting = false; diff --git a/src/plots/plots.js b/src/plots/plots.js index 4b814bfd85c..307c4d38547 100644 --- a/src/plots/plots.js +++ b/src/plots/plots.js @@ -809,6 +809,10 @@ plots.purge = function(gd) { // remove modebar if(fullLayout._modeBar) fullLayout._modeBar.destroy(); + // styling + delete gd._document; + delete gd._plotCSSLoaded; + // data and layout delete gd.data; delete gd.layout; diff --git a/src/plots/ternary/ternary.js b/src/plots/ternary/ternary.js index 1fd6d7094f7..9a93f376205 100644 --- a/src/plots/ternary/ternary.js +++ b/src/plots/ternary/ternary.js @@ -570,7 +570,7 @@ proto.initInteractions = function() { Plotly.relayout(gd, attrs); if(SHOWZOOMOUTTIP && gd.data && gd._context.showTips) { - Lib.notifier('Double-click to
zoom back out', 'long'); + Lib.notifier(gd, 'Double-click to
zoom back out', 'long'); SHOWZOOMOUTTIP = false; } } diff --git a/src/traces/heatmap/calc.js b/src/traces/heatmap/calc.js index e8611ccc82e..828c3288db9 100644 --- a/src/traces/heatmap/calc.js +++ b/src/traces/heatmap/calc.js @@ -73,7 +73,7 @@ module.exports = function calc(gd, trace) { function noZsmooth(msg) { zsmooth = trace._input.zsmooth = trace.zsmooth = false; - Lib.notifier('cannot fast-zsmooth: ' + msg); + Lib.notifier(gd, 'cannot fast-zsmooth: ' + msg); } // check whether we really can smooth (ie all boxes are about the same size) diff --git a/tasks/util/pull_css.js b/tasks/util/pull_css.js index 1f3cb6def53..ff5fefea671 100644 --- a/tasks/util/pull_css.js +++ b/tasks/util/pull_css.js @@ -38,15 +38,9 @@ module.exports = function pullCSS(data, pathOut) { var outStr = [ '\'use strict\';', '', - 'var Plotly = require(\'../src/plotly\');', 'var rules = ' + rulesStr + ';', '', - 'for(var selector in rules) {', - ' var fullSelector = selector.replace(/^,/,\' ,\')', - ' .replace(/X/g, \'.js-plotly-plot .plotly\')', - ' .replace(/Y/g, \'.plotly-notifier\');', - ' Plotly.Lib.addStyleRule(fullSelector, rules[selector]);', - '}', + 'module.exports = rules;', '' ].join('\n'); diff --git a/test/jasmine/tests/dragelement_test.js b/test/jasmine/tests/dragelement_test.js index 924f7f3bcaf..ad6abd29eb1 100644 --- a/test/jasmine/tests/dragelement_test.js +++ b/test/jasmine/tests/dragelement_test.js @@ -15,6 +15,7 @@ describe('dragElement', function() { this.element = document.createElement('div'); this.gd.className = 'js-plotly-plot'; + this.gd._document = document; this.gd._fullLayout = { _hoverlayer: d3.select(this.hoverlayer) }; diff --git a/test/jasmine/tests/plot_css_test.js b/test/jasmine/tests/plot_css_test.js new file mode 100644 index 00000000000..204d420f100 --- /dev/null +++ b/test/jasmine/tests/plot_css_test.js @@ -0,0 +1,152 @@ +var Plotly = require('@lib/index'); + +var createGraphDiv = require('../assets/create_graph_div'); +var destroyGraphDiv = require('../assets/destroy_graph_div'); + +describe('css injection', function() { + var helpers = require('@src/css/helpers'); + var plotcss = require('@build/plotcss'); + + // create a graph div in a child window + function createGraphDivInChildWindow() { + var childWindow = window.open('about:blank', 'popoutWindow', ''); + + var gd = childWindow.document.createElement('div'); + gd.id = 'graph'; + childWindow.document.body.appendChild(gd); + + // force the graph to be at position 0,0 no matter what + gd.style.position = 'fixed'; + gd.style.left = 0; + gd.style.top = 0; + + return gd; + } + + // the most basic of basic plots + function plot(target) { + Plotly.plot(target, [{ + x: [1, 2, 3, 4, 5], + y: [1, 2, 4, 8, 16] + }], { + margin: { + t: 0 + } + }); + } + + // deletes all rules defined in plotcss + function deletePlotCSSRules(sourceDocument) { + for(var selector in plotcss) { + var fullSelector = helpers.buildFullSelector(selector); + + for(var i = 0; i < sourceDocument.styleSheets.length; i++) { + var styleSheet = sourceDocument.styleSheets[i]; + var selectors = []; + + for(var j = 0; j < styleSheet.cssRules.length; j++) { + var cssRule = styleSheet.cssRules[j]; + + selectors.push(cssRule.selectorText); + } + + var selectorIndex = selectors.indexOf(fullSelector); + + if(selectorIndex !== -1) { + styleSheet.deleteRule(selectorIndex); + break; + } + } + } + } + + it('inserts styles on initial plot', function() { + deletePlotCSSRules(document); // clear the rules + + // fix scope errors + var selector = null; + var fullSelector = null; + + // make sure the rules are cleared + var allSelectors = helpers.getAllRuleSelectors(document); + + for(selector in plotcss) { + fullSelector = helpers.buildFullSelector(selector); + + expect(allSelectors.indexOf(fullSelector)).toEqual(-1); + } + + // plot + var gd = createGraphDiv(); + plot(gd); + + // check for styles + allSelectors = helpers.getAllRuleSelectors(document); + + for(selector in plotcss) { + fullSelector = helpers.buildFullSelector(selector); + + expect(allSelectors.indexOf(fullSelector)).not.toEqual(-1); + } + + // clean up + destroyGraphDiv(); + }); + + it('inserts styles in a child window document', function() { + var gd = createGraphDivInChildWindow(); + var childWindow = gd.ownerDocument.defaultView; + + // plot + plot(gd); + + // check for styles + var allSelectors = helpers.getAllRuleSelectors(gd.ownerDocument); + + for(var selector in plotcss) { + var fullSelector = helpers.buildFullSelector(selector); + + expect(allSelectors.indexOf(fullSelector)).not.toEqual(-1); + } + + // clean up + childWindow.close(); + }); + + it('does not insert duplicate styles', function() { + deletePlotCSSRules(document); // clear the rules + + // fix scope errors + var selector = null; + var fullSelector = null; + + // make sure the rules are cleared + var allSelectors = helpers.getAllRuleSelectors(document); + + for(selector in plotcss) { + fullSelector = helpers.buildFullSelector(selector); + + expect(allSelectors.indexOf(fullSelector)).toEqual(-1); + } + + // plot + var gd = createGraphDiv(); + plot(gd); + plot(gd); // plot again so injectStyles gets called again + + // check for styles + allSelectors = helpers.getAllRuleSelectors(document); + + for(selector in plotcss) { + fullSelector = helpers.buildFullSelector(selector); + + var firstIndex = allSelectors.indexOf(fullSelector); + + // there should be no occurences after the initial one + expect(allSelectors.indexOf(fullSelector, firstIndex + 1)).toEqual(-1); + } + + // clean up + destroyGraphDiv(); + }); +}); diff --git a/test/jasmine/tests/plot_interact_test.js b/test/jasmine/tests/plot_interact_test.js index aa9df30fc62..68ffdc4ee95 100644 --- a/test/jasmine/tests/plot_interact_test.js +++ b/test/jasmine/tests/plot_interact_test.js @@ -625,3 +625,144 @@ describe('plot svg clip paths', function() { }); }); }); + +describe('css injection', function() { + var helpers = require('../../../src/css/helpers'); + var plotcss = require('../../../build/plotcss') + + // create a graph div in a child window + function createGraphDivInChildWindow() { + var childWindow = window.open('about:blank', 'popoutWindow', ''); + + var gd = childWindow.document.createElement('div'); + gd.id = 'graph'; + childWindow.document.body.appendChild(gd); + + // force the graph to be at position 0,0 no matter what + gd.style.position = 'fixed'; + gd.style.left = 0; + gd.style.top = 0; + + return gd; + } + + // the most basic of basic plots + function plot(target) { + Plotly.plot(target, [{ + x: [1, 2, 3, 4, 5], + y: [1, 2, 4, 8, 16] + }], { + margin: { + t: 0 + } + }); + } + + // deletes all rules defined in plotcss + function deletePlotCSSRules(sourceDocument) { + for(var selector in plotcss) { + var ruleDeleted = false; + var fullSelector = helpers.buildFullSelector(selector); + + for(var i = 0; i < sourceDocument.styleSheets.length; i++) { + var styleSheet = sourceDocument.styleSheets[i]; + var selectors = [] + + for(var j = 0; j < styleSheet.cssRules.length; j++) { + var cssRule = styleSheet.cssRules[j]; + + selectors.push(cssRule.selectorText); + } + + var selectorIndex = selectors.indexOf(fullSelector); + + if(selectorIndex !== -1) { + styleSheet.deleteRule(selectorIndex); + break; + } + } + } + } + + it('inserts styles on initial plot', function() { + deletePlotCSSRules(document); // clear the rules + + // make sure the rules are clared + var allSelectors = helpers.getAllRuleSelectors(document); + + for(var selector in plotcss) { + var fullSelector = helpers.buildFullSelector(selector); + + expect(allSelectors.indexOf(fullSelector)).toEqual(-1); + } + + // plot + var gd = createGraphDiv(); + plot(gd); + + // check for styles + allSelectors = helpers.getAllRuleSelectors(document); + + for(var selector in plotcss) { + var fullSelector = helpers.buildFullSelector(selector); + + expect(allSelectors.indexOf(fullSelector)).not.toEqual(-1); + } + + // clean up + destroyGraphDiv(); + }); + + it('inserts styles in a child window document', function() { + var gd = createGraphDivInChildWindow(); + var childWindow = gd.ownerDocument.defaultView; + + // plot + plot(gd); + + // check for styles + allSelectors = helpers.getAllRuleSelectors(gd.ownerDocument); + + for(var selector in plotcss) { + var fullSelector = helpers.buildFullSelector(selector); + + expect(allSelectors.indexOf(fullSelector)).not.toEqual(-1); + } + + // clean up + childWindow.close(); + }); + + it('does not insert duplicate styles', function() { + deletePlotCSSRules(document); // clear the rules + + // make sure the rules are clared + var allSelectors = helpers.getAllRuleSelectors(document); + + for(var selector in plotcss) { + var fullSelector = helpers.buildFullSelector(selector); + + expect(allSelectors.indexOf(fullSelector)).toEqual(-1); + } + + // plot + var gd = createGraphDiv(); + plot(gd); + plot(gd); // plot again so injectStyles gets called again + + // check for styles + allSelectors = helpers.getAllRuleSelectors(document); + + for(var selector in plotcss) { + var fullSelector = helpers.buildFullSelector(selector); + + var firstIndex = allSelectors.indexOf(fullSelector); + + // there should be no occurences after the initial one + expect(allSelectors.indexOf(fullSelector, firstIndex + 1)).toEqual(-1); + } + + // clean up + destroyGraphDiv(); + }); +});