|
9 | 9 |
|
10 | 10 | 'use strict'; |
11 | 11 |
|
12 | | -var d3 = require('d3'); |
13 | | - |
14 | | -var Lib = require('../../lib'); |
15 | | -var Icons = require('../../../build/ploticon'); |
16 | | - |
17 | | - |
18 | | -/** |
19 | | - * UI controller for interactive plots |
20 | | - * @Class |
21 | | - * @Param {object} opts |
22 | | - * @Param {object} opts.buttons nested arrays of grouped buttons config objects |
23 | | - * @Param {object} opts.container container div to append modeBar |
24 | | - * @Param {object} opts.graphInfo primary plot object containing data and layout |
25 | | - */ |
26 | | -function ModeBar(opts) { |
27 | | - this.container = opts.container; |
28 | | - this.element = document.createElement('div'); |
29 | | - |
30 | | - this.update(opts.graphInfo, opts.buttons); |
31 | | - |
32 | | - this.container.appendChild(this.element); |
33 | | -} |
34 | | - |
35 | | -var proto = ModeBar.prototype; |
36 | | - |
37 | | -/** |
38 | | - * Update modeBar (buttons and logo) |
39 | | - * |
40 | | - * @param {object} graphInfo primary plot object containing data and layout |
41 | | - * @param {array of arrays} buttons nested arrays of grouped buttons to initialize |
42 | | - * |
43 | | - */ |
44 | | -proto.update = function(graphInfo, buttons) { |
45 | | - this.graphInfo = graphInfo; |
46 | | - |
47 | | - var context = this.graphInfo._context; |
48 | | - |
49 | | - if(context.displayModeBar === 'hover') { |
50 | | - this.element.className = 'modebar modebar--hover'; |
51 | | - } |
52 | | - else this.element.className = 'modebar'; |
53 | | - |
54 | | - // if buttons or logo have changed, redraw modebar interior |
55 | | - var needsNewButtons = !this.hasButtons(buttons), |
56 | | - needsNewLogo = (this.hasLogo !== context.displaylogo); |
57 | | - |
58 | | - if(needsNewButtons || needsNewLogo) { |
59 | | - this.removeAllButtons(); |
60 | | - |
61 | | - this.updateButtons(buttons); |
62 | | - |
63 | | - if(context.displaylogo) { |
64 | | - this.element.appendChild(this.getLogo()); |
65 | | - this.hasLogo = true; |
66 | | - } |
67 | | - } |
68 | | - |
69 | | - this.updateActiveButton(); |
70 | | -}; |
71 | | - |
72 | | -proto.updateButtons = function(buttons) { |
73 | | - var _this = this; |
74 | | - |
75 | | - this.buttons = buttons; |
76 | | - this.buttonElements = []; |
77 | | - this.buttonsNames = []; |
78 | | - |
79 | | - this.buttons.forEach(function(buttonGroup) { |
80 | | - var group = _this.createGroup(); |
81 | | - |
82 | | - buttonGroup.forEach(function(buttonConfig) { |
83 | | - var buttonName = buttonConfig.name; |
84 | | - if(!buttonName) { |
85 | | - throw new Error('must provide button \'name\' in button config'); |
86 | | - } |
87 | | - if(_this.buttonsNames.indexOf(buttonName) !== -1) { |
88 | | - throw new Error('button name \'' + buttonName + '\' is taken'); |
89 | | - } |
90 | | - _this.buttonsNames.push(buttonName); |
91 | | - |
92 | | - var button = _this.createButton(buttonConfig); |
93 | | - _this.buttonElements.push(button); |
94 | | - group.appendChild(button); |
95 | | - }); |
96 | | - |
97 | | - _this.element.appendChild(group); |
98 | | - }); |
99 | | -}; |
100 | | - |
101 | | -/** |
102 | | - * Empty div for containing a group of buttons |
103 | | - * @Return {HTMLelement} |
104 | | - */ |
105 | | -proto.createGroup = function() { |
106 | | - var group = document.createElement('div'); |
107 | | - group.className = 'modebar-group'; |
108 | | - |
109 | | - return group; |
110 | | -}; |
111 | | - |
112 | | -/** |
113 | | - * Create a new button div and set constant and configurable attributes |
114 | | - * @Param {object} config (see ./buttons.js for more info) |
115 | | - * @Return {HTMLelement} |
116 | | - */ |
117 | | -proto.createButton = function(config) { |
118 | | - var _this = this, |
119 | | - button = document.createElement('a'); |
120 | | - |
121 | | - button.setAttribute('rel', 'tooltip'); |
122 | | - button.className = 'modebar-btn'; |
123 | | - |
124 | | - var title = config.title; |
125 | | - if(title === undefined) title = config.name; |
126 | | - if(title || title === 0) button.setAttribute('data-title', title); |
127 | | - |
128 | | - if(config.attr !== undefined) button.setAttribute('data-attr', config.attr); |
129 | | - |
130 | | - var val = config.val; |
131 | | - if(val !== undefined) { |
132 | | - if(typeof val === 'function') val = val(this.graphInfo); |
133 | | - button.setAttribute('data-val', val); |
134 | | - } |
135 | | - |
136 | | - var click = config.click; |
137 | | - if(typeof click !== 'function') { |
138 | | - throw new Error('must provide button \'click\' function in button config'); |
139 | | - } |
140 | | - else { |
141 | | - button.addEventListener('click', function(ev) { |
142 | | - config.click(_this.graphInfo, ev); |
143 | | - |
144 | | - // only needed for 'hoverClosestGeo' which does not call relayout |
145 | | - _this.updateActiveButton(ev.currentTarget); |
146 | | - }); |
147 | | - } |
148 | | - |
149 | | - button.setAttribute('data-toggle', config.toggle || false); |
150 | | - if(config.toggle) button.classList.add('active'); |
151 | | - |
152 | | - button.appendChild(this.createIcon(config.icon || Icons.question)); |
153 | | - button.setAttribute('data-gravity', config.gravity || 'n'); |
154 | | - |
155 | | - return button; |
156 | | -}; |
157 | | - |
158 | | -/** |
159 | | - * Add an icon to a button |
160 | | - * @Param {object} thisIcon |
161 | | - * @Param {number} thisIcon.width |
162 | | - * @Param {string} thisIcon.path |
163 | | - * @Return {HTMLelement} |
164 | | - */ |
165 | | -proto.createIcon = function(thisIcon) { |
166 | | - var iconHeight = thisIcon.ascent - thisIcon.descent, |
167 | | - svgNS = 'http://www.w3.org/2000/svg', |
168 | | - icon = document.createElementNS(svgNS, 'svg'), |
169 | | - path = document.createElementNS(svgNS, 'path'); |
170 | | - |
171 | | - icon.setAttribute('height', '1em'); |
172 | | - icon.setAttribute('width', (thisIcon.width / iconHeight) + 'em'); |
173 | | - icon.setAttribute('viewBox', [0, 0, thisIcon.width, iconHeight].join(' ')); |
174 | | - |
175 | | - path.setAttribute('d', thisIcon.path); |
176 | | - path.setAttribute('transform', 'matrix(1 0 0 -1 0 ' + thisIcon.ascent + ')'); |
177 | | - icon.appendChild(path); |
178 | | - |
179 | | - return icon; |
180 | | -}; |
181 | | - |
182 | | -/** |
183 | | - * Updates active button with attribute specified in layout |
184 | | - * @Param {object} graphInfo plot object containing data and layout |
185 | | - * @Return {HTMLelement} |
186 | | - */ |
187 | | -proto.updateActiveButton = function(buttonClicked) { |
188 | | - var fullLayout = this.graphInfo._fullLayout, |
189 | | - dataAttrClicked = (buttonClicked !== undefined) ? |
190 | | - buttonClicked.getAttribute('data-attr') : |
191 | | - null; |
192 | | - |
193 | | - this.buttonElements.forEach(function(button) { |
194 | | - var thisval = button.getAttribute('data-val') || true, |
195 | | - dataAttr = button.getAttribute('data-attr'), |
196 | | - isToggleButton = (button.getAttribute('data-toggle') === 'true'), |
197 | | - button3 = d3.select(button); |
198 | | - |
199 | | - // Use 'data-toggle' and 'buttonClicked' to toggle buttons |
200 | | - // that have no one-to-one equivalent in fullLayout |
201 | | - if(isToggleButton) { |
202 | | - if(dataAttr === dataAttrClicked) { |
203 | | - button3.classed('active', !button3.classed('active')); |
204 | | - } |
205 | | - } |
206 | | - else { |
207 | | - var val = (dataAttr === null) ? |
208 | | - dataAttr : |
209 | | - Lib.nestedProperty(fullLayout, dataAttr).get(); |
210 | | - |
211 | | - button3.classed('active', val === thisval); |
212 | | - } |
213 | | - |
214 | | - }); |
215 | | -}; |
216 | | - |
217 | | -/** |
218 | | - * Check if modeBar is configured as button configuration argument |
219 | | - * |
220 | | - * @Param {object} buttons 2d array of grouped button config objects |
221 | | - * @Return {boolean} |
222 | | - */ |
223 | | -proto.hasButtons = function(buttons) { |
224 | | - var currentButtons = this.buttons; |
225 | | - |
226 | | - if(!currentButtons) return false; |
227 | | - |
228 | | - if(buttons.length !== currentButtons.length) return false; |
229 | | - |
230 | | - for(var i = 0; i < buttons.length; ++i) { |
231 | | - if(buttons[i].length !== currentButtons[i].length) return false; |
232 | | - for(var j = 0; j < buttons[i].length; j++) { |
233 | | - if(buttons[i][j].name !== currentButtons[i][j].name) return false; |
234 | | - } |
235 | | - } |
236 | | - |
237 | | - return true; |
238 | | -}; |
239 | | - |
240 | | -/** |
241 | | - * @return {HTMLDivElement} The logo image wrapped in a group |
242 | | - */ |
243 | | -proto.getLogo = function() { |
244 | | - var group = this.createGroup(), |
245 | | - a = document.createElement('a'); |
246 | | - |
247 | | - a.href = 'https://plot.ly/'; |
248 | | - a.target = '_blank'; |
249 | | - a.setAttribute('data-title', 'Produced with Plotly'); |
250 | | - a.className = 'modebar-btn plotlyjsicon modebar-btn--logo'; |
251 | | - |
252 | | - a.appendChild(this.createIcon(Icons.plotlylogo)); |
253 | | - |
254 | | - group.appendChild(a); |
255 | | - return group; |
256 | | -}; |
257 | | - |
258 | | -proto.removeAllButtons = function() { |
259 | | - while(this.element.firstChild) { |
260 | | - this.element.removeChild(this.element.firstChild); |
261 | | - } |
262 | | - |
263 | | - this.hasLogo = false; |
264 | | -}; |
265 | | - |
266 | | -proto.destroy = function() { |
267 | | - Lib.removeElement(this.container.querySelector('.modebar')); |
268 | | -}; |
269 | | - |
270 | | -function createModeBar(gd, buttons) { |
271 | | - var fullLayout = gd._fullLayout; |
272 | | - |
273 | | - var modeBar = new ModeBar({ |
274 | | - graphInfo: gd, |
275 | | - container: fullLayout._paperdiv.node(), |
276 | | - buttons: buttons |
277 | | - }); |
278 | | - |
279 | | - if(fullLayout._privateplot) { |
280 | | - d3.select(modeBar.element).append('span') |
281 | | - .classed('badge-private float--left', true) |
282 | | - .text('PRIVATE'); |
283 | | - } |
284 | | - |
285 | | - return modeBar; |
286 | | -} |
287 | | - |
288 | | -module.exports = createModeBar; |
| 12 | +exports.manage = require('./manage'); |
0 commit comments