diff --git a/build/plotcss.js b/build/plotcss.js
index afed129a1d9..669cf65d480 100644
--- a/build/plotcss.js
+++ b/build/plotcss.js
@@ -31,21 +31,21 @@ var rules = {
"X .cursor-n-resize": "cursor:n-resize;",
"X .cursor-ne-resize": "cursor:ne-resize;",
"X .cursor-grab": "cursor:-webkit-grab;cursor:grab;",
- "X .modebar": "position:absolute;top:2px;right:2px;z-index:1001;background:rgba(255,255,255,0.7);",
+ "X .modebar": "position:absolute;top:2px;right:2px;z-index:1001;",
"X .modebar--hover": "opacity:0;-webkit-transition:opacity 0.3s ease 0s;-moz-transition:opacity 0.3s ease 0s;-ms-transition:opacity 0.3s ease 0s;-o-transition:opacity 0.3s ease 0s;transition:opacity 0.3s ease 0s;",
"X:hover .modebar--hover": "opacity:1;",
"X .modebar-group": "float:left;display:inline-block;box-sizing:border-box;margin-left:8px;position:relative;vertical-align:middle;white-space:nowrap;",
- "X .modebar-group:first-child": "margin-left:0px;",
- "X .modebar-btn": "position:relative;font-size:16px;padding:3px 4px;cursor:pointer;line-height:normal;box-sizing:border-box;",
+ "X .modebar-btn": "position:relative;font-size:16px;padding:3px 4px;height:22px;cursor:pointer;line-height:normal;box-sizing:border-box;",
"X .modebar-btn svg": "position:relative;top:2px;",
- "X .modebar-btn path": "fill:rgba(0,31,95,0.3);",
- "X .modebar-btn.active path,X .modebar-btn:hover path": "fill:rgba(0,22,72,0.5);",
- "X .modebar-btn.modebar-btn--logo": "padding:3px 1px;",
- "X .modebar-btn.modebar-btn--logo path": "fill:#447adb !important;",
+ "X .modebar.vertical": "top:-1px;",
+ "X .modebar.vertical .modebar-group": "display:block;float:none;margin-left:0px;margin-bottom:8px;",
+ "X .modebar.vertical .modebar-group .modebar-btn": "display:block;text-align:center;",
"X [data-title]:before,X [data-title]:after": "position:absolute;-webkit-transform:translate3d(0, 0, 0);-moz-transform:translate3d(0, 0, 0);-ms-transform:translate3d(0, 0, 0);-o-transform:translate3d(0, 0, 0);transform:translate3d(0, 0, 0);display:none;opacity:0;z-index:1001;pointer-events:none;top:110%;right:50%;",
"X [data-title]:hover:before,X [data-title]:hover:after": "display:block;opacity:1;",
"X [data-title]:before": "content:'';position:absolute;background:transparent;border:6px solid transparent;z-index:1002;margin-top:-12px;border-bottom-color:#69738a;margin-right:-6px;",
"X [data-title]:after": "content:attr(data-title);background:#69738a;color:white;padding:8px 10px;font-size:12px;line-height:12px;white-space:nowrap;margin-right:-18px;border-radius:2px;",
+ "X .vertical [data-title]:before,X .vertical [data-title]:after": "top:0%;right:200%;",
+ "X .vertical [data-title]:before": "border:6px solid transparent;border-left-color:#69738a;margin-top:8px;margin-right:-30px;",
"X .select-outline": "fill:none;stroke-width:1;shape-rendering:crispEdges;",
"X .select-outline-1": "stroke:white;",
"X .select-outline-2": "stroke:black;stroke-dasharray:2px 2px;",
diff --git a/build/ploticon.js b/build/ploticon.js
index f7bcbcd4781..eb6ecadccac 100644
--- a/build/ploticon.js
+++ b/build/ploticon.js
@@ -32,13 +32,13 @@ module.exports = {
'transform': 'matrix(1 0 0 -1 0 850)'
},
'zoom_plus': {
- 'width': 1000,
+ 'width': 875,
'height': 1000,
'path': 'm1 787l0-875 875 0 0 875-875 0z m687-500l-187 0 0-187-125 0 0 187-188 0 0 125 188 0 0 187 125 0 0-187 187 0 0-125z',
'transform': 'matrix(1 0 0 -1 0 850)'
},
'zoom_minus': {
- 'width': 1000,
+ 'width': 875,
'height': 1000,
'path': 'm0 788l0-876 875 0 0 876-875 0z m688-500l-500 0 0 125 500 0 0-125z',
'transform': 'matrix(1 0 0 -1 0 850)'
@@ -120,5 +120,9 @@ module.exports = {
'height': 1000,
'path': 'M512 409c0-57-46-104-103-104-57 0-104 47-104 104 0 57 47 103 104 103 57 0 103-46 103-103z m-327-39l92 0 0 92-92 0z m-185 0l92 0 0 92-92 0z m370-186l92 0 0 93-92 0z m0-184l92 0 0 92-92 0z',
'transform': 'matrix(1.5 0 0 -1.5 0 850)'
+ },
+ 'newplotlylogo': {
+ 'name': 'newplotlylogo',
+ 'svg': ''
}
};
diff --git a/src/components/modebar/modebar.js b/src/components/modebar/modebar.js
index 445ad3e5088..6928d133aa3 100644
--- a/src/components/modebar/modebar.js
+++ b/src/components/modebar/modebar.js
@@ -14,7 +14,7 @@ var isNumeric = require('fast-isnumeric');
var Lib = require('../../lib');
var Icons = require('../../../build/ploticon');
-
+var Parser = new DOMParser();
/**
* UI controller for interactive plots
@@ -45,13 +45,29 @@ var proto = ModeBar.prototype;
proto.update = function(graphInfo, buttons) {
this.graphInfo = graphInfo;
- var context = this.graphInfo._context;
+ var context = this.graphInfo._context,
+ fullLayout = this.graphInfo._fullLayout,
+ modeBarId = 'modebar-' + fullLayout._uid;
+
+ this.element.setAttribute('id', modeBarId);
+ this._uid = modeBarId;
if(context.displayModeBar === 'hover') {
this.element.className = 'modebar modebar--hover';
}
else this.element.className = 'modebar';
+ if(fullLayout.modebar.orientation === 'v') {
+ this.element.className += ' vertical';
+ buttons = buttons.reverse();
+ }
+
+ Lib.deleteRelatedStyleRule(modeBarId);
+ Lib.addRelatedStyleRule(modeBarId, '#' + modeBarId, 'background-color: ' + fullLayout.modebar.bgcolor);
+ Lib.addRelatedStyleRule(modeBarId, '#' + modeBarId + ' .modebar-btn .icon path', 'fill: ' + fullLayout.modebar.color);
+ Lib.addRelatedStyleRule(modeBarId, '#' + modeBarId + ' .modebar-btn:hover .icon path', 'fill: ' + fullLayout.modebar.activecolor);
+ Lib.addRelatedStyleRule(modeBarId, '#' + modeBarId + ' .modebar-btn.active .icon path', 'fill: ' + fullLayout.modebar.activecolor);
+
// if buttons or logo have changed, redraw modebar interior
var needsNewButtons = !this.hasButtons(buttons);
var needsNewLogo = (this.hasLogo !== context.displaylogo);
@@ -65,7 +81,12 @@ proto.update = function(graphInfo, buttons) {
this.updateButtons(buttons);
if(context.displaylogo) {
- this.element.appendChild(this.getLogo());
+ if(fullLayout.modebar.orientation === 'v') {
+ this.element.prepend(this.getLogo());
+ } else {
+ this.element.appendChild(this.getLogo());
+ }
+
this.hasLogo = true;
}
}
@@ -173,6 +194,7 @@ proto.createButton = function(config) {
* @Param {object} thisIcon
* @Param {number} thisIcon.width
* @Param {string} thisIcon.path
+ * @Param {string} thisIcon.color
* @Return {HTMLelement}
*/
proto.createIcon = function(thisIcon) {
@@ -180,24 +202,34 @@ proto.createIcon = function(thisIcon) {
Number(thisIcon.height) :
thisIcon.ascent - thisIcon.descent,
svgNS = 'http://www.w3.org/2000/svg',
- icon = document.createElementNS(svgNS, 'svg'),
- path = document.createElementNS(svgNS, 'path');
+ icon;
- icon.setAttribute('height', '1em');
- icon.setAttribute('width', (thisIcon.width / iconHeight) + 'em');
- icon.setAttribute('viewBox', [0, 0, thisIcon.width, iconHeight].join(' '));
+ if(thisIcon.path) {
+ icon = document.createElementNS(svgNS, 'svg');
+ icon.setAttribute('viewBox', [0, 0, thisIcon.width, iconHeight].join(' '));
+ icon.setAttribute('class', 'icon');
+
+ var path = document.createElementNS(svgNS, 'path');
+ path.setAttribute('d', thisIcon.path);
- path.setAttribute('d', thisIcon.path);
+ if(thisIcon.transform) {
+ path.setAttribute('transform', thisIcon.transform);
+ }
+ else if(thisIcon.ascent !== undefined) {
+ // Legacy icon transform calculation
+ path.setAttribute('transform', 'matrix(1 0 0 -1 0 ' + thisIcon.ascent + ')');
+ }
- if(thisIcon.transform) {
- path.setAttribute('transform', thisIcon.transform);
+ icon.appendChild(path);
}
- else if(thisIcon.ascent !== undefined) {
- // Legacy icon transform calculation
- path.setAttribute('transform', 'matrix(1 0 0 -1 0 ' + thisIcon.ascent + ')');
+
+ if(thisIcon.svg) {
+ var svgDoc = Parser.parseFromString(thisIcon.svg, 'application/xml');
+ icon = svgDoc.childNodes[0];
}
- icon.appendChild(path);
+ icon.setAttribute('height', '1em');
+ icon.setAttribute('width', '1em');
return icon;
};
@@ -272,7 +304,7 @@ proto.getLogo = function() {
a.setAttribute('data-title', Lib._(this.graphInfo, 'Produced with Plotly'));
a.className = 'modebar-btn plotlyjsicon modebar-btn--logo';
- a.appendChild(this.createIcon(Icons.plotlylogo));
+ a.appendChild(this.createIcon(Icons.newplotlylogo));
group.appendChild(a);
return group;
@@ -288,6 +320,7 @@ proto.removeAllButtons = function() {
proto.destroy = function() {
Lib.removeElement(this.container.querySelector('.modebar'));
+ Lib.deleteRelatedStyleRule(this._uid);
};
function createModeBar(gd, buttons) {
diff --git a/src/css/_modebar.scss b/src/css/_modebar.scss
index adb85a6878a..a7e128bb573 100644
--- a/src/css/_modebar.scss
+++ b/src/css/_modebar.scss
@@ -3,7 +3,6 @@
top: 2px;
right: 2px;
z-index: 1001;
- background: rgba(255,255,255,0.7);
}
.modebar--hover {
@@ -23,17 +22,13 @@
position: relative;
vertical-align: middle;
white-space: nowrap;
-
- &:first-child {
- margin-left: 0px;
- }
}
-
.modebar-btn {
position: relative;
font-size: 16px;
padding: 3px 4px;
+ height: 22px;
/* display: inline-block; including this breaks 3d interaction in .embed mode. Chrome bug? */
cursor: pointer;
line-height: normal;
@@ -44,19 +39,22 @@
top: 2px;
}
- path {
- fill: rgba(0,31,95,0.3);
- }
+ &.modebar-btn--logo {
- &.active path, &:hover path {
- fill: rgba(0,22,72,0.5);
}
+}
- &.modebar-btn--logo {
- padding: 3px 1px;
+.modebar.vertical {
+ top: -1px;
+ .modebar-group {
+ display: block;
+ float: none;
+ margin-left: 0px;
+ margin-bottom: 8px;
- path {
- fill: $color-brand-primary !important;
+ .modebar-btn {
+ display: block;
+ text-align: center;
}
}
}
diff --git a/src/css/_tooltip.scss b/src/css/_tooltip.scss
index 1f113a4af7f..596ed44d936 100644
--- a/src/css/_tooltip.scss
+++ b/src/css/_tooltip.scss
@@ -51,7 +51,7 @@ $successColor: hsl(121, 32%, 40%) !default;
opacity: 1;
}
- // Arrow
+ // Top arrow
&:before {
content: '';
position: absolute;
@@ -78,3 +78,18 @@ $successColor: hsl(121, 32%, 40%) !default;
border-radius: 2px;
}
}
+
+.vertical [data-title] {
+ &:before, &:after {
+ top: 0%;
+ right: 200%;
+ }
+
+ // Right arrow
+ &:before {
+ border: $arrowBorderWidth solid transparent;
+ border-left-color: $defaultColor;
+ margin-top: $verticalPadding;
+ margin-right: -1 * ($arrowOffsetX + 2 * $arrowBorderWidth);
+ }
+}
diff --git a/src/fonts/ploticon/ploticon.svg b/src/fonts/ploticon/ploticon.svg
index 5007169983b..e2a07ec54d4 100644
--- a/src/fonts/ploticon/ploticon.svg
+++ b/src/fonts/ploticon/ploticon.svg
@@ -11,8 +11,8 @@
-
-
+
+
@@ -27,5 +27,25 @@
+
+
diff --git a/src/lib/index.js b/src/lib/index.js
index 50a1e39948b..e0b6576d8ca 100644
--- a/src/lib/index.js
+++ b/src/lib/index.js
@@ -679,14 +679,24 @@ lib.removeElement = function(el) {
* by all calls to this function
*/
lib.addStyleRule = function(selector, styleString) {
- if(!lib.styleSheet) {
- var style = document.createElement('style');
+ lib.addRelatedStyleRule('global', selector, styleString);
+};
+
+/**
+ * for dynamically adding style rules
+ * to a stylesheet uniquely identified by a uid
+ */
+lib.addRelatedStyleRule = function(uid, selector, styleString) {
+ var id = 'plotly.js-style-' + uid,
+ style = document.getElementById(id);
+ if(!style) {
+ style = document.createElement('style');
+ style.setAttribute('id', id);
// WebKit hack :(
style.appendChild(document.createTextNode(''));
document.head.appendChild(style);
- lib.styleSheet = style.sheet;
}
- var styleSheet = lib.styleSheet;
+ var styleSheet = style.sheet;
if(styleSheet.insertRule) {
styleSheet.insertRule(selector + '{' + styleString + '}', 0);
@@ -697,6 +707,15 @@ lib.addStyleRule = function(selector, styleString) {
else lib.warn('addStyleRule failed');
};
+/**
+ * to remove from the page a stylesheet identified by a given uid
+ */
+lib.deleteRelatedStyleRule = function(uid) {
+ var id = 'plotly.js-style-' + uid,
+ style = document.getElementById(id);
+ if(style) style.remove();
+};
+
lib.isIE = function() {
return typeof window.navigator.msSaveBlob !== 'undefined';
};
diff --git a/src/plots/layout_attributes.js b/src/plots/layout_attributes.js
index 9e422717d4c..103a0b234f7 100644
--- a/src/plots/layout_attributes.js
+++ b/src/plots/layout_attributes.js
@@ -224,5 +224,34 @@ module.exports = {
'or a logo image, for example. To omit one of these items on the plot,',
'make an item with matching `templateitemname` and `visible: false`.'
].join(' ')
+ },
+ modebar: {
+ orientation: {
+ valType: 'enumerated',
+ values: ['v', 'h'],
+ dflt: 'h',
+ role: 'info',
+ editType: 'modebar',
+ description: 'Sets the orientation of the modebar.'
+ },
+ bgcolor: {
+ valType: 'color',
+ role: 'style',
+ editType: 'modebar',
+ description: 'Sets the background color of the modebar.'
+ },
+ color: {
+ valType: 'color',
+ role: 'style',
+ editType: 'modebar',
+ description: 'Sets the color of the icons in the modebar.'
+ },
+ activecolor: {
+ valType: 'color',
+ role: 'style',
+ editType: 'modebar',
+ description: 'Sets the color of the active or hovered on icons in the modebar.'
+ },
+ editType: 'modebar'
}
};
diff --git a/src/plots/plots.js b/src/plots/plots.js
index 8f172d14ba0..648b340094b 100644
--- a/src/plots/plots.js
+++ b/src/plots/plots.js
@@ -1334,6 +1334,12 @@ plots.supplyLayoutGlobalDefaults = function(layoutIn, layoutOut, formatObj) {
coerce('datarevision');
+ coerce('modebar.orientation');
+ coerce('modebar.bgcolor', Color.addOpacity(layoutOut.paper_bgcolor, 0.5));
+ var modebarDefaultColor = Color.contrast(Color.rgb(layoutOut.modebar.bgcolor));
+ coerce('modebar.color', Color.addOpacity(modebarDefaultColor, 0.3));
+ coerce('modebar.activecolor', Color.addOpacity(modebarDefaultColor, 0.7));
+
Registry.getComponentMethod(
'calendars',
'handleDefaults'
diff --git a/tasks/util/pull_font_svg.js b/tasks/util/pull_font_svg.js
index b509b98a512..5fe807d6de4 100644
--- a/tasks/util/pull_font_svg.js
+++ b/tasks/util/pull_font_svg.js
@@ -2,7 +2,7 @@ var fs = require('fs');
var xml2js = require('xml2js');
var parser = new xml2js.Parser();
-
+var builder = new xml2js.Builder({ headless: true, rootName: 'svg', renderOpts: {'newline': ''}});
module.exports = function pullFontSVG(data, pathOut) {
parser.parseString(data, function(err, result) {
@@ -28,6 +28,17 @@ module.exports = function pullFontSVG(data, pathOut) {
};
});
+ // Load SVG
+ var svgs = result.svg.defs[0].svg;
+ svgs.forEach(function(svg) {
+ var name = svg.$.id;
+ delete svg.$.id;
+ chars[name] = {
+ name: name,
+ svg: builder.buildObject(svg)
+ };
+ });
+
// turn remaining double quotes into single
var charStr = JSON.stringify(chars, null, 4).replace(/\"/g, '\'');
diff --git a/test/jasmine/tests/modebar_test.js b/test/jasmine/tests/modebar_test.js
index d0e447d21a4..8c2267bb3cf 100644
--- a/test/jasmine/tests/modebar_test.js
+++ b/test/jasmine/tests/modebar_test.js
@@ -9,6 +9,7 @@ var Registry = require('@src/registry');
var createGraphDiv = require('../assets/create_graph_div');
var destroyGraphDiv = require('../assets/destroy_graph_div');
var selectButton = require('../assets/modebar_button');
+var failTest = require('../assets/fail_test');
describe('ModeBar', function() {
@@ -29,10 +30,17 @@ describe('ModeBar', function() {
function getMockGraphInfo(xaxes, yaxes) {
return {
_fullLayout: {
+ _uid: '6ea6a7',
dragmode: 'zoom',
_paperdiv: d3.select(getMockContainerTree()),
_has: Plots._hasPlotType,
- _subplots: {xaxis: xaxes || [], yaxis: yaxes || []}
+ _subplots: {xaxis: xaxes || [], yaxis: yaxes || []},
+ modebar: {
+ orientation: 'h',
+ bgcolor: 'rgba(255,255,255,0.7)',
+ color: 'rgba(0, 31, 95, 0.3)',
+ activecolor: 'rgba(0, 31, 95, 1)'
+ }
},
_fullData: [],
_context: {
@@ -291,6 +299,7 @@ describe('ModeBar', function() {
modeBar.destroy();
expect(modeBarParent.querySelector('.modebar')).toBeNull();
+
});
});
@@ -1229,4 +1238,104 @@ describe('ModeBar', function() {
});
});
});
+
+ describe('modebar styling', function() {
+ var gd,
+ colors = ['rgba(128, 128, 128, 0.7)', 'rgba(255, 0, 128, 0.2)'],
+ targetBtn = 'pan2d', button, style;
+
+ beforeEach(function() {
+ gd = createGraphDiv();
+ });
+
+ afterEach(function() {
+ Plotly.purge(gd);
+ destroyGraphDiv();
+ });
+
+ function checkButtonColor(button, color) {
+ var paths = button.node.querySelector('path');
+ var style = window.getComputedStyle(paths);
+ expect(style.fill).toBe(color);
+ }
+
+ it('create an associated style element and destroy it on purge', function(done) {
+ var styleSelector, style;
+ Plotly.plot(gd, [], {})
+ .then(function() {
+ styleSelector = 'style[id*="modebar-' + gd._fullLayout._uid + '"]';
+
+ style = document.querySelector(styleSelector);
+ expect(style).toBeTruthy();
+ })
+ .then(function() {
+ Plotly.purge(gd);
+ style = document.querySelector(styleSelector);
+ expect(style).toBeNull();
+ })
+ .then(done);
+ });
+
+ it('changes icon colors', function(done) {
+ Plotly.plot(gd, [], {modebar: { color: colors[0]}})
+ .then(function() {
+ button = selectButton(gd._fullLayout._modeBar, targetBtn);
+ checkButtonColor(button, colors[0]);
+ })
+ .then(function() {Plotly.relayout(gd, 'modebar.color', colors[1]);})
+ .then(function() {
+ checkButtonColor(button, colors[1]);
+ })
+ .catch(failTest)
+ .then(done);
+ });
+
+ it('changes active icon colors', function(done) {
+ Plotly.plot(gd, [], {modebar: { activecolor: colors[0]}})
+ .then(function() {
+ button = selectButton(gd._fullLayout._modeBar, targetBtn);
+ button.click();
+ checkButtonColor(button, colors[0]);
+ })
+ .then(function() {Plotly.relayout(gd, 'modebar.activecolor', colors[1]);})
+ .then(function() {
+ checkButtonColor(button, colors[1]);
+ })
+ .catch(failTest)
+ .then(done);
+ });
+
+ it('changes background color', function(done) {
+ Plotly.plot(gd, [], {modebar: { bgcolor: colors[0]}})
+ .then(function() {
+ style = window.getComputedStyle(gd._fullLayout._modeBar.element);
+ expect(style.backgroundColor).toBe(colors[0]);
+ })
+ .then(function() {Plotly.relayout(gd, 'modebar.bgcolor', colors[1]);})
+ .then(function() {
+ style = window.getComputedStyle(gd._fullLayout._modeBar.element);
+ expect(style.backgroundColor).toBe(colors[1]);
+ })
+ .catch(failTest)
+ .then(done);
+ });
+
+ it('changes orientation', function(done) {
+ var modeBarEl, size;
+
+ Plotly.plot(gd, [], {modebar: { orientation: 'v' }})
+ .then(function() {
+ modeBarEl = gd._fullLayout._modeBar.element;
+ size = modeBarEl.getBoundingClientRect();
+ expect(size.width < size.height).toBeTruthy();
+ })
+ .then(function() {Plotly.relayout(gd, 'modebar.orientation', 'h');})
+ .catch(failTest)
+ .then(function() {
+ size = modeBarEl.getBoundingClientRect();
+ expect(size.width > size.height).toBeTruthy();
+ })
+ .then(done);
+ });
+ });
});