|
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