UNPKG

2.95 MBJavaScriptView Raw
1/**
2* plotly.js (cartesian) v1.54.4
3* Copyright 2012-2020, Plotly, Inc.
4* All rights reserved.
5* Licensed under the MIT license
6*/
7(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.Plotly = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(_dereq_,module,exports){
8'use strict';
9
10var Lib = _dereq_('../src/lib');
11var rules = {
12 "X,X div": "direction:ltr;font-family:'Open Sans', verdana, arial, sans-serif;margin:0;padding:0;",
13 "X input,X button": "font-family:'Open Sans', verdana, arial, sans-serif;",
14 "X input:focus,X button:focus": "outline:none;",
15 "X a": "text-decoration:none;",
16 "X a:hover": "text-decoration:none;",
17 "X .crisp": "shape-rendering:crispEdges;",
18 "X .user-select-none": "-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;-o-user-select:none;user-select:none;",
19 "X svg": "overflow:hidden;",
20 "X svg a": "fill:#447adb;",
21 "X svg a:hover": "fill:#3c6dc5;",
22 "X .main-svg": "position:absolute;top:0;left:0;pointer-events:none;",
23 "X .main-svg .draglayer": "pointer-events:all;",
24 "X .cursor-default": "cursor:default;",
25 "X .cursor-pointer": "cursor:pointer;",
26 "X .cursor-crosshair": "cursor:crosshair;",
27 "X .cursor-move": "cursor:move;",
28 "X .cursor-col-resize": "cursor:col-resize;",
29 "X .cursor-row-resize": "cursor:row-resize;",
30 "X .cursor-ns-resize": "cursor:ns-resize;",
31 "X .cursor-ew-resize": "cursor:ew-resize;",
32 "X .cursor-sw-resize": "cursor:sw-resize;",
33 "X .cursor-s-resize": "cursor:s-resize;",
34 "X .cursor-se-resize": "cursor:se-resize;",
35 "X .cursor-w-resize": "cursor:w-resize;",
36 "X .cursor-e-resize": "cursor:e-resize;",
37 "X .cursor-nw-resize": "cursor:nw-resize;",
38 "X .cursor-n-resize": "cursor:n-resize;",
39 "X .cursor-ne-resize": "cursor:ne-resize;",
40 "X .cursor-grab": "cursor:-webkit-grab;cursor:grab;",
41 "X .modebar": "position:absolute;top:2px;right:2px;",
42 "X .ease-bg": "-webkit-transition:background-color 0.3s ease 0s;-moz-transition:background-color 0.3s ease 0s;-ms-transition:background-color 0.3s ease 0s;-o-transition:background-color 0.3s ease 0s;transition:background-color 0.3s ease 0s;",
43 "X .modebar--hover>:not(.watermark)": "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;",
44 "X:hover .modebar--hover .modebar-group": "opacity:1;",
45 "X .modebar-group": "float:left;display:inline-block;box-sizing:border-box;padding-left:8px;position:relative;vertical-align:middle;white-space:nowrap;",
46 "X .modebar-btn": "position:relative;font-size:16px;padding:3px 4px;height:22px;cursor:pointer;line-height:normal;box-sizing:border-box;",
47 "X .modebar-btn svg": "position:relative;top:2px;",
48 "X .modebar.vertical": "display:flex;flex-direction:column;flex-wrap:wrap;align-content:flex-end;max-height:100%;",
49 "X .modebar.vertical svg": "top:-1px;",
50 "X .modebar.vertical .modebar-group": "display:block;float:none;padding-left:0px;padding-bottom:8px;",
51 "X .modebar.vertical .modebar-group .modebar-btn": "display:block;text-align:center;",
52 "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%;",
53 "X [data-title]:hover:before,X [data-title]:hover:after": "display:block;opacity:1;",
54 "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;",
55 "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;",
56 "X .vertical [data-title]:before,X .vertical [data-title]:after": "top:0%;right:200%;",
57 "X .vertical [data-title]:before": "border:6px solid transparent;border-left-color:#69738a;margin-top:8px;margin-right:-30px;",
58 "X .select-outline": "fill:none;stroke-width:1;shape-rendering:crispEdges;",
59 "X .select-outline-1": "stroke:white;",
60 "X .select-outline-2": "stroke:black;stroke-dasharray:2px 2px;",
61 Y: "font-family:'Open Sans', verdana, arial, sans-serif;position:fixed;top:50px;right:20px;z-index:10000;font-size:10pt;max-width:180px;",
62 "Y p": "margin:0;",
63 "Y .notifier-note": "min-width:180px;max-width:250px;border:1px solid #fff;z-index:3000;margin:0;background-color:#8c97af;background-color:rgba(140,151,175,0.9);color:#fff;padding:10px;overflow-wrap:break-word;word-wrap:break-word;-ms-hyphens:auto;-webkit-hyphens:auto;hyphens:auto;",
64 "Y .notifier-close": "color:#fff;opacity:0.8;float:right;padding:0 5px;background:none;border:none;font-size:20px;font-weight:bold;line-height:20px;",
65 "Y .notifier-close:hover": "color:#444;text-decoration:none;cursor:pointer;"
66};
67
68for(var selector in rules) {
69 var fullSelector = selector.replace(/^,/,' ,')
70 .replace(/X/g, '.js-plotly-plot .plotly')
71 .replace(/Y/g, '.plotly-notifier');
72 Lib.addStyleRule(fullSelector, rules[selector]);
73}
74
75},{"../src/lib":178}],2:[function(_dereq_,module,exports){
76/**
77* Copyright 2012-2020, Plotly, Inc.
78* All rights reserved.
79*
80* This source code is licensed under the MIT license found in the
81* LICENSE file in the root directory of this source tree.
82*/
83
84'use strict';
85
86module.exports = _dereq_('../src/traces/bar');
87
88},{"../src/traces/bar":287}],3:[function(_dereq_,module,exports){
89/**
90* Copyright 2012-2020, Plotly, Inc.
91* All rights reserved.
92*
93* This source code is licensed under the MIT license found in the
94* LICENSE file in the root directory of this source tree.
95*/
96
97'use strict';
98
99module.exports = _dereq_('../src/traces/box');
100
101},{"../src/traces/box":302}],4:[function(_dereq_,module,exports){
102/**
103* Copyright 2012-2020, Plotly, Inc.
104* All rights reserved.
105*
106* This source code is licensed under the MIT license found in the
107* LICENSE file in the root directory of this source tree.
108*/
109
110'use strict';
111
112module.exports = _dereq_('../src/traces/contour');
113
114},{"../src/traces/contour":322}],5:[function(_dereq_,module,exports){
115/**
116* Copyright 2012-2020, Plotly, Inc.
117* All rights reserved.
118*
119* This source code is licensed under the MIT license found in the
120* LICENSE file in the root directory of this source tree.
121*/
122
123'use strict';
124
125module.exports = _dereq_('../src/core');
126
127},{"../src/core":160}],6:[function(_dereq_,module,exports){
128/**
129* Copyright 2012-2020, Plotly, Inc.
130* All rights reserved.
131*
132* This source code is licensed under the MIT license found in the
133* LICENSE file in the root directory of this source tree.
134*/
135
136'use strict';
137
138module.exports = _dereq_('../src/traces/heatmap');
139
140},{"../src/traces/heatmap":338}],7:[function(_dereq_,module,exports){
141/**
142* Copyright 2012-2020, Plotly, Inc.
143* All rights reserved.
144*
145* This source code is licensed under the MIT license found in the
146* LICENSE file in the root directory of this source tree.
147*/
148
149'use strict';
150
151module.exports = _dereq_('../src/traces/histogram');
152
153},{"../src/traces/histogram":356}],8:[function(_dereq_,module,exports){
154/**
155* Copyright 2012-2020, Plotly, Inc.
156* All rights reserved.
157*
158* This source code is licensed under the MIT license found in the
159* LICENSE file in the root directory of this source tree.
160*/
161
162'use strict';
163
164module.exports = _dereq_('../src/traces/histogram2d');
165
166},{"../src/traces/histogram2d":362}],9:[function(_dereq_,module,exports){
167/**
168* Copyright 2012-2020, Plotly, Inc.
169* All rights reserved.
170*
171* This source code is licensed under the MIT license found in the
172* LICENSE file in the root directory of this source tree.
173*/
174
175'use strict';
176
177module.exports = _dereq_('../src/traces/histogram2dcontour');
178
179},{"../src/traces/histogram2dcontour":366}],10:[function(_dereq_,module,exports){
180/**
181* Copyright 2012-2020, Plotly, Inc.
182* All rights reserved.
183*
184* This source code is licensed under the MIT license found in the
185* LICENSE file in the root directory of this source tree.
186*/
187
188'use strict';
189
190module.exports = _dereq_('../src/traces/image');
191
192},{"../src/traces/image":373}],11:[function(_dereq_,module,exports){
193/**
194* Copyright 2012-2020, Plotly, Inc.
195* All rights reserved.
196*
197* This source code is licensed under the MIT license found in the
198* LICENSE file in the root directory of this source tree.
199*/
200
201'use strict';
202
203var Plotly = _dereq_('./core');
204
205Plotly.register([
206 _dereq_('./bar'),
207 _dereq_('./box'),
208 _dereq_('./heatmap'),
209 _dereq_('./histogram'),
210 _dereq_('./histogram2d'),
211 _dereq_('./histogram2dcontour'),
212 _dereq_('./image'),
213 _dereq_('./pie'),
214 _dereq_('./contour'),
215 _dereq_('./scatterternary'),
216 _dereq_('./violin')
217]);
218
219module.exports = Plotly;
220
221},{"./bar":2,"./box":3,"./contour":4,"./core":5,"./heatmap":6,"./histogram":7,"./histogram2d":8,"./histogram2dcontour":9,"./image":10,"./pie":12,"./scatterternary":13,"./violin":14}],12:[function(_dereq_,module,exports){
222/**
223* Copyright 2012-2020, Plotly, Inc.
224* All rights reserved.
225*
226* This source code is licensed under the MIT license found in the
227* LICENSE file in the root directory of this source tree.
228*/
229
230'use strict';
231
232module.exports = _dereq_('../src/traces/pie');
233
234},{"../src/traces/pie":382}],13:[function(_dereq_,module,exports){
235/**
236* Copyright 2012-2020, Plotly, Inc.
237* All rights reserved.
238*
239* This source code is licensed under the MIT license found in the
240* LICENSE file in the root directory of this source tree.
241*/
242
243'use strict';
244
245module.exports = _dereq_('../src/traces/scatterternary');
246
247},{"../src/traces/scatterternary":422}],14:[function(_dereq_,module,exports){
248/**
249* Copyright 2012-2020, Plotly, Inc.
250* All rights reserved.
251*
252* This source code is licensed under the MIT license found in the
253* LICENSE file in the root directory of this source tree.
254*/
255
256'use strict';
257
258module.exports = _dereq_('../src/traces/violin');
259
260},{"../src/traces/violin":430}],15:[function(_dereq_,module,exports){
261// Copyright Joyent, Inc. and other Node contributors.
262//
263// Permission is hereby granted, free of charge, to any person obtaining a
264// copy of this software and associated documentation files (the
265// "Software"), to deal in the Software without restriction, including
266// without limitation the rights to use, copy, modify, merge, publish,
267// distribute, sublicense, and/or sell copies of the Software, and to permit
268// persons to whom the Software is furnished to do so, subject to the
269// following conditions:
270//
271// The above copyright notice and this permission notice shall be included
272// in all copies or substantial portions of the Software.
273//
274// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
275// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
276// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
277// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
278// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
279// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
280// USE OR OTHER DEALINGS IN THE SOFTWARE.
281
282var objectCreate = Object.create || objectCreatePolyfill
283var objectKeys = Object.keys || objectKeysPolyfill
284var bind = Function.prototype.bind || functionBindPolyfill
285
286function EventEmitter() {
287 if (!this._events || !Object.prototype.hasOwnProperty.call(this, '_events')) {
288 this._events = objectCreate(null);
289 this._eventsCount = 0;
290 }
291
292 this._maxListeners = this._maxListeners || undefined;
293}
294module.exports = EventEmitter;
295
296// Backwards-compat with node 0.10.x
297EventEmitter.EventEmitter = EventEmitter;
298
299EventEmitter.prototype._events = undefined;
300EventEmitter.prototype._maxListeners = undefined;
301
302// By default EventEmitters will print a warning if more than 10 listeners are
303// added to it. This is a useful default which helps finding memory leaks.
304var defaultMaxListeners = 10;
305
306var hasDefineProperty;
307try {
308 var o = {};
309 if (Object.defineProperty) Object.defineProperty(o, 'x', { value: 0 });
310 hasDefineProperty = o.x === 0;
311} catch (err) { hasDefineProperty = false }
312if (hasDefineProperty) {
313 Object.defineProperty(EventEmitter, 'defaultMaxListeners', {
314 enumerable: true,
315 get: function() {
316 return defaultMaxListeners;
317 },
318 set: function(arg) {
319 // check whether the input is a positive number (whose value is zero or
320 // greater and not a NaN).
321 if (typeof arg !== 'number' || arg < 0 || arg !== arg)
322 throw new TypeError('"defaultMaxListeners" must be a positive number');
323 defaultMaxListeners = arg;
324 }
325 });
326} else {
327 EventEmitter.defaultMaxListeners = defaultMaxListeners;
328}
329
330// Obviously not all Emitters should be limited to 10. This function allows
331// that to be increased. Set to zero for unlimited.
332EventEmitter.prototype.setMaxListeners = function setMaxListeners(n) {
333 if (typeof n !== 'number' || n < 0 || isNaN(n))
334 throw new TypeError('"n" argument must be a positive number');
335 this._maxListeners = n;
336 return this;
337};
338
339function $getMaxListeners(that) {
340 if (that._maxListeners === undefined)
341 return EventEmitter.defaultMaxListeners;
342 return that._maxListeners;
343}
344
345EventEmitter.prototype.getMaxListeners = function getMaxListeners() {
346 return $getMaxListeners(this);
347};
348
349// These standalone emit* functions are used to optimize calling of event
350// handlers for fast cases because emit() itself often has a variable number of
351// arguments and can be deoptimized because of that. These functions always have
352// the same number of arguments and thus do not get deoptimized, so the code
353// inside them can execute faster.
354function emitNone(handler, isFn, self) {
355 if (isFn)
356 handler.call(self);
357 else {
358 var len = handler.length;
359 var listeners = arrayClone(handler, len);
360 for (var i = 0; i < len; ++i)
361 listeners[i].call(self);
362 }
363}
364function emitOne(handler, isFn, self, arg1) {
365 if (isFn)
366 handler.call(self, arg1);
367 else {
368 var len = handler.length;
369 var listeners = arrayClone(handler, len);
370 for (var i = 0; i < len; ++i)
371 listeners[i].call(self, arg1);
372 }
373}
374function emitTwo(handler, isFn, self, arg1, arg2) {
375 if (isFn)
376 handler.call(self, arg1, arg2);
377 else {
378 var len = handler.length;
379 var listeners = arrayClone(handler, len);
380 for (var i = 0; i < len; ++i)
381 listeners[i].call(self, arg1, arg2);
382 }
383}
384function emitThree(handler, isFn, self, arg1, arg2, arg3) {
385 if (isFn)
386 handler.call(self, arg1, arg2, arg3);
387 else {
388 var len = handler.length;
389 var listeners = arrayClone(handler, len);
390 for (var i = 0; i < len; ++i)
391 listeners[i].call(self, arg1, arg2, arg3);
392 }
393}
394
395function emitMany(handler, isFn, self, args) {
396 if (isFn)
397 handler.apply(self, args);
398 else {
399 var len = handler.length;
400 var listeners = arrayClone(handler, len);
401 for (var i = 0; i < len; ++i)
402 listeners[i].apply(self, args);
403 }
404}
405
406EventEmitter.prototype.emit = function emit(type) {
407 var er, handler, len, args, i, events;
408 var doError = (type === 'error');
409
410 events = this._events;
411 if (events)
412 doError = (doError && events.error == null);
413 else if (!doError)
414 return false;
415
416 // If there is no 'error' event listener then throw.
417 if (doError) {
418 if (arguments.length > 1)
419 er = arguments[1];
420 if (er instanceof Error) {
421 throw er; // Unhandled 'error' event
422 } else {
423 // At least give some kind of context to the user
424 var err = new Error('Unhandled "error" event. (' + er + ')');
425 err.context = er;
426 throw err;
427 }
428 return false;
429 }
430
431 handler = events[type];
432
433 if (!handler)
434 return false;
435
436 var isFn = typeof handler === 'function';
437 len = arguments.length;
438 switch (len) {
439 // fast cases
440 case 1:
441 emitNone(handler, isFn, this);
442 break;
443 case 2:
444 emitOne(handler, isFn, this, arguments[1]);
445 break;
446 case 3:
447 emitTwo(handler, isFn, this, arguments[1], arguments[2]);
448 break;
449 case 4:
450 emitThree(handler, isFn, this, arguments[1], arguments[2], arguments[3]);
451 break;
452 // slower
453 default:
454 args = new Array(len - 1);
455 for (i = 1; i < len; i++)
456 args[i - 1] = arguments[i];
457 emitMany(handler, isFn, this, args);
458 }
459
460 return true;
461};
462
463function _addListener(target, type, listener, prepend) {
464 var m;
465 var events;
466 var existing;
467
468 if (typeof listener !== 'function')
469 throw new TypeError('"listener" argument must be a function');
470
471 events = target._events;
472 if (!events) {
473 events = target._events = objectCreate(null);
474 target._eventsCount = 0;
475 } else {
476 // To avoid recursion in the case that type === "newListener"! Before
477 // adding it to the listeners, first emit "newListener".
478 if (events.newListener) {
479 target.emit('newListener', type,
480 listener.listener ? listener.listener : listener);
481
482 // Re-assign `events` because a newListener handler could have caused the
483 // this._events to be assigned to a new object
484 events = target._events;
485 }
486 existing = events[type];
487 }
488
489 if (!existing) {
490 // Optimize the case of one listener. Don't need the extra array object.
491 existing = events[type] = listener;
492 ++target._eventsCount;
493 } else {
494 if (typeof existing === 'function') {
495 // Adding the second element, need to change to array.
496 existing = events[type] =
497 prepend ? [listener, existing] : [existing, listener];
498 } else {
499 // If we've already got an array, just append.
500 if (prepend) {
501 existing.unshift(listener);
502 } else {
503 existing.push(listener);
504 }
505 }
506
507 // Check for listener leak
508 if (!existing.warned) {
509 m = $getMaxListeners(target);
510 if (m && m > 0 && existing.length > m) {
511 existing.warned = true;
512 var w = new Error('Possible EventEmitter memory leak detected. ' +
513 existing.length + ' "' + String(type) + '" listeners ' +
514 'added. Use emitter.setMaxListeners() to ' +
515 'increase limit.');
516 w.name = 'MaxListenersExceededWarning';
517 w.emitter = target;
518 w.type = type;
519 w.count = existing.length;
520 if (typeof console === 'object' && console.warn) {
521 console.warn('%s: %s', w.name, w.message);
522 }
523 }
524 }
525 }
526
527 return target;
528}
529
530EventEmitter.prototype.addListener = function addListener(type, listener) {
531 return _addListener(this, type, listener, false);
532};
533
534EventEmitter.prototype.on = EventEmitter.prototype.addListener;
535
536EventEmitter.prototype.prependListener =
537 function prependListener(type, listener) {
538 return _addListener(this, type, listener, true);
539 };
540
541function onceWrapper() {
542 if (!this.fired) {
543 this.target.removeListener(this.type, this.wrapFn);
544 this.fired = true;
545 switch (arguments.length) {
546 case 0:
547 return this.listener.call(this.target);
548 case 1:
549 return this.listener.call(this.target, arguments[0]);
550 case 2:
551 return this.listener.call(this.target, arguments[0], arguments[1]);
552 case 3:
553 return this.listener.call(this.target, arguments[0], arguments[1],
554 arguments[2]);
555 default:
556 var args = new Array(arguments.length);
557 for (var i = 0; i < args.length; ++i)
558 args[i] = arguments[i];
559 this.listener.apply(this.target, args);
560 }
561 }
562}
563
564function _onceWrap(target, type, listener) {
565 var state = { fired: false, wrapFn: undefined, target: target, type: type, listener: listener };
566 var wrapped = bind.call(onceWrapper, state);
567 wrapped.listener = listener;
568 state.wrapFn = wrapped;
569 return wrapped;
570}
571
572EventEmitter.prototype.once = function once(type, listener) {
573 if (typeof listener !== 'function')
574 throw new TypeError('"listener" argument must be a function');
575 this.on(type, _onceWrap(this, type, listener));
576 return this;
577};
578
579EventEmitter.prototype.prependOnceListener =
580 function prependOnceListener(type, listener) {
581 if (typeof listener !== 'function')
582 throw new TypeError('"listener" argument must be a function');
583 this.prependListener(type, _onceWrap(this, type, listener));
584 return this;
585 };
586
587// Emits a 'removeListener' event if and only if the listener was removed.
588EventEmitter.prototype.removeListener =
589 function removeListener(type, listener) {
590 var list, events, position, i, originalListener;
591
592 if (typeof listener !== 'function')
593 throw new TypeError('"listener" argument must be a function');
594
595 events = this._events;
596 if (!events)
597 return this;
598
599 list = events[type];
600 if (!list)
601 return this;
602
603 if (list === listener || list.listener === listener) {
604 if (--this._eventsCount === 0)
605 this._events = objectCreate(null);
606 else {
607 delete events[type];
608 if (events.removeListener)
609 this.emit('removeListener', type, list.listener || listener);
610 }
611 } else if (typeof list !== 'function') {
612 position = -1;
613
614 for (i = list.length - 1; i >= 0; i--) {
615 if (list[i] === listener || list[i].listener === listener) {
616 originalListener = list[i].listener;
617 position = i;
618 break;
619 }
620 }
621
622 if (position < 0)
623 return this;
624
625 if (position === 0)
626 list.shift();
627 else
628 spliceOne(list, position);
629
630 if (list.length === 1)
631 events[type] = list[0];
632
633 if (events.removeListener)
634 this.emit('removeListener', type, originalListener || listener);
635 }
636
637 return this;
638 };
639
640EventEmitter.prototype.removeAllListeners =
641 function removeAllListeners(type) {
642 var listeners, events, i;
643
644 events = this._events;
645 if (!events)
646 return this;
647
648 // not listening for removeListener, no need to emit
649 if (!events.removeListener) {
650 if (arguments.length === 0) {
651 this._events = objectCreate(null);
652 this._eventsCount = 0;
653 } else if (events[type]) {
654 if (--this._eventsCount === 0)
655 this._events = objectCreate(null);
656 else
657 delete events[type];
658 }
659 return this;
660 }
661
662 // emit removeListener for all listeners on all events
663 if (arguments.length === 0) {
664 var keys = objectKeys(events);
665 var key;
666 for (i = 0; i < keys.length; ++i) {
667 key = keys[i];
668 if (key === 'removeListener') continue;
669 this.removeAllListeners(key);
670 }
671 this.removeAllListeners('removeListener');
672 this._events = objectCreate(null);
673 this._eventsCount = 0;
674 return this;
675 }
676
677 listeners = events[type];
678
679 if (typeof listeners === 'function') {
680 this.removeListener(type, listeners);
681 } else if (listeners) {
682 // LIFO order
683 for (i = listeners.length - 1; i >= 0; i--) {
684 this.removeListener(type, listeners[i]);
685 }
686 }
687
688 return this;
689 };
690
691function _listeners(target, type, unwrap) {
692 var events = target._events;
693
694 if (!events)
695 return [];
696
697 var evlistener = events[type];
698 if (!evlistener)
699 return [];
700
701 if (typeof evlistener === 'function')
702 return unwrap ? [evlistener.listener || evlistener] : [evlistener];
703
704 return unwrap ? unwrapListeners(evlistener) : arrayClone(evlistener, evlistener.length);
705}
706
707EventEmitter.prototype.listeners = function listeners(type) {
708 return _listeners(this, type, true);
709};
710
711EventEmitter.prototype.rawListeners = function rawListeners(type) {
712 return _listeners(this, type, false);
713};
714
715EventEmitter.listenerCount = function(emitter, type) {
716 if (typeof emitter.listenerCount === 'function') {
717 return emitter.listenerCount(type);
718 } else {
719 return listenerCount.call(emitter, type);
720 }
721};
722
723EventEmitter.prototype.listenerCount = listenerCount;
724function listenerCount(type) {
725 var events = this._events;
726
727 if (events) {
728 var evlistener = events[type];
729
730 if (typeof evlistener === 'function') {
731 return 1;
732 } else if (evlistener) {
733 return evlistener.length;
734 }
735 }
736
737 return 0;
738}
739
740EventEmitter.prototype.eventNames = function eventNames() {
741 return this._eventsCount > 0 ? Reflect.ownKeys(this._events) : [];
742};
743
744// About 1.5x faster than the two-arg version of Array#splice().
745function spliceOne(list, index) {
746 for (var i = index, k = i + 1, n = list.length; k < n; i += 1, k += 1)
747 list[i] = list[k];
748 list.pop();
749}
750
751function arrayClone(arr, n) {
752 var copy = new Array(n);
753 for (var i = 0; i < n; ++i)
754 copy[i] = arr[i];
755 return copy;
756}
757
758function unwrapListeners(arr) {
759 var ret = new Array(arr.length);
760 for (var i = 0; i < ret.length; ++i) {
761 ret[i] = arr[i].listener || arr[i];
762 }
763 return ret;
764}
765
766function objectCreatePolyfill(proto) {
767 var F = function() {};
768 F.prototype = proto;
769 return new F;
770}
771function objectKeysPolyfill(obj) {
772 var keys = [];
773 for (var k in obj) if (Object.prototype.hasOwnProperty.call(obj, k)) {
774 keys.push(k);
775 }
776 return k;
777}
778function functionBindPolyfill(context) {
779 var fn = this;
780 return function () {
781 return fn.apply(context, arguments);
782 };
783}
784
785},{}],16:[function(_dereq_,module,exports){
786!function() {
787 var d3 = {
788 version: "3.5.17"
789 };
790 var d3_arraySlice = [].slice, d3_array = function(list) {
791 return d3_arraySlice.call(list);
792 };
793 var d3_document = this.document;
794 function d3_documentElement(node) {
795 return node && (node.ownerDocument || node.document || node).documentElement;
796 }
797 function d3_window(node) {
798 return node && (node.ownerDocument && node.ownerDocument.defaultView || node.document && node || node.defaultView);
799 }
800 if (d3_document) {
801 try {
802 d3_array(d3_document.documentElement.childNodes)[0].nodeType;
803 } catch (e) {
804 d3_array = function(list) {
805 var i = list.length, array = new Array(i);
806 while (i--) array[i] = list[i];
807 return array;
808 };
809 }
810 }
811 if (!Date.now) Date.now = function() {
812 return +new Date();
813 };
814 if (d3_document) {
815 try {
816 d3_document.createElement("DIV").style.setProperty("opacity", 0, "");
817 } catch (error) {
818 var d3_element_prototype = this.Element.prototype, d3_element_setAttribute = d3_element_prototype.setAttribute, d3_element_setAttributeNS = d3_element_prototype.setAttributeNS, d3_style_prototype = this.CSSStyleDeclaration.prototype, d3_style_setProperty = d3_style_prototype.setProperty;
819 d3_element_prototype.setAttribute = function(name, value) {
820 d3_element_setAttribute.call(this, name, value + "");
821 };
822 d3_element_prototype.setAttributeNS = function(space, local, value) {
823 d3_element_setAttributeNS.call(this, space, local, value + "");
824 };
825 d3_style_prototype.setProperty = function(name, value, priority) {
826 d3_style_setProperty.call(this, name, value + "", priority);
827 };
828 }
829 }
830 d3.ascending = d3_ascending;
831 function d3_ascending(a, b) {
832 return a < b ? -1 : a > b ? 1 : a >= b ? 0 : NaN;
833 }
834 d3.descending = function(a, b) {
835 return b < a ? -1 : b > a ? 1 : b >= a ? 0 : NaN;
836 };
837 d3.min = function(array, f) {
838 var i = -1, n = array.length, a, b;
839 if (arguments.length === 1) {
840 while (++i < n) if ((b = array[i]) != null && b >= b) {
841 a = b;
842 break;
843 }
844 while (++i < n) if ((b = array[i]) != null && a > b) a = b;
845 } else {
846 while (++i < n) if ((b = f.call(array, array[i], i)) != null && b >= b) {
847 a = b;
848 break;
849 }
850 while (++i < n) if ((b = f.call(array, array[i], i)) != null && a > b) a = b;
851 }
852 return a;
853 };
854 d3.max = function(array, f) {
855 var i = -1, n = array.length, a, b;
856 if (arguments.length === 1) {
857 while (++i < n) if ((b = array[i]) != null && b >= b) {
858 a = b;
859 break;
860 }
861 while (++i < n) if ((b = array[i]) != null && b > a) a = b;
862 } else {
863 while (++i < n) if ((b = f.call(array, array[i], i)) != null && b >= b) {
864 a = b;
865 break;
866 }
867 while (++i < n) if ((b = f.call(array, array[i], i)) != null && b > a) a = b;
868 }
869 return a;
870 };
871 d3.extent = function(array, f) {
872 var i = -1, n = array.length, a, b, c;
873 if (arguments.length === 1) {
874 while (++i < n) if ((b = array[i]) != null && b >= b) {
875 a = c = b;
876 break;
877 }
878 while (++i < n) if ((b = array[i]) != null) {
879 if (a > b) a = b;
880 if (c < b) c = b;
881 }
882 } else {
883 while (++i < n) if ((b = f.call(array, array[i], i)) != null && b >= b) {
884 a = c = b;
885 break;
886 }
887 while (++i < n) if ((b = f.call(array, array[i], i)) != null) {
888 if (a > b) a = b;
889 if (c < b) c = b;
890 }
891 }
892 return [ a, c ];
893 };
894 function d3_number(x) {
895 return x === null ? NaN : +x;
896 }
897 function d3_numeric(x) {
898 return !isNaN(x);
899 }
900 d3.sum = function(array, f) {
901 var s = 0, n = array.length, a, i = -1;
902 if (arguments.length === 1) {
903 while (++i < n) if (d3_numeric(a = +array[i])) s += a;
904 } else {
905 while (++i < n) if (d3_numeric(a = +f.call(array, array[i], i))) s += a;
906 }
907 return s;
908 };
909 d3.mean = function(array, f) {
910 var s = 0, n = array.length, a, i = -1, j = n;
911 if (arguments.length === 1) {
912 while (++i < n) if (d3_numeric(a = d3_number(array[i]))) s += a; else --j;
913 } else {
914 while (++i < n) if (d3_numeric(a = d3_number(f.call(array, array[i], i)))) s += a; else --j;
915 }
916 if (j) return s / j;
917 };
918 d3.quantile = function(values, p) {
919 var H = (values.length - 1) * p + 1, h = Math.floor(H), v = +values[h - 1], e = H - h;
920 return e ? v + e * (values[h] - v) : v;
921 };
922 d3.median = function(array, f) {
923 var numbers = [], n = array.length, a, i = -1;
924 if (arguments.length === 1) {
925 while (++i < n) if (d3_numeric(a = d3_number(array[i]))) numbers.push(a);
926 } else {
927 while (++i < n) if (d3_numeric(a = d3_number(f.call(array, array[i], i)))) numbers.push(a);
928 }
929 if (numbers.length) return d3.quantile(numbers.sort(d3_ascending), .5);
930 };
931 d3.variance = function(array, f) {
932 var n = array.length, m = 0, a, d, s = 0, i = -1, j = 0;
933 if (arguments.length === 1) {
934 while (++i < n) {
935 if (d3_numeric(a = d3_number(array[i]))) {
936 d = a - m;
937 m += d / ++j;
938 s += d * (a - m);
939 }
940 }
941 } else {
942 while (++i < n) {
943 if (d3_numeric(a = d3_number(f.call(array, array[i], i)))) {
944 d = a - m;
945 m += d / ++j;
946 s += d * (a - m);
947 }
948 }
949 }
950 if (j > 1) return s / (j - 1);
951 };
952 d3.deviation = function() {
953 var v = d3.variance.apply(this, arguments);
954 return v ? Math.sqrt(v) : v;
955 };
956 function d3_bisector(compare) {
957 return {
958 left: function(a, x, lo, hi) {
959 if (arguments.length < 3) lo = 0;
960 if (arguments.length < 4) hi = a.length;
961 while (lo < hi) {
962 var mid = lo + hi >>> 1;
963 if (compare(a[mid], x) < 0) lo = mid + 1; else hi = mid;
964 }
965 return lo;
966 },
967 right: function(a, x, lo, hi) {
968 if (arguments.length < 3) lo = 0;
969 if (arguments.length < 4) hi = a.length;
970 while (lo < hi) {
971 var mid = lo + hi >>> 1;
972 if (compare(a[mid], x) > 0) hi = mid; else lo = mid + 1;
973 }
974 return lo;
975 }
976 };
977 }
978 var d3_bisect = d3_bisector(d3_ascending);
979 d3.bisectLeft = d3_bisect.left;
980 d3.bisect = d3.bisectRight = d3_bisect.right;
981 d3.bisector = function(f) {
982 return d3_bisector(f.length === 1 ? function(d, x) {
983 return d3_ascending(f(d), x);
984 } : f);
985 };
986 d3.shuffle = function(array, i0, i1) {
987 if ((m = arguments.length) < 3) {
988 i1 = array.length;
989 if (m < 2) i0 = 0;
990 }
991 var m = i1 - i0, t, i;
992 while (m) {
993 i = Math.random() * m-- | 0;
994 t = array[m + i0], array[m + i0] = array[i + i0], array[i + i0] = t;
995 }
996 return array;
997 };
998 d3.permute = function(array, indexes) {
999 var i = indexes.length, permutes = new Array(i);
1000 while (i--) permutes[i] = array[indexes[i]];
1001 return permutes;
1002 };
1003 d3.pairs = function(array) {
1004 var i = 0, n = array.length - 1, p0, p1 = array[0], pairs = new Array(n < 0 ? 0 : n);
1005 while (i < n) pairs[i] = [ p0 = p1, p1 = array[++i] ];
1006 return pairs;
1007 };
1008 d3.transpose = function(matrix) {
1009 if (!(n = matrix.length)) return [];
1010 for (var i = -1, m = d3.min(matrix, d3_transposeLength), transpose = new Array(m); ++i < m; ) {
1011 for (var j = -1, n, row = transpose[i] = new Array(n); ++j < n; ) {
1012 row[j] = matrix[j][i];
1013 }
1014 }
1015 return transpose;
1016 };
1017 function d3_transposeLength(d) {
1018 return d.length;
1019 }
1020 d3.zip = function() {
1021 return d3.transpose(arguments);
1022 };
1023 d3.keys = function(map) {
1024 var keys = [];
1025 for (var key in map) keys.push(key);
1026 return keys;
1027 };
1028 d3.values = function(map) {
1029 var values = [];
1030 for (var key in map) values.push(map[key]);
1031 return values;
1032 };
1033 d3.entries = function(map) {
1034 var entries = [];
1035 for (var key in map) entries.push({
1036 key: key,
1037 value: map[key]
1038 });
1039 return entries;
1040 };
1041 d3.merge = function(arrays) {
1042 var n = arrays.length, m, i = -1, j = 0, merged, array;
1043 while (++i < n) j += arrays[i].length;
1044 merged = new Array(j);
1045 while (--n >= 0) {
1046 array = arrays[n];
1047 m = array.length;
1048 while (--m >= 0) {
1049 merged[--j] = array[m];
1050 }
1051 }
1052 return merged;
1053 };
1054 var abs = Math.abs;
1055 d3.range = function(start, stop, step) {
1056 if (arguments.length < 3) {
1057 step = 1;
1058 if (arguments.length < 2) {
1059 stop = start;
1060 start = 0;
1061 }
1062 }
1063 if ((stop - start) / step === Infinity) throw new Error("infinite range");
1064 var range = [], k = d3_range_integerScale(abs(step)), i = -1, j;
1065 start *= k, stop *= k, step *= k;
1066 if (step < 0) while ((j = start + step * ++i) > stop) range.push(j / k); else while ((j = start + step * ++i) < stop) range.push(j / k);
1067 return range;
1068 };
1069 function d3_range_integerScale(x) {
1070 var k = 1;
1071 while (x * k % 1) k *= 10;
1072 return k;
1073 }
1074 function d3_class(ctor, properties) {
1075 for (var key in properties) {
1076 Object.defineProperty(ctor.prototype, key, {
1077 value: properties[key],
1078 enumerable: false
1079 });
1080 }
1081 }
1082 d3.map = function(object, f) {
1083 var map = new d3_Map();
1084 if (object instanceof d3_Map) {
1085 object.forEach(function(key, value) {
1086 map.set(key, value);
1087 });
1088 } else if (Array.isArray(object)) {
1089 var i = -1, n = object.length, o;
1090 if (arguments.length === 1) while (++i < n) map.set(i, object[i]); else while (++i < n) map.set(f.call(object, o = object[i], i), o);
1091 } else {
1092 for (var key in object) map.set(key, object[key]);
1093 }
1094 return map;
1095 };
1096 function d3_Map() {
1097 this._ = Object.create(null);
1098 }
1099 var d3_map_proto = "__proto__", d3_map_zero = "\x00";
1100 d3_class(d3_Map, {
1101 has: d3_map_has,
1102 get: function(key) {
1103 return this._[d3_map_escape(key)];
1104 },
1105 set: function(key, value) {
1106 return this._[d3_map_escape(key)] = value;
1107 },
1108 remove: d3_map_remove,
1109 keys: d3_map_keys,
1110 values: function() {
1111 var values = [];
1112 for (var key in this._) values.push(this._[key]);
1113 return values;
1114 },
1115 entries: function() {
1116 var entries = [];
1117 for (var key in this._) entries.push({
1118 key: d3_map_unescape(key),
1119 value: this._[key]
1120 });
1121 return entries;
1122 },
1123 size: d3_map_size,
1124 empty: d3_map_empty,
1125 forEach: function(f) {
1126 for (var key in this._) f.call(this, d3_map_unescape(key), this._[key]);
1127 }
1128 });
1129 function d3_map_escape(key) {
1130 return (key += "") === d3_map_proto || key[0] === d3_map_zero ? d3_map_zero + key : key;
1131 }
1132 function d3_map_unescape(key) {
1133 return (key += "")[0] === d3_map_zero ? key.slice(1) : key;
1134 }
1135 function d3_map_has(key) {
1136 return d3_map_escape(key) in this._;
1137 }
1138 function d3_map_remove(key) {
1139 return (key = d3_map_escape(key)) in this._ && delete this._[key];
1140 }
1141 function d3_map_keys() {
1142 var keys = [];
1143 for (var key in this._) keys.push(d3_map_unescape(key));
1144 return keys;
1145 }
1146 function d3_map_size() {
1147 var size = 0;
1148 for (var key in this._) ++size;
1149 return size;
1150 }
1151 function d3_map_empty() {
1152 for (var key in this._) return false;
1153 return true;
1154 }
1155 d3.nest = function() {
1156 var nest = {}, keys = [], sortKeys = [], sortValues, rollup;
1157 function map(mapType, array, depth) {
1158 if (depth >= keys.length) return rollup ? rollup.call(nest, array) : sortValues ? array.sort(sortValues) : array;
1159 var i = -1, n = array.length, key = keys[depth++], keyValue, object, setter, valuesByKey = new d3_Map(), values;
1160 while (++i < n) {
1161 if (values = valuesByKey.get(keyValue = key(object = array[i]))) {
1162 values.push(object);
1163 } else {
1164 valuesByKey.set(keyValue, [ object ]);
1165 }
1166 }
1167 if (mapType) {
1168 object = mapType();
1169 setter = function(keyValue, values) {
1170 object.set(keyValue, map(mapType, values, depth));
1171 };
1172 } else {
1173 object = {};
1174 setter = function(keyValue, values) {
1175 object[keyValue] = map(mapType, values, depth);
1176 };
1177 }
1178 valuesByKey.forEach(setter);
1179 return object;
1180 }
1181 function entries(map, depth) {
1182 if (depth >= keys.length) return map;
1183 var array = [], sortKey = sortKeys[depth++];
1184 map.forEach(function(key, keyMap) {
1185 array.push({
1186 key: key,
1187 values: entries(keyMap, depth)
1188 });
1189 });
1190 return sortKey ? array.sort(function(a, b) {
1191 return sortKey(a.key, b.key);
1192 }) : array;
1193 }
1194 nest.map = function(array, mapType) {
1195 return map(mapType, array, 0);
1196 };
1197 nest.entries = function(array) {
1198 return entries(map(d3.map, array, 0), 0);
1199 };
1200 nest.key = function(d) {
1201 keys.push(d);
1202 return nest;
1203 };
1204 nest.sortKeys = function(order) {
1205 sortKeys[keys.length - 1] = order;
1206 return nest;
1207 };
1208 nest.sortValues = function(order) {
1209 sortValues = order;
1210 return nest;
1211 };
1212 nest.rollup = function(f) {
1213 rollup = f;
1214 return nest;
1215 };
1216 return nest;
1217 };
1218 d3.set = function(array) {
1219 var set = new d3_Set();
1220 if (array) for (var i = 0, n = array.length; i < n; ++i) set.add(array[i]);
1221 return set;
1222 };
1223 function d3_Set() {
1224 this._ = Object.create(null);
1225 }
1226 d3_class(d3_Set, {
1227 has: d3_map_has,
1228 add: function(key) {
1229 this._[d3_map_escape(key += "")] = true;
1230 return key;
1231 },
1232 remove: d3_map_remove,
1233 values: d3_map_keys,
1234 size: d3_map_size,
1235 empty: d3_map_empty,
1236 forEach: function(f) {
1237 for (var key in this._) f.call(this, d3_map_unescape(key));
1238 }
1239 });
1240 d3.behavior = {};
1241 function d3_identity(d) {
1242 return d;
1243 }
1244 d3.rebind = function(target, source) {
1245 var i = 1, n = arguments.length, method;
1246 while (++i < n) target[method = arguments[i]] = d3_rebind(target, source, source[method]);
1247 return target;
1248 };
1249 function d3_rebind(target, source, method) {
1250 return function() {
1251 var value = method.apply(source, arguments);
1252 return value === source ? target : value;
1253 };
1254 }
1255 function d3_vendorSymbol(object, name) {
1256 if (name in object) return name;
1257 name = name.charAt(0).toUpperCase() + name.slice(1);
1258 for (var i = 0, n = d3_vendorPrefixes.length; i < n; ++i) {
1259 var prefixName = d3_vendorPrefixes[i] + name;
1260 if (prefixName in object) return prefixName;
1261 }
1262 }
1263 var d3_vendorPrefixes = [ "webkit", "ms", "moz", "Moz", "o", "O" ];
1264 function d3_noop() {}
1265 d3.dispatch = function() {
1266 var dispatch = new d3_dispatch(), i = -1, n = arguments.length;
1267 while (++i < n) dispatch[arguments[i]] = d3_dispatch_event(dispatch);
1268 return dispatch;
1269 };
1270 function d3_dispatch() {}
1271 d3_dispatch.prototype.on = function(type, listener) {
1272 var i = type.indexOf("."), name = "";
1273 if (i >= 0) {
1274 name = type.slice(i + 1);
1275 type = type.slice(0, i);
1276 }
1277 if (type) return arguments.length < 2 ? this[type].on(name) : this[type].on(name, listener);
1278 if (arguments.length === 2) {
1279 if (listener == null) for (type in this) {
1280 if (this.hasOwnProperty(type)) this[type].on(name, null);
1281 }
1282 return this;
1283 }
1284 };
1285 function d3_dispatch_event(dispatch) {
1286 var listeners = [], listenerByName = new d3_Map();
1287 function event() {
1288 var z = listeners, i = -1, n = z.length, l;
1289 while (++i < n) if (l = z[i].on) l.apply(this, arguments);
1290 return dispatch;
1291 }
1292 event.on = function(name, listener) {
1293 var l = listenerByName.get(name), i;
1294 if (arguments.length < 2) return l && l.on;
1295 if (l) {
1296 l.on = null;
1297 listeners = listeners.slice(0, i = listeners.indexOf(l)).concat(listeners.slice(i + 1));
1298 listenerByName.remove(name);
1299 }
1300 if (listener) listeners.push(listenerByName.set(name, {
1301 on: listener
1302 }));
1303 return dispatch;
1304 };
1305 return event;
1306 }
1307 d3.event = null;
1308 function d3_eventPreventDefault() {
1309 d3.event.preventDefault();
1310 }
1311 function d3_eventSource() {
1312 var e = d3.event, s;
1313 while (s = e.sourceEvent) e = s;
1314 return e;
1315 }
1316 function d3_eventDispatch(target) {
1317 var dispatch = new d3_dispatch(), i = 0, n = arguments.length;
1318 while (++i < n) dispatch[arguments[i]] = d3_dispatch_event(dispatch);
1319 dispatch.of = function(thiz, argumentz) {
1320 return function(e1) {
1321 try {
1322 var e0 = e1.sourceEvent = d3.event;
1323 e1.target = target;
1324 d3.event = e1;
1325 dispatch[e1.type].apply(thiz, argumentz);
1326 } finally {
1327 d3.event = e0;
1328 }
1329 };
1330 };
1331 return dispatch;
1332 }
1333 d3.requote = function(s) {
1334 return s.replace(d3_requote_re, "\\$&");
1335 };
1336 var d3_requote_re = /[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g;
1337 var d3_subclass = {}.__proto__ ? function(object, prototype) {
1338 object.__proto__ = prototype;
1339 } : function(object, prototype) {
1340 for (var property in prototype) object[property] = prototype[property];
1341 };
1342 function d3_selection(groups) {
1343 d3_subclass(groups, d3_selectionPrototype);
1344 return groups;
1345 }
1346 var d3_select = function(s, n) {
1347 return n.querySelector(s);
1348 }, d3_selectAll = function(s, n) {
1349 return n.querySelectorAll(s);
1350 }, d3_selectMatches = function(n, s) {
1351 var d3_selectMatcher = n.matches || n[d3_vendorSymbol(n, "matchesSelector")];
1352 d3_selectMatches = function(n, s) {
1353 return d3_selectMatcher.call(n, s);
1354 };
1355 return d3_selectMatches(n, s);
1356 };
1357 if (typeof Sizzle === "function") {
1358 d3_select = function(s, n) {
1359 return Sizzle(s, n)[0] || null;
1360 };
1361 d3_selectAll = Sizzle;
1362 d3_selectMatches = Sizzle.matchesSelector;
1363 }
1364 d3.selection = function() {
1365 return d3.select(d3_document.documentElement);
1366 };
1367 var d3_selectionPrototype = d3.selection.prototype = [];
1368 d3_selectionPrototype.select = function(selector) {
1369 var subgroups = [], subgroup, subnode, group, node;
1370 selector = d3_selection_selector(selector);
1371 for (var j = -1, m = this.length; ++j < m; ) {
1372 subgroups.push(subgroup = []);
1373 subgroup.parentNode = (group = this[j]).parentNode;
1374 for (var i = -1, n = group.length; ++i < n; ) {
1375 if (node = group[i]) {
1376 subgroup.push(subnode = selector.call(node, node.__data__, i, j));
1377 if (subnode && "__data__" in node) subnode.__data__ = node.__data__;
1378 } else {
1379 subgroup.push(null);
1380 }
1381 }
1382 }
1383 return d3_selection(subgroups);
1384 };
1385 function d3_selection_selector(selector) {
1386 return typeof selector === "function" ? selector : function() {
1387 return d3_select(selector, this);
1388 };
1389 }
1390 d3_selectionPrototype.selectAll = function(selector) {
1391 var subgroups = [], subgroup, node;
1392 selector = d3_selection_selectorAll(selector);
1393 for (var j = -1, m = this.length; ++j < m; ) {
1394 for (var group = this[j], i = -1, n = group.length; ++i < n; ) {
1395 if (node = group[i]) {
1396 subgroups.push(subgroup = d3_array(selector.call(node, node.__data__, i, j)));
1397 subgroup.parentNode = node;
1398 }
1399 }
1400 }
1401 return d3_selection(subgroups);
1402 };
1403 function d3_selection_selectorAll(selector) {
1404 return typeof selector === "function" ? selector : function() {
1405 return d3_selectAll(selector, this);
1406 };
1407 }
1408 var d3_nsXhtml = "http://www.w3.org/1999/xhtml";
1409 var d3_nsPrefix = {
1410 svg: "http://www.w3.org/2000/svg",
1411 xhtml: d3_nsXhtml,
1412 xlink: "http://www.w3.org/1999/xlink",
1413 xml: "http://www.w3.org/XML/1998/namespace",
1414 xmlns: "http://www.w3.org/2000/xmlns/"
1415 };
1416 d3.ns = {
1417 prefix: d3_nsPrefix,
1418 qualify: function(name) {
1419 var i = name.indexOf(":"), prefix = name;
1420 if (i >= 0 && (prefix = name.slice(0, i)) !== "xmlns") name = name.slice(i + 1);
1421 return d3_nsPrefix.hasOwnProperty(prefix) ? {
1422 space: d3_nsPrefix[prefix],
1423 local: name
1424 } : name;
1425 }
1426 };
1427 d3_selectionPrototype.attr = function(name, value) {
1428 if (arguments.length < 2) {
1429 if (typeof name === "string") {
1430 var node = this.node();
1431 name = d3.ns.qualify(name);
1432 return name.local ? node.getAttributeNS(name.space, name.local) : node.getAttribute(name);
1433 }
1434 for (value in name) this.each(d3_selection_attr(value, name[value]));
1435 return this;
1436 }
1437 return this.each(d3_selection_attr(name, value));
1438 };
1439 function d3_selection_attr(name, value) {
1440 name = d3.ns.qualify(name);
1441 function attrNull() {
1442 this.removeAttribute(name);
1443 }
1444 function attrNullNS() {
1445 this.removeAttributeNS(name.space, name.local);
1446 }
1447 function attrConstant() {
1448 this.setAttribute(name, value);
1449 }
1450 function attrConstantNS() {
1451 this.setAttributeNS(name.space, name.local, value);
1452 }
1453 function attrFunction() {
1454 var x = value.apply(this, arguments);
1455 if (x == null) this.removeAttribute(name); else this.setAttribute(name, x);
1456 }
1457 function attrFunctionNS() {
1458 var x = value.apply(this, arguments);
1459 if (x == null) this.removeAttributeNS(name.space, name.local); else this.setAttributeNS(name.space, name.local, x);
1460 }
1461 return value == null ? name.local ? attrNullNS : attrNull : typeof value === "function" ? name.local ? attrFunctionNS : attrFunction : name.local ? attrConstantNS : attrConstant;
1462 }
1463 function d3_collapse(s) {
1464 return s.trim().replace(/\s+/g, " ");
1465 }
1466 d3_selectionPrototype.classed = function(name, value) {
1467 if (arguments.length < 2) {
1468 if (typeof name === "string") {
1469 var node = this.node(), n = (name = d3_selection_classes(name)).length, i = -1;
1470 if (value = node.classList) {
1471 while (++i < n) if (!value.contains(name[i])) return false;
1472 } else {
1473 value = node.getAttribute("class");
1474 while (++i < n) if (!d3_selection_classedRe(name[i]).test(value)) return false;
1475 }
1476 return true;
1477 }
1478 for (value in name) this.each(d3_selection_classed(value, name[value]));
1479 return this;
1480 }
1481 return this.each(d3_selection_classed(name, value));
1482 };
1483 function d3_selection_classedRe(name) {
1484 return new RegExp("(?:^|\\s+)" + d3.requote(name) + "(?:\\s+|$)", "g");
1485 }
1486 function d3_selection_classes(name) {
1487 return (name + "").trim().split(/^|\s+/);
1488 }
1489 function d3_selection_classed(name, value) {
1490 name = d3_selection_classes(name).map(d3_selection_classedName);
1491 var n = name.length;
1492 function classedConstant() {
1493 var i = -1;
1494 while (++i < n) name[i](this, value);
1495 }
1496 function classedFunction() {
1497 var i = -1, x = value.apply(this, arguments);
1498 while (++i < n) name[i](this, x);
1499 }
1500 return typeof value === "function" ? classedFunction : classedConstant;
1501 }
1502 function d3_selection_classedName(name) {
1503 var re = d3_selection_classedRe(name);
1504 return function(node, value) {
1505 if (c = node.classList) return value ? c.add(name) : c.remove(name);
1506 var c = node.getAttribute("class") || "";
1507 if (value) {
1508 re.lastIndex = 0;
1509 if (!re.test(c)) node.setAttribute("class", d3_collapse(c + " " + name));
1510 } else {
1511 node.setAttribute("class", d3_collapse(c.replace(re, " ")));
1512 }
1513 };
1514 }
1515 d3_selectionPrototype.style = function(name, value, priority) {
1516 var n = arguments.length;
1517 if (n < 3) {
1518 if (typeof name !== "string") {
1519 if (n < 2) value = "";
1520 for (priority in name) this.each(d3_selection_style(priority, name[priority], value));
1521 return this;
1522 }
1523 if (n < 2) {
1524 var node = this.node();
1525 return d3_window(node).getComputedStyle(node, null).getPropertyValue(name);
1526 }
1527 priority = "";
1528 }
1529 return this.each(d3_selection_style(name, value, priority));
1530 };
1531 function d3_selection_style(name, value, priority) {
1532 function styleNull() {
1533 this.style.removeProperty(name);
1534 }
1535 function styleConstant() {
1536 this.style.setProperty(name, value, priority);
1537 }
1538 function styleFunction() {
1539 var x = value.apply(this, arguments);
1540 if (x == null) this.style.removeProperty(name); else this.style.setProperty(name, x, priority);
1541 }
1542 return value == null ? styleNull : typeof value === "function" ? styleFunction : styleConstant;
1543 }
1544 d3_selectionPrototype.property = function(name, value) {
1545 if (arguments.length < 2) {
1546 if (typeof name === "string") return this.node()[name];
1547 for (value in name) this.each(d3_selection_property(value, name[value]));
1548 return this;
1549 }
1550 return this.each(d3_selection_property(name, value));
1551 };
1552 function d3_selection_property(name, value) {
1553 function propertyNull() {
1554 delete this[name];
1555 }
1556 function propertyConstant() {
1557 this[name] = value;
1558 }
1559 function propertyFunction() {
1560 var x = value.apply(this, arguments);
1561 if (x == null) delete this[name]; else this[name] = x;
1562 }
1563 return value == null ? propertyNull : typeof value === "function" ? propertyFunction : propertyConstant;
1564 }
1565 d3_selectionPrototype.text = function(value) {
1566 return arguments.length ? this.each(typeof value === "function" ? function() {
1567 var v = value.apply(this, arguments);
1568 this.textContent = v == null ? "" : v;
1569 } : value == null ? function() {
1570 this.textContent = "";
1571 } : function() {
1572 this.textContent = value;
1573 }) : this.node().textContent;
1574 };
1575 d3_selectionPrototype.html = function(value) {
1576 return arguments.length ? this.each(typeof value === "function" ? function() {
1577 var v = value.apply(this, arguments);
1578 this.innerHTML = v == null ? "" : v;
1579 } : value == null ? function() {
1580 this.innerHTML = "";
1581 } : function() {
1582 this.innerHTML = value;
1583 }) : this.node().innerHTML;
1584 };
1585 d3_selectionPrototype.append = function(name) {
1586 name = d3_selection_creator(name);
1587 return this.select(function() {
1588 return this.appendChild(name.apply(this, arguments));
1589 });
1590 };
1591 function d3_selection_creator(name) {
1592 function create() {
1593 var document = this.ownerDocument, namespace = this.namespaceURI;
1594 return namespace === d3_nsXhtml && document.documentElement.namespaceURI === d3_nsXhtml ? document.createElement(name) : document.createElementNS(namespace, name);
1595 }
1596 function createNS() {
1597 return this.ownerDocument.createElementNS(name.space, name.local);
1598 }
1599 return typeof name === "function" ? name : (name = d3.ns.qualify(name)).local ? createNS : create;
1600 }
1601 d3_selectionPrototype.insert = function(name, before) {
1602 name = d3_selection_creator(name);
1603 before = d3_selection_selector(before);
1604 return this.select(function() {
1605 return this.insertBefore(name.apply(this, arguments), before.apply(this, arguments) || null);
1606 });
1607 };
1608 d3_selectionPrototype.remove = function() {
1609 return this.each(d3_selectionRemove);
1610 };
1611 function d3_selectionRemove() {
1612 var parent = this.parentNode;
1613 if (parent) parent.removeChild(this);
1614 }
1615 d3_selectionPrototype.data = function(value, key) {
1616 var i = -1, n = this.length, group, node;
1617 if (!arguments.length) {
1618 value = new Array(n = (group = this[0]).length);
1619 while (++i < n) {
1620 if (node = group[i]) {
1621 value[i] = node.__data__;
1622 }
1623 }
1624 return value;
1625 }
1626 function bind(group, groupData) {
1627 var i, n = group.length, m = groupData.length, n0 = Math.min(n, m), updateNodes = new Array(m), enterNodes = new Array(m), exitNodes = new Array(n), node, nodeData;
1628 if (key) {
1629 var nodeByKeyValue = new d3_Map(), keyValues = new Array(n), keyValue;
1630 for (i = -1; ++i < n; ) {
1631 if (node = group[i]) {
1632 if (nodeByKeyValue.has(keyValue = key.call(node, node.__data__, i))) {
1633 exitNodes[i] = node;
1634 } else {
1635 nodeByKeyValue.set(keyValue, node);
1636 }
1637 keyValues[i] = keyValue;
1638 }
1639 }
1640 for (i = -1; ++i < m; ) {
1641 if (!(node = nodeByKeyValue.get(keyValue = key.call(groupData, nodeData = groupData[i], i)))) {
1642 enterNodes[i] = d3_selection_dataNode(nodeData);
1643 } else if (node !== true) {
1644 updateNodes[i] = node;
1645 node.__data__ = nodeData;
1646 }
1647 nodeByKeyValue.set(keyValue, true);
1648 }
1649 for (i = -1; ++i < n; ) {
1650 if (i in keyValues && nodeByKeyValue.get(keyValues[i]) !== true) {
1651 exitNodes[i] = group[i];
1652 }
1653 }
1654 } else {
1655 for (i = -1; ++i < n0; ) {
1656 node = group[i];
1657 nodeData = groupData[i];
1658 if (node) {
1659 node.__data__ = nodeData;
1660 updateNodes[i] = node;
1661 } else {
1662 enterNodes[i] = d3_selection_dataNode(nodeData);
1663 }
1664 }
1665 for (;i < m; ++i) {
1666 enterNodes[i] = d3_selection_dataNode(groupData[i]);
1667 }
1668 for (;i < n; ++i) {
1669 exitNodes[i] = group[i];
1670 }
1671 }
1672 enterNodes.update = updateNodes;
1673 enterNodes.parentNode = updateNodes.parentNode = exitNodes.parentNode = group.parentNode;
1674 enter.push(enterNodes);
1675 update.push(updateNodes);
1676 exit.push(exitNodes);
1677 }
1678 var enter = d3_selection_enter([]), update = d3_selection([]), exit = d3_selection([]);
1679 if (typeof value === "function") {
1680 while (++i < n) {
1681 bind(group = this[i], value.call(group, group.parentNode.__data__, i));
1682 }
1683 } else {
1684 while (++i < n) {
1685 bind(group = this[i], value);
1686 }
1687 }
1688 update.enter = function() {
1689 return enter;
1690 };
1691 update.exit = function() {
1692 return exit;
1693 };
1694 return update;
1695 };
1696 function d3_selection_dataNode(data) {
1697 return {
1698 __data__: data
1699 };
1700 }
1701 d3_selectionPrototype.datum = function(value) {
1702 return arguments.length ? this.property("__data__", value) : this.property("__data__");
1703 };
1704 d3_selectionPrototype.filter = function(filter) {
1705 var subgroups = [], subgroup, group, node;
1706 if (typeof filter !== "function") filter = d3_selection_filter(filter);
1707 for (var j = 0, m = this.length; j < m; j++) {
1708 subgroups.push(subgroup = []);
1709 subgroup.parentNode = (group = this[j]).parentNode;
1710 for (var i = 0, n = group.length; i < n; i++) {
1711 if ((node = group[i]) && filter.call(node, node.__data__, i, j)) {
1712 subgroup.push(node);
1713 }
1714 }
1715 }
1716 return d3_selection(subgroups);
1717 };
1718 function d3_selection_filter(selector) {
1719 return function() {
1720 return d3_selectMatches(this, selector);
1721 };
1722 }
1723 d3_selectionPrototype.order = function() {
1724 for (var j = -1, m = this.length; ++j < m; ) {
1725 for (var group = this[j], i = group.length - 1, next = group[i], node; --i >= 0; ) {
1726 if (node = group[i]) {
1727 if (next && next !== node.nextSibling) next.parentNode.insertBefore(node, next);
1728 next = node;
1729 }
1730 }
1731 }
1732 return this;
1733 };
1734 d3_selectionPrototype.sort = function(comparator) {
1735 comparator = d3_selection_sortComparator.apply(this, arguments);
1736 for (var j = -1, m = this.length; ++j < m; ) this[j].sort(comparator);
1737 return this.order();
1738 };
1739 function d3_selection_sortComparator(comparator) {
1740 if (!arguments.length) comparator = d3_ascending;
1741 return function(a, b) {
1742 return a && b ? comparator(a.__data__, b.__data__) : !a - !b;
1743 };
1744 }
1745 d3_selectionPrototype.each = function(callback) {
1746 return d3_selection_each(this, function(node, i, j) {
1747 callback.call(node, node.__data__, i, j);
1748 });
1749 };
1750 function d3_selection_each(groups, callback) {
1751 for (var j = 0, m = groups.length; j < m; j++) {
1752 for (var group = groups[j], i = 0, n = group.length, node; i < n; i++) {
1753 if (node = group[i]) callback(node, i, j);
1754 }
1755 }
1756 return groups;
1757 }
1758 d3_selectionPrototype.call = function(callback) {
1759 var args = d3_array(arguments);
1760 callback.apply(args[0] = this, args);
1761 return this;
1762 };
1763 d3_selectionPrototype.empty = function() {
1764 return !this.node();
1765 };
1766 d3_selectionPrototype.node = function() {
1767 for (var j = 0, m = this.length; j < m; j++) {
1768 for (var group = this[j], i = 0, n = group.length; i < n; i++) {
1769 var node = group[i];
1770 if (node) return node;
1771 }
1772 }
1773 return null;
1774 };
1775 d3_selectionPrototype.size = function() {
1776 var n = 0;
1777 d3_selection_each(this, function() {
1778 ++n;
1779 });
1780 return n;
1781 };
1782 function d3_selection_enter(selection) {
1783 d3_subclass(selection, d3_selection_enterPrototype);
1784 return selection;
1785 }
1786 var d3_selection_enterPrototype = [];
1787 d3.selection.enter = d3_selection_enter;
1788 d3.selection.enter.prototype = d3_selection_enterPrototype;
1789 d3_selection_enterPrototype.append = d3_selectionPrototype.append;
1790 d3_selection_enterPrototype.empty = d3_selectionPrototype.empty;
1791 d3_selection_enterPrototype.node = d3_selectionPrototype.node;
1792 d3_selection_enterPrototype.call = d3_selectionPrototype.call;
1793 d3_selection_enterPrototype.size = d3_selectionPrototype.size;
1794 d3_selection_enterPrototype.select = function(selector) {
1795 var subgroups = [], subgroup, subnode, upgroup, group, node;
1796 for (var j = -1, m = this.length; ++j < m; ) {
1797 upgroup = (group = this[j]).update;
1798 subgroups.push(subgroup = []);
1799 subgroup.parentNode = group.parentNode;
1800 for (var i = -1, n = group.length; ++i < n; ) {
1801 if (node = group[i]) {
1802 subgroup.push(upgroup[i] = subnode = selector.call(group.parentNode, node.__data__, i, j));
1803 subnode.__data__ = node.__data__;
1804 } else {
1805 subgroup.push(null);
1806 }
1807 }
1808 }
1809 return d3_selection(subgroups);
1810 };
1811 d3_selection_enterPrototype.insert = function(name, before) {
1812 if (arguments.length < 2) before = d3_selection_enterInsertBefore(this);
1813 return d3_selectionPrototype.insert.call(this, name, before);
1814 };
1815 function d3_selection_enterInsertBefore(enter) {
1816 var i0, j0;
1817 return function(d, i, j) {
1818 var group = enter[j].update, n = group.length, node;
1819 if (j != j0) j0 = j, i0 = 0;
1820 if (i >= i0) i0 = i + 1;
1821 while (!(node = group[i0]) && ++i0 < n) ;
1822 return node;
1823 };
1824 }
1825 d3.select = function(node) {
1826 var group;
1827 if (typeof node === "string") {
1828 group = [ d3_select(node, d3_document) ];
1829 group.parentNode = d3_document.documentElement;
1830 } else {
1831 group = [ node ];
1832 group.parentNode = d3_documentElement(node);
1833 }
1834 return d3_selection([ group ]);
1835 };
1836 d3.selectAll = function(nodes) {
1837 var group;
1838 if (typeof nodes === "string") {
1839 group = d3_array(d3_selectAll(nodes, d3_document));
1840 group.parentNode = d3_document.documentElement;
1841 } else {
1842 group = d3_array(nodes);
1843 group.parentNode = null;
1844 }
1845 return d3_selection([ group ]);
1846 };
1847 d3_selectionPrototype.on = function(type, listener, capture) {
1848 var n = arguments.length;
1849 if (n < 3) {
1850 if (typeof type !== "string") {
1851 if (n < 2) listener = false;
1852 for (capture in type) this.each(d3_selection_on(capture, type[capture], listener));
1853 return this;
1854 }
1855 if (n < 2) return (n = this.node()["__on" + type]) && n._;
1856 capture = false;
1857 }
1858 return this.each(d3_selection_on(type, listener, capture));
1859 };
1860 function d3_selection_on(type, listener, capture) {
1861 var name = "__on" + type, i = type.indexOf("."), wrap = d3_selection_onListener;
1862 if (i > 0) type = type.slice(0, i);
1863 var filter = d3_selection_onFilters.get(type);
1864 if (filter) type = filter, wrap = d3_selection_onFilter;
1865 function onRemove() {
1866 var l = this[name];
1867 if (l) {
1868 this.removeEventListener(type, l, l.$);
1869 delete this[name];
1870 }
1871 }
1872 function onAdd() {
1873 var l = wrap(listener, d3_array(arguments));
1874 onRemove.call(this);
1875 this.addEventListener(type, this[name] = l, l.$ = capture);
1876 l._ = listener;
1877 }
1878 function removeAll() {
1879 var re = new RegExp("^__on([^.]+)" + d3.requote(type) + "$"), match;
1880 for (var name in this) {
1881 if (match = name.match(re)) {
1882 var l = this[name];
1883 this.removeEventListener(match[1], l, l.$);
1884 delete this[name];
1885 }
1886 }
1887 }
1888 return i ? listener ? onAdd : onRemove : listener ? d3_noop : removeAll;
1889 }
1890 var d3_selection_onFilters = d3.map({
1891 mouseenter: "mouseover",
1892 mouseleave: "mouseout"
1893 });
1894 if (d3_document) {
1895 d3_selection_onFilters.forEach(function(k) {
1896 if ("on" + k in d3_document) d3_selection_onFilters.remove(k);
1897 });
1898 }
1899 function d3_selection_onListener(listener, argumentz) {
1900 return function(e) {
1901 var o = d3.event;
1902 d3.event = e;
1903 argumentz[0] = this.__data__;
1904 try {
1905 listener.apply(this, argumentz);
1906 } finally {
1907 d3.event = o;
1908 }
1909 };
1910 }
1911 function d3_selection_onFilter(listener, argumentz) {
1912 var l = d3_selection_onListener(listener, argumentz);
1913 return function(e) {
1914 var target = this, related = e.relatedTarget;
1915 if (!related || related !== target && !(related.compareDocumentPosition(target) & 8)) {
1916 l.call(target, e);
1917 }
1918 };
1919 }
1920 var d3_event_dragSelect, d3_event_dragId = 0;
1921 function d3_event_dragSuppress(node) {
1922 var name = ".dragsuppress-" + ++d3_event_dragId, click = "click" + name, w = d3.select(d3_window(node)).on("touchmove" + name, d3_eventPreventDefault).on("dragstart" + name, d3_eventPreventDefault).on("selectstart" + name, d3_eventPreventDefault);
1923 if (d3_event_dragSelect == null) {
1924 d3_event_dragSelect = "onselectstart" in node ? false : d3_vendorSymbol(node.style, "userSelect");
1925 }
1926 if (d3_event_dragSelect) {
1927 var style = d3_documentElement(node).style, select = style[d3_event_dragSelect];
1928 style[d3_event_dragSelect] = "none";
1929 }
1930 return function(suppressClick) {
1931 w.on(name, null);
1932 if (d3_event_dragSelect) style[d3_event_dragSelect] = select;
1933 if (suppressClick) {
1934 var off = function() {
1935 w.on(click, null);
1936 };
1937 w.on(click, function() {
1938 d3_eventPreventDefault();
1939 off();
1940 }, true);
1941 setTimeout(off, 0);
1942 }
1943 };
1944 }
1945 d3.mouse = function(container) {
1946 return d3_mousePoint(container, d3_eventSource());
1947 };
1948 var d3_mouse_bug44083 = this.navigator && /WebKit/.test(this.navigator.userAgent) ? -1 : 0;
1949 function d3_mousePoint(container, e) {
1950 if (e.changedTouches) e = e.changedTouches[0];
1951 var svg = container.ownerSVGElement || container;
1952 if (svg.createSVGPoint) {
1953 var point = svg.createSVGPoint();
1954 if (d3_mouse_bug44083 < 0) {
1955 var window = d3_window(container);
1956 if (window.scrollX || window.scrollY) {
1957 svg = d3.select("body").append("svg").style({
1958 position: "absolute",
1959 top: 0,
1960 left: 0,
1961 margin: 0,
1962 padding: 0,
1963 border: "none"
1964 }, "important");
1965 var ctm = svg[0][0].getScreenCTM();
1966 d3_mouse_bug44083 = !(ctm.f || ctm.e);
1967 svg.remove();
1968 }
1969 }
1970 if (d3_mouse_bug44083) point.x = e.pageX, point.y = e.pageY; else point.x = e.clientX,
1971 point.y = e.clientY;
1972 point = point.matrixTransform(container.getScreenCTM().inverse());
1973 return [ point.x, point.y ];
1974 }
1975 var rect = container.getBoundingClientRect();
1976 return [ e.clientX - rect.left - container.clientLeft, e.clientY - rect.top - container.clientTop ];
1977 }
1978 d3.touch = function(container, touches, identifier) {
1979 if (arguments.length < 3) identifier = touches, touches = d3_eventSource().changedTouches;
1980 if (touches) for (var i = 0, n = touches.length, touch; i < n; ++i) {
1981 if ((touch = touches[i]).identifier === identifier) {
1982 return d3_mousePoint(container, touch);
1983 }
1984 }
1985 };
1986 d3.behavior.drag = function() {
1987 var event = d3_eventDispatch(drag, "drag", "dragstart", "dragend"), origin = null, mousedown = dragstart(d3_noop, d3.mouse, d3_window, "mousemove", "mouseup"), touchstart = dragstart(d3_behavior_dragTouchId, d3.touch, d3_identity, "touchmove", "touchend");
1988 function drag() {
1989 this.on("mousedown.drag", mousedown).on("touchstart.drag", touchstart);
1990 }
1991 function dragstart(id, position, subject, move, end) {
1992 return function() {
1993 var that = this, target = d3.event.target.correspondingElement || d3.event.target, parent = that.parentNode, dispatch = event.of(that, arguments), dragged = 0, dragId = id(), dragName = ".drag" + (dragId == null ? "" : "-" + dragId), dragOffset, dragSubject = d3.select(subject(target)).on(move + dragName, moved).on(end + dragName, ended), dragRestore = d3_event_dragSuppress(target), position0 = position(parent, dragId);
1994 if (origin) {
1995 dragOffset = origin.apply(that, arguments);
1996 dragOffset = [ dragOffset.x - position0[0], dragOffset.y - position0[1] ];
1997 } else {
1998 dragOffset = [ 0, 0 ];
1999 }
2000 dispatch({
2001 type: "dragstart"
2002 });
2003 function moved() {
2004 var position1 = position(parent, dragId), dx, dy;
2005 if (!position1) return;
2006 dx = position1[0] - position0[0];
2007 dy = position1[1] - position0[1];
2008 dragged |= dx | dy;
2009 position0 = position1;
2010 dispatch({
2011 type: "drag",
2012 x: position1[0] + dragOffset[0],
2013 y: position1[1] + dragOffset[1],
2014 dx: dx,
2015 dy: dy
2016 });
2017 }
2018 function ended() {
2019 if (!position(parent, dragId)) return;
2020 dragSubject.on(move + dragName, null).on(end + dragName, null);
2021 dragRestore(dragged);
2022 dispatch({
2023 type: "dragend"
2024 });
2025 }
2026 };
2027 }
2028 drag.origin = function(x) {
2029 if (!arguments.length) return origin;
2030 origin = x;
2031 return drag;
2032 };
2033 return d3.rebind(drag, event, "on");
2034 };
2035 function d3_behavior_dragTouchId() {
2036 return d3.event.changedTouches[0].identifier;
2037 }
2038 d3.touches = function(container, touches) {
2039 if (arguments.length < 2) touches = d3_eventSource().touches;
2040 return touches ? d3_array(touches).map(function(touch) {
2041 var point = d3_mousePoint(container, touch);
2042 point.identifier = touch.identifier;
2043 return point;
2044 }) : [];
2045 };
2046 var ε = 1e-6, ε2 = ε * ε, π = Math.PI, τ = 2 * π, τε = τ - ε, halfπ = π / 2, d3_radians = π / 180, d3_degrees = 180 / π;
2047 function d3_sgn(x) {
2048 return x > 0 ? 1 : x < 0 ? -1 : 0;
2049 }
2050 function d3_cross2d(a, b, c) {
2051 return (b[0] - a[0]) * (c[1] - a[1]) - (b[1] - a[1]) * (c[0] - a[0]);
2052 }
2053 function d3_acos(x) {
2054 return x > 1 ? 0 : x < -1 ? π : Math.acos(x);
2055 }
2056 function d3_asin(x) {
2057 return x > 1 ? halfπ : x < -1 ? -halfπ : Math.asin(x);
2058 }
2059 function d3_sinh(x) {
2060 return ((x = Math.exp(x)) - 1 / x) / 2;
2061 }
2062 function d3_cosh(x) {
2063 return ((x = Math.exp(x)) + 1 / x) / 2;
2064 }
2065 function d3_tanh(x) {
2066 return ((x = Math.exp(2 * x)) - 1) / (x + 1);
2067 }
2068 function d3_haversin(x) {
2069 return (x = Math.sin(x / 2)) * x;
2070 }
2071 var ρ = Math.SQRT2, ρ2 = 2, ρ4 = 4;
2072 d3.interpolateZoom = function(p0, p1) {
2073 var ux0 = p0[0], uy0 = p0[1], w0 = p0[2], ux1 = p1[0], uy1 = p1[1], w1 = p1[2], dx = ux1 - ux0, dy = uy1 - uy0, d2 = dx * dx + dy * dy, i, S;
2074 if (d2 < ε2) {
2075 S = Math.log(w1 / w0) / ρ;
2076 i = function(t) {
2077 return [ ux0 + t * dx, uy0 + t * dy, w0 * Math.exp(ρ * t * S) ];
2078 };
2079 } else {
2080 var d1 = Math.sqrt(d2), b0 = (w1 * w1 - w0 * w0 + ρ4 * d2) / (2 * w0 * ρ2 * d1), b1 = (w1 * w1 - w0 * w0 - ρ4 * d2) / (2 * w1 * ρ2 * d1), r0 = Math.log(Math.sqrt(b0 * b0 + 1) - b0), r1 = Math.log(Math.sqrt(b1 * b1 + 1) - b1);
2081 S = (r1 - r0) / ρ;
2082 i = function(t) {
2083 var s = t * S, coshr0 = d3_cosh(r0), u = w0 / (ρ2 * d1) * (coshr0 * d3_tanh(ρ * s + r0) - d3_sinh(r0));
2084 return [ ux0 + u * dx, uy0 + u * dy, w0 * coshr0 / d3_cosh(ρ * s + r0) ];
2085 };
2086 }
2087 i.duration = S * 1e3;
2088 return i;
2089 };
2090 d3.behavior.zoom = function() {
2091 var view = {
2092 x: 0,
2093 y: 0,
2094 k: 1
2095 }, translate0, center0, center, size = [ 960, 500 ], scaleExtent = d3_behavior_zoomInfinity, duration = 250, zooming = 0, mousedown = "mousedown.zoom", mousemove = "mousemove.zoom", mouseup = "mouseup.zoom", mousewheelTimer, touchstart = "touchstart.zoom", touchtime, event = d3_eventDispatch(zoom, "zoomstart", "zoom", "zoomend"), x0, x1, y0, y1;
2096 if (!d3_behavior_zoomWheel) {
2097 d3_behavior_zoomWheel = "onwheel" in d3_document ? (d3_behavior_zoomDelta = function() {
2098 return -d3.event.deltaY * (d3.event.deltaMode ? 120 : 1);
2099 }, "wheel") : "onmousewheel" in d3_document ? (d3_behavior_zoomDelta = function() {
2100 return d3.event.wheelDelta;
2101 }, "mousewheel") : (d3_behavior_zoomDelta = function() {
2102 return -d3.event.detail;
2103 }, "MozMousePixelScroll");
2104 }
2105 function zoom(g) {
2106 g.on(mousedown, mousedowned).on(d3_behavior_zoomWheel + ".zoom", mousewheeled).on("dblclick.zoom", dblclicked).on(touchstart, touchstarted);
2107 }
2108 zoom.event = function(g) {
2109 g.each(function() {
2110 var dispatch = event.of(this, arguments), view1 = view;
2111 if (d3_transitionInheritId) {
2112 d3.select(this).transition().each("start.zoom", function() {
2113 view = this.__chart__ || {
2114 x: 0,
2115 y: 0,
2116 k: 1
2117 };
2118 zoomstarted(dispatch);
2119 }).tween("zoom:zoom", function() {
2120 var dx = size[0], dy = size[1], cx = center0 ? center0[0] : dx / 2, cy = center0 ? center0[1] : dy / 2, i = d3.interpolateZoom([ (cx - view.x) / view.k, (cy - view.y) / view.k, dx / view.k ], [ (cx - view1.x) / view1.k, (cy - view1.y) / view1.k, dx / view1.k ]);
2121 return function(t) {
2122 var l = i(t), k = dx / l[2];
2123 this.__chart__ = view = {
2124 x: cx - l[0] * k,
2125 y: cy - l[1] * k,
2126 k: k
2127 };
2128 zoomed(dispatch);
2129 };
2130 }).each("interrupt.zoom", function() {
2131 zoomended(dispatch);
2132 }).each("end.zoom", function() {
2133 zoomended(dispatch);
2134 });
2135 } else {
2136 this.__chart__ = view;
2137 zoomstarted(dispatch);
2138 zoomed(dispatch);
2139 zoomended(dispatch);
2140 }
2141 });
2142 };
2143 zoom.translate = function(_) {
2144 if (!arguments.length) return [ view.x, view.y ];
2145 view = {
2146 x: +_[0],
2147 y: +_[1],
2148 k: view.k
2149 };
2150 rescale();
2151 return zoom;
2152 };
2153 zoom.scale = function(_) {
2154 if (!arguments.length) return view.k;
2155 view = {
2156 x: view.x,
2157 y: view.y,
2158 k: null
2159 };
2160 scaleTo(+_);
2161 rescale();
2162 return zoom;
2163 };
2164 zoom.scaleExtent = function(_) {
2165 if (!arguments.length) return scaleExtent;
2166 scaleExtent = _ == null ? d3_behavior_zoomInfinity : [ +_[0], +_[1] ];
2167 return zoom;
2168 };
2169 zoom.center = function(_) {
2170 if (!arguments.length) return center;
2171 center = _ && [ +_[0], +_[1] ];
2172 return zoom;
2173 };
2174 zoom.size = function(_) {
2175 if (!arguments.length) return size;
2176 size = _ && [ +_[0], +_[1] ];
2177 return zoom;
2178 };
2179 zoom.duration = function(_) {
2180 if (!arguments.length) return duration;
2181 duration = +_;
2182 return zoom;
2183 };
2184 zoom.x = function(z) {
2185 if (!arguments.length) return x1;
2186 x1 = z;
2187 x0 = z.copy();
2188 view = {
2189 x: 0,
2190 y: 0,
2191 k: 1
2192 };
2193 return zoom;
2194 };
2195 zoom.y = function(z) {
2196 if (!arguments.length) return y1;
2197 y1 = z;
2198 y0 = z.copy();
2199 view = {
2200 x: 0,
2201 y: 0,
2202 k: 1
2203 };
2204 return zoom;
2205 };
2206 function location(p) {
2207 return [ (p[0] - view.x) / view.k, (p[1] - view.y) / view.k ];
2208 }
2209 function point(l) {
2210 return [ l[0] * view.k + view.x, l[1] * view.k + view.y ];
2211 }
2212 function scaleTo(s) {
2213 view.k = Math.max(scaleExtent[0], Math.min(scaleExtent[1], s));
2214 }
2215 function translateTo(p, l) {
2216 l = point(l);
2217 view.x += p[0] - l[0];
2218 view.y += p[1] - l[1];
2219 }
2220 function zoomTo(that, p, l, k) {
2221 that.__chart__ = {
2222 x: view.x,
2223 y: view.y,
2224 k: view.k
2225 };
2226 scaleTo(Math.pow(2, k));
2227 translateTo(center0 = p, l);
2228 that = d3.select(that);
2229 if (duration > 0) that = that.transition().duration(duration);
2230 that.call(zoom.event);
2231 }
2232 function rescale() {
2233 if (x1) x1.domain(x0.range().map(function(x) {
2234 return (x - view.x) / view.k;
2235 }).map(x0.invert));
2236 if (y1) y1.domain(y0.range().map(function(y) {
2237 return (y - view.y) / view.k;
2238 }).map(y0.invert));
2239 }
2240 function zoomstarted(dispatch) {
2241 if (!zooming++) dispatch({
2242 type: "zoomstart"
2243 });
2244 }
2245 function zoomed(dispatch) {
2246 rescale();
2247 dispatch({
2248 type: "zoom",
2249 scale: view.k,
2250 translate: [ view.x, view.y ]
2251 });
2252 }
2253 function zoomended(dispatch) {
2254 if (!--zooming) dispatch({
2255 type: "zoomend"
2256 }), center0 = null;
2257 }
2258 function mousedowned() {
2259 var that = this, dispatch = event.of(that, arguments), dragged = 0, subject = d3.select(d3_window(that)).on(mousemove, moved).on(mouseup, ended), location0 = location(d3.mouse(that)), dragRestore = d3_event_dragSuppress(that);
2260 d3_selection_interrupt.call(that);
2261 zoomstarted(dispatch);
2262 function moved() {
2263 dragged = 1;
2264 translateTo(d3.mouse(that), location0);
2265 zoomed(dispatch);
2266 }
2267 function ended() {
2268 subject.on(mousemove, null).on(mouseup, null);
2269 dragRestore(dragged);
2270 zoomended(dispatch);
2271 }
2272 }
2273 function touchstarted() {
2274 var that = this, dispatch = event.of(that, arguments), locations0 = {}, distance0 = 0, scale0, zoomName = ".zoom-" + d3.event.changedTouches[0].identifier, touchmove = "touchmove" + zoomName, touchend = "touchend" + zoomName, targets = [], subject = d3.select(that), dragRestore = d3_event_dragSuppress(that);
2275 started();
2276 zoomstarted(dispatch);
2277 subject.on(mousedown, null).on(touchstart, started);
2278 function relocate() {
2279 var touches = d3.touches(that);
2280 scale0 = view.k;
2281 touches.forEach(function(t) {
2282 if (t.identifier in locations0) locations0[t.identifier] = location(t);
2283 });
2284 return touches;
2285 }
2286 function started() {
2287 var target = d3.event.target;
2288 d3.select(target).on(touchmove, moved).on(touchend, ended);
2289 targets.push(target);
2290 var changed = d3.event.changedTouches;
2291 for (var i = 0, n = changed.length; i < n; ++i) {
2292 locations0[changed[i].identifier] = null;
2293 }
2294 var touches = relocate(), now = Date.now();
2295 if (touches.length === 1) {
2296 if (now - touchtime < 500) {
2297 var p = touches[0];
2298 zoomTo(that, p, locations0[p.identifier], Math.floor(Math.log(view.k) / Math.LN2) + 1);
2299 d3_eventPreventDefault();
2300 }
2301 touchtime = now;
2302 } else if (touches.length > 1) {
2303 var p = touches[0], q = touches[1], dx = p[0] - q[0], dy = p[1] - q[1];
2304 distance0 = dx * dx + dy * dy;
2305 }
2306 }
2307 function moved() {
2308 var touches = d3.touches(that), p0, l0, p1, l1;
2309 d3_selection_interrupt.call(that);
2310 for (var i = 0, n = touches.length; i < n; ++i, l1 = null) {
2311 p1 = touches[i];
2312 if (l1 = locations0[p1.identifier]) {
2313 if (l0) break;
2314 p0 = p1, l0 = l1;
2315 }
2316 }
2317 if (l1) {
2318 var distance1 = (distance1 = p1[0] - p0[0]) * distance1 + (distance1 = p1[1] - p0[1]) * distance1, scale1 = distance0 && Math.sqrt(distance1 / distance0);
2319 p0 = [ (p0[0] + p1[0]) / 2, (p0[1] + p1[1]) / 2 ];
2320 l0 = [ (l0[0] + l1[0]) / 2, (l0[1] + l1[1]) / 2 ];
2321 scaleTo(scale1 * scale0);
2322 }
2323 touchtime = null;
2324 translateTo(p0, l0);
2325 zoomed(dispatch);
2326 }
2327 function ended() {
2328 if (d3.event.touches.length) {
2329 var changed = d3.event.changedTouches;
2330 for (var i = 0, n = changed.length; i < n; ++i) {
2331 delete locations0[changed[i].identifier];
2332 }
2333 for (var identifier in locations0) {
2334 return void relocate();
2335 }
2336 }
2337 d3.selectAll(targets).on(zoomName, null);
2338 subject.on(mousedown, mousedowned).on(touchstart, touchstarted);
2339 dragRestore();
2340 zoomended(dispatch);
2341 }
2342 }
2343 function mousewheeled() {
2344 var dispatch = event.of(this, arguments);
2345 if (mousewheelTimer) clearTimeout(mousewheelTimer); else d3_selection_interrupt.call(this),
2346 translate0 = location(center0 = center || d3.mouse(this)), zoomstarted(dispatch);
2347 mousewheelTimer = setTimeout(function() {
2348 mousewheelTimer = null;
2349 zoomended(dispatch);
2350 }, 50);
2351 d3_eventPreventDefault();
2352 scaleTo(Math.pow(2, d3_behavior_zoomDelta() * .002) * view.k);
2353 translateTo(center0, translate0);
2354 zoomed(dispatch);
2355 }
2356 function dblclicked() {
2357 var p = d3.mouse(this), k = Math.log(view.k) / Math.LN2;
2358 zoomTo(this, p, location(p), d3.event.shiftKey ? Math.ceil(k) - 1 : Math.floor(k) + 1);
2359 }
2360 return d3.rebind(zoom, event, "on");
2361 };
2362 var d3_behavior_zoomInfinity = [ 0, Infinity ], d3_behavior_zoomDelta, d3_behavior_zoomWheel;
2363 d3.color = d3_color;
2364 function d3_color() {}
2365 d3_color.prototype.toString = function() {
2366 return this.rgb() + "";
2367 };
2368 d3.hsl = d3_hsl;
2369 function d3_hsl(h, s, l) {
2370 return this instanceof d3_hsl ? void (this.h = +h, this.s = +s, this.l = +l) : arguments.length < 2 ? h instanceof d3_hsl ? new d3_hsl(h.h, h.s, h.l) : d3_rgb_parse("" + h, d3_rgb_hsl, d3_hsl) : new d3_hsl(h, s, l);
2371 }
2372 var d3_hslPrototype = d3_hsl.prototype = new d3_color();
2373 d3_hslPrototype.brighter = function(k) {
2374 k = Math.pow(.7, arguments.length ? k : 1);
2375 return new d3_hsl(this.h, this.s, this.l / k);
2376 };
2377 d3_hslPrototype.darker = function(k) {
2378 k = Math.pow(.7, arguments.length ? k : 1);
2379 return new d3_hsl(this.h, this.s, k * this.l);
2380 };
2381 d3_hslPrototype.rgb = function() {
2382 return d3_hsl_rgb(this.h, this.s, this.l);
2383 };
2384 function d3_hsl_rgb(h, s, l) {
2385 var m1, m2;
2386 h = isNaN(h) ? 0 : (h %= 360) < 0 ? h + 360 : h;
2387 s = isNaN(s) ? 0 : s < 0 ? 0 : s > 1 ? 1 : s;
2388 l = l < 0 ? 0 : l > 1 ? 1 : l;
2389 m2 = l <= .5 ? l * (1 + s) : l + s - l * s;
2390 m1 = 2 * l - m2;
2391 function v(h) {
2392 if (h > 360) h -= 360; else if (h < 0) h += 360;
2393 if (h < 60) return m1 + (m2 - m1) * h / 60;
2394 if (h < 180) return m2;
2395 if (h < 240) return m1 + (m2 - m1) * (240 - h) / 60;
2396 return m1;
2397 }
2398 function vv(h) {
2399 return Math.round(v(h) * 255);
2400 }
2401 return new d3_rgb(vv(h + 120), vv(h), vv(h - 120));
2402 }
2403 d3.hcl = d3_hcl;
2404 function d3_hcl(h, c, l) {
2405 return this instanceof d3_hcl ? void (this.h = +h, this.c = +c, this.l = +l) : arguments.length < 2 ? h instanceof d3_hcl ? new d3_hcl(h.h, h.c, h.l) : h instanceof d3_lab ? d3_lab_hcl(h.l, h.a, h.b) : d3_lab_hcl((h = d3_rgb_lab((h = d3.rgb(h)).r, h.g, h.b)).l, h.a, h.b) : new d3_hcl(h, c, l);
2406 }
2407 var d3_hclPrototype = d3_hcl.prototype = new d3_color();
2408 d3_hclPrototype.brighter = function(k) {
2409 return new d3_hcl(this.h, this.c, Math.min(100, this.l + d3_lab_K * (arguments.length ? k : 1)));
2410 };
2411 d3_hclPrototype.darker = function(k) {
2412 return new d3_hcl(this.h, this.c, Math.max(0, this.l - d3_lab_K * (arguments.length ? k : 1)));
2413 };
2414 d3_hclPrototype.rgb = function() {
2415 return d3_hcl_lab(this.h, this.c, this.l).rgb();
2416 };
2417 function d3_hcl_lab(h, c, l) {
2418 if (isNaN(h)) h = 0;
2419 if (isNaN(c)) c = 0;
2420 return new d3_lab(l, Math.cos(h *= d3_radians) * c, Math.sin(h) * c);
2421 }
2422 d3.lab = d3_lab;
2423 function d3_lab(l, a, b) {
2424 return this instanceof d3_lab ? void (this.l = +l, this.a = +a, this.b = +b) : arguments.length < 2 ? l instanceof d3_lab ? new d3_lab(l.l, l.a, l.b) : l instanceof d3_hcl ? d3_hcl_lab(l.h, l.c, l.l) : d3_rgb_lab((l = d3_rgb(l)).r, l.g, l.b) : new d3_lab(l, a, b);
2425 }
2426 var d3_lab_K = 18;
2427 var d3_lab_X = .95047, d3_lab_Y = 1, d3_lab_Z = 1.08883;
2428 var d3_labPrototype = d3_lab.prototype = new d3_color();
2429 d3_labPrototype.brighter = function(k) {
2430 return new d3_lab(Math.min(100, this.l + d3_lab_K * (arguments.length ? k : 1)), this.a, this.b);
2431 };
2432 d3_labPrototype.darker = function(k) {
2433 return new d3_lab(Math.max(0, this.l - d3_lab_K * (arguments.length ? k : 1)), this.a, this.b);
2434 };
2435 d3_labPrototype.rgb = function() {
2436 return d3_lab_rgb(this.l, this.a, this.b);
2437 };
2438 function d3_lab_rgb(l, a, b) {
2439 var y = (l + 16) / 116, x = y + a / 500, z = y - b / 200;
2440 x = d3_lab_xyz(x) * d3_lab_X;
2441 y = d3_lab_xyz(y) * d3_lab_Y;
2442 z = d3_lab_xyz(z) * d3_lab_Z;
2443 return new d3_rgb(d3_xyz_rgb(3.2404542 * x - 1.5371385 * y - .4985314 * z), d3_xyz_rgb(-.969266 * x + 1.8760108 * y + .041556 * z), d3_xyz_rgb(.0556434 * x - .2040259 * y + 1.0572252 * z));
2444 }
2445 function d3_lab_hcl(l, a, b) {
2446 return l > 0 ? new d3_hcl(Math.atan2(b, a) * d3_degrees, Math.sqrt(a * a + b * b), l) : new d3_hcl(NaN, NaN, l);
2447 }
2448 function d3_lab_xyz(x) {
2449 return x > .206893034 ? x * x * x : (x - 4 / 29) / 7.787037;
2450 }
2451 function d3_xyz_lab(x) {
2452 return x > .008856 ? Math.pow(x, 1 / 3) : 7.787037 * x + 4 / 29;
2453 }
2454 function d3_xyz_rgb(r) {
2455 return Math.round(255 * (r <= .00304 ? 12.92 * r : 1.055 * Math.pow(r, 1 / 2.4) - .055));
2456 }
2457 d3.rgb = d3_rgb;
2458 function d3_rgb(r, g, b) {
2459 return this instanceof d3_rgb ? void (this.r = ~~r, this.g = ~~g, this.b = ~~b) : arguments.length < 2 ? r instanceof d3_rgb ? new d3_rgb(r.r, r.g, r.b) : d3_rgb_parse("" + r, d3_rgb, d3_hsl_rgb) : new d3_rgb(r, g, b);
2460 }
2461 function d3_rgbNumber(value) {
2462 return new d3_rgb(value >> 16, value >> 8 & 255, value & 255);
2463 }
2464 function d3_rgbString(value) {
2465 return d3_rgbNumber(value) + "";
2466 }
2467 var d3_rgbPrototype = d3_rgb.prototype = new d3_color();
2468 d3_rgbPrototype.brighter = function(k) {
2469 k = Math.pow(.7, arguments.length ? k : 1);
2470 var r = this.r, g = this.g, b = this.b, i = 30;
2471 if (!r && !g && !b) return new d3_rgb(i, i, i);
2472 if (r && r < i) r = i;
2473 if (g && g < i) g = i;
2474 if (b && b < i) b = i;
2475 return new d3_rgb(Math.min(255, r / k), Math.min(255, g / k), Math.min(255, b / k));
2476 };
2477 d3_rgbPrototype.darker = function(k) {
2478 k = Math.pow(.7, arguments.length ? k : 1);
2479 return new d3_rgb(k * this.r, k * this.g, k * this.b);
2480 };
2481 d3_rgbPrototype.hsl = function() {
2482 return d3_rgb_hsl(this.r, this.g, this.b);
2483 };
2484 d3_rgbPrototype.toString = function() {
2485 return "#" + d3_rgb_hex(this.r) + d3_rgb_hex(this.g) + d3_rgb_hex(this.b);
2486 };
2487 function d3_rgb_hex(v) {
2488 return v < 16 ? "0" + Math.max(0, v).toString(16) : Math.min(255, v).toString(16);
2489 }
2490 function d3_rgb_parse(format, rgb, hsl) {
2491 var r = 0, g = 0, b = 0, m1, m2, color;
2492 m1 = /([a-z]+)\((.*)\)/.exec(format = format.toLowerCase());
2493 if (m1) {
2494 m2 = m1[2].split(",");
2495 switch (m1[1]) {
2496 case "hsl":
2497 {
2498 return hsl(parseFloat(m2[0]), parseFloat(m2[1]) / 100, parseFloat(m2[2]) / 100);
2499 }
2500
2501 case "rgb":
2502 {
2503 return rgb(d3_rgb_parseNumber(m2[0]), d3_rgb_parseNumber(m2[1]), d3_rgb_parseNumber(m2[2]));
2504 }
2505 }
2506 }
2507 if (color = d3_rgb_names.get(format)) {
2508 return rgb(color.r, color.g, color.b);
2509 }
2510 if (format != null && format.charAt(0) === "#" && !isNaN(color = parseInt(format.slice(1), 16))) {
2511 if (format.length === 4) {
2512 r = (color & 3840) >> 4;
2513 r = r >> 4 | r;
2514 g = color & 240;
2515 g = g >> 4 | g;
2516 b = color & 15;
2517 b = b << 4 | b;
2518 } else if (format.length === 7) {
2519 r = (color & 16711680) >> 16;
2520 g = (color & 65280) >> 8;
2521 b = color & 255;
2522 }
2523 }
2524 return rgb(r, g, b);
2525 }
2526 function d3_rgb_hsl(r, g, b) {
2527 var min = Math.min(r /= 255, g /= 255, b /= 255), max = Math.max(r, g, b), d = max - min, h, s, l = (max + min) / 2;
2528 if (d) {
2529 s = l < .5 ? d / (max + min) : d / (2 - max - min);
2530 if (r == max) h = (g - b) / d + (g < b ? 6 : 0); else if (g == max) h = (b - r) / d + 2; else h = (r - g) / d + 4;
2531 h *= 60;
2532 } else {
2533 h = NaN;
2534 s = l > 0 && l < 1 ? 0 : h;
2535 }
2536 return new d3_hsl(h, s, l);
2537 }
2538 function d3_rgb_lab(r, g, b) {
2539 r = d3_rgb_xyz(r);
2540 g = d3_rgb_xyz(g);
2541 b = d3_rgb_xyz(b);
2542 var x = d3_xyz_lab((.4124564 * r + .3575761 * g + .1804375 * b) / d3_lab_X), y = d3_xyz_lab((.2126729 * r + .7151522 * g + .072175 * b) / d3_lab_Y), z = d3_xyz_lab((.0193339 * r + .119192 * g + .9503041 * b) / d3_lab_Z);
2543 return d3_lab(116 * y - 16, 500 * (x - y), 200 * (y - z));
2544 }
2545 function d3_rgb_xyz(r) {
2546 return (r /= 255) <= .04045 ? r / 12.92 : Math.pow((r + .055) / 1.055, 2.4);
2547 }
2548 function d3_rgb_parseNumber(c) {
2549 var f = parseFloat(c);
2550 return c.charAt(c.length - 1) === "%" ? Math.round(f * 2.55) : f;
2551 }
2552 var d3_rgb_names = d3.map({
2553 aliceblue: 15792383,
2554 antiquewhite: 16444375,
2555 aqua: 65535,
2556 aquamarine: 8388564,
2557 azure: 15794175,
2558 beige: 16119260,
2559 bisque: 16770244,
2560 black: 0,
2561 blanchedalmond: 16772045,
2562 blue: 255,
2563 blueviolet: 9055202,
2564 brown: 10824234,
2565 burlywood: 14596231,
2566 cadetblue: 6266528,
2567 chartreuse: 8388352,
2568 chocolate: 13789470,
2569 coral: 16744272,
2570 cornflowerblue: 6591981,
2571 cornsilk: 16775388,
2572 crimson: 14423100,
2573 cyan: 65535,
2574 darkblue: 139,
2575 darkcyan: 35723,
2576 darkgoldenrod: 12092939,
2577 darkgray: 11119017,
2578 darkgreen: 25600,
2579 darkgrey: 11119017,
2580 darkkhaki: 12433259,
2581 darkmagenta: 9109643,
2582 darkolivegreen: 5597999,
2583 darkorange: 16747520,
2584 darkorchid: 10040012,
2585 darkred: 9109504,
2586 darksalmon: 15308410,
2587 darkseagreen: 9419919,
2588 darkslateblue: 4734347,
2589 darkslategray: 3100495,
2590 darkslategrey: 3100495,
2591 darkturquoise: 52945,
2592 darkviolet: 9699539,
2593 deeppink: 16716947,
2594 deepskyblue: 49151,
2595 dimgray: 6908265,
2596 dimgrey: 6908265,
2597 dodgerblue: 2003199,
2598 firebrick: 11674146,
2599 floralwhite: 16775920,
2600 forestgreen: 2263842,
2601 fuchsia: 16711935,
2602 gainsboro: 14474460,
2603 ghostwhite: 16316671,
2604 gold: 16766720,
2605 goldenrod: 14329120,
2606 gray: 8421504,
2607 green: 32768,
2608 greenyellow: 11403055,
2609 grey: 8421504,
2610 honeydew: 15794160,
2611 hotpink: 16738740,
2612 indianred: 13458524,
2613 indigo: 4915330,
2614 ivory: 16777200,
2615 khaki: 15787660,
2616 lavender: 15132410,
2617 lavenderblush: 16773365,
2618 lawngreen: 8190976,
2619 lemonchiffon: 16775885,
2620 lightblue: 11393254,
2621 lightcoral: 15761536,
2622 lightcyan: 14745599,
2623 lightgoldenrodyellow: 16448210,
2624 lightgray: 13882323,
2625 lightgreen: 9498256,
2626 lightgrey: 13882323,
2627 lightpink: 16758465,
2628 lightsalmon: 16752762,
2629 lightseagreen: 2142890,
2630 lightskyblue: 8900346,
2631 lightslategray: 7833753,
2632 lightslategrey: 7833753,
2633 lightsteelblue: 11584734,
2634 lightyellow: 16777184,
2635 lime: 65280,
2636 limegreen: 3329330,
2637 linen: 16445670,
2638 magenta: 16711935,
2639 maroon: 8388608,
2640 mediumaquamarine: 6737322,
2641 mediumblue: 205,
2642 mediumorchid: 12211667,
2643 mediumpurple: 9662683,
2644 mediumseagreen: 3978097,
2645 mediumslateblue: 8087790,
2646 mediumspringgreen: 64154,
2647 mediumturquoise: 4772300,
2648 mediumvioletred: 13047173,
2649 midnightblue: 1644912,
2650 mintcream: 16121850,
2651 mistyrose: 16770273,
2652 moccasin: 16770229,
2653 navajowhite: 16768685,
2654 navy: 128,
2655 oldlace: 16643558,
2656 olive: 8421376,
2657 olivedrab: 7048739,
2658 orange: 16753920,
2659 orangered: 16729344,
2660 orchid: 14315734,
2661 palegoldenrod: 15657130,
2662 palegreen: 10025880,
2663 paleturquoise: 11529966,
2664 palevioletred: 14381203,
2665 papayawhip: 16773077,
2666 peachpuff: 16767673,
2667 peru: 13468991,
2668 pink: 16761035,
2669 plum: 14524637,
2670 powderblue: 11591910,
2671 purple: 8388736,
2672 rebeccapurple: 6697881,
2673 red: 16711680,
2674 rosybrown: 12357519,
2675 royalblue: 4286945,
2676 saddlebrown: 9127187,
2677 salmon: 16416882,
2678 sandybrown: 16032864,
2679 seagreen: 3050327,
2680 seashell: 16774638,
2681 sienna: 10506797,
2682 silver: 12632256,
2683 skyblue: 8900331,
2684 slateblue: 6970061,
2685 slategray: 7372944,
2686 slategrey: 7372944,
2687 snow: 16775930,
2688 springgreen: 65407,
2689 steelblue: 4620980,
2690 tan: 13808780,
2691 teal: 32896,
2692 thistle: 14204888,
2693 tomato: 16737095,
2694 turquoise: 4251856,
2695 violet: 15631086,
2696 wheat: 16113331,
2697 white: 16777215,
2698 whitesmoke: 16119285,
2699 yellow: 16776960,
2700 yellowgreen: 10145074
2701 });
2702 d3_rgb_names.forEach(function(key, value) {
2703 d3_rgb_names.set(key, d3_rgbNumber(value));
2704 });
2705 function d3_functor(v) {
2706 return typeof v === "function" ? v : function() {
2707 return v;
2708 };
2709 }
2710 d3.functor = d3_functor;
2711 d3.xhr = d3_xhrType(d3_identity);
2712 function d3_xhrType(response) {
2713 return function(url, mimeType, callback) {
2714 if (arguments.length === 2 && typeof mimeType === "function") callback = mimeType,
2715 mimeType = null;
2716 return d3_xhr(url, mimeType, response, callback);
2717 };
2718 }
2719 function d3_xhr(url, mimeType, response, callback) {
2720 var xhr = {}, dispatch = d3.dispatch("beforesend", "progress", "load", "error"), headers = {}, request = new XMLHttpRequest(), responseType = null;
2721 if (this.XDomainRequest && !("withCredentials" in request) && /^(http(s)?:)?\/\//.test(url)) request = new XDomainRequest();
2722 "onload" in request ? request.onload = request.onerror = respond : request.onreadystatechange = function() {
2723 request.readyState > 3 && respond();
2724 };
2725 function respond() {
2726 var status = request.status, result;
2727 if (!status && d3_xhrHasResponse(request) || status >= 200 && status < 300 || status === 304) {
2728 try {
2729 result = response.call(xhr, request);
2730 } catch (e) {
2731 dispatch.error.call(xhr, e);
2732 return;
2733 }
2734 dispatch.load.call(xhr, result);
2735 } else {
2736 dispatch.error.call(xhr, request);
2737 }
2738 }
2739 request.onprogress = function(event) {
2740 var o = d3.event;
2741 d3.event = event;
2742 try {
2743 dispatch.progress.call(xhr, request);
2744 } finally {
2745 d3.event = o;
2746 }
2747 };
2748 xhr.header = function(name, value) {
2749 name = (name + "").toLowerCase();
2750 if (arguments.length < 2) return headers[name];
2751 if (value == null) delete headers[name]; else headers[name] = value + "";
2752 return xhr;
2753 };
2754 xhr.mimeType = function(value) {
2755 if (!arguments.length) return mimeType;
2756 mimeType = value == null ? null : value + "";
2757 return xhr;
2758 };
2759 xhr.responseType = function(value) {
2760 if (!arguments.length) return responseType;
2761 responseType = value;
2762 return xhr;
2763 };
2764 xhr.response = function(value) {
2765 response = value;
2766 return xhr;
2767 };
2768 [ "get", "post" ].forEach(function(method) {
2769 xhr[method] = function() {
2770 return xhr.send.apply(xhr, [ method ].concat(d3_array(arguments)));
2771 };
2772 });
2773 xhr.send = function(method, data, callback) {
2774 if (arguments.length === 2 && typeof data === "function") callback = data, data = null;
2775 request.open(method, url, true);
2776 if (mimeType != null && !("accept" in headers)) headers["accept"] = mimeType + ",*/*";
2777 if (request.setRequestHeader) for (var name in headers) request.setRequestHeader(name, headers[name]);
2778 if (mimeType != null && request.overrideMimeType) request.overrideMimeType(mimeType);
2779 if (responseType != null) request.responseType = responseType;
2780 if (callback != null) xhr.on("error", callback).on("load", function(request) {
2781 callback(null, request);
2782 });
2783 dispatch.beforesend.call(xhr, request);
2784 request.send(data == null ? null : data);
2785 return xhr;
2786 };
2787 xhr.abort = function() {
2788 request.abort();
2789 return xhr;
2790 };
2791 d3.rebind(xhr, dispatch, "on");
2792 return callback == null ? xhr : xhr.get(d3_xhr_fixCallback(callback));
2793 }
2794 function d3_xhr_fixCallback(callback) {
2795 return callback.length === 1 ? function(error, request) {
2796 callback(error == null ? request : null);
2797 } : callback;
2798 }
2799 function d3_xhrHasResponse(request) {
2800 var type = request.responseType;
2801 return type && type !== "text" ? request.response : request.responseText;
2802 }
2803 d3.dsv = function(delimiter, mimeType) {
2804 var reFormat = new RegExp('["' + delimiter + "\n]"), delimiterCode = delimiter.charCodeAt(0);
2805 function dsv(url, row, callback) {
2806 if (arguments.length < 3) callback = row, row = null;
2807 var xhr = d3_xhr(url, mimeType, row == null ? response : typedResponse(row), callback);
2808 xhr.row = function(_) {
2809 return arguments.length ? xhr.response((row = _) == null ? response : typedResponse(_)) : row;
2810 };
2811 return xhr;
2812 }
2813 function response(request) {
2814 return dsv.parse(request.responseText);
2815 }
2816 function typedResponse(f) {
2817 return function(request) {
2818 return dsv.parse(request.responseText, f);
2819 };
2820 }
2821 dsv.parse = function(text, f) {
2822 var o;
2823 return dsv.parseRows(text, function(row, i) {
2824 if (o) return o(row, i - 1);
2825 var a = new Function("d", "return {" + row.map(function(name, i) {
2826 return JSON.stringify(name) + ": d[" + i + "]";
2827 }).join(",") + "}");
2828 o = f ? function(row, i) {
2829 return f(a(row), i);
2830 } : a;
2831 });
2832 };
2833 dsv.parseRows = function(text, f) {
2834 var EOL = {}, EOF = {}, rows = [], N = text.length, I = 0, n = 0, t, eol;
2835 function token() {
2836 if (I >= N) return EOF;
2837 if (eol) return eol = false, EOL;
2838 var j = I;
2839 if (text.charCodeAt(j) === 34) {
2840 var i = j;
2841 while (i++ < N) {
2842 if (text.charCodeAt(i) === 34) {
2843 if (text.charCodeAt(i + 1) !== 34) break;
2844 ++i;
2845 }
2846 }
2847 I = i + 2;
2848 var c = text.charCodeAt(i + 1);
2849 if (c === 13) {
2850 eol = true;
2851 if (text.charCodeAt(i + 2) === 10) ++I;
2852 } else if (c === 10) {
2853 eol = true;
2854 }
2855 return text.slice(j + 1, i).replace(/""/g, '"');
2856 }
2857 while (I < N) {
2858 var c = text.charCodeAt(I++), k = 1;
2859 if (c === 10) eol = true; else if (c === 13) {
2860 eol = true;
2861 if (text.charCodeAt(I) === 10) ++I, ++k;
2862 } else if (c !== delimiterCode) continue;
2863 return text.slice(j, I - k);
2864 }
2865 return text.slice(j);
2866 }
2867 while ((t = token()) !== EOF) {
2868 var a = [];
2869 while (t !== EOL && t !== EOF) {
2870 a.push(t);
2871 t = token();
2872 }
2873 if (f && (a = f(a, n++)) == null) continue;
2874 rows.push(a);
2875 }
2876 return rows;
2877 };
2878 dsv.format = function(rows) {
2879 if (Array.isArray(rows[0])) return dsv.formatRows(rows);
2880 var fieldSet = new d3_Set(), fields = [];
2881 rows.forEach(function(row) {
2882 for (var field in row) {
2883 if (!fieldSet.has(field)) {
2884 fields.push(fieldSet.add(field));
2885 }
2886 }
2887 });
2888 return [ fields.map(formatValue).join(delimiter) ].concat(rows.map(function(row) {
2889 return fields.map(function(field) {
2890 return formatValue(row[field]);
2891 }).join(delimiter);
2892 })).join("\n");
2893 };
2894 dsv.formatRows = function(rows) {
2895 return rows.map(formatRow).join("\n");
2896 };
2897 function formatRow(row) {
2898 return row.map(formatValue).join(delimiter);
2899 }
2900 function formatValue(text) {
2901 return reFormat.test(text) ? '"' + text.replace(/\"/g, '""') + '"' : text;
2902 }
2903 return dsv;
2904 };
2905 d3.csv = d3.dsv(",", "text/csv");
2906 d3.tsv = d3.dsv(" ", "text/tab-separated-values");
2907 var d3_timer_queueHead, d3_timer_queueTail, d3_timer_interval, d3_timer_timeout, d3_timer_frame = this[d3_vendorSymbol(this, "requestAnimationFrame")] || function(callback) {
2908 setTimeout(callback, 17);
2909 };
2910 d3.timer = function() {
2911 d3_timer.apply(this, arguments);
2912 };
2913 function d3_timer(callback, delay, then) {
2914 var n = arguments.length;
2915 if (n < 2) delay = 0;
2916 if (n < 3) then = Date.now();
2917 var time = then + delay, timer = {
2918 c: callback,
2919 t: time,
2920 n: null
2921 };
2922 if (d3_timer_queueTail) d3_timer_queueTail.n = timer; else d3_timer_queueHead = timer;
2923 d3_timer_queueTail = timer;
2924 if (!d3_timer_interval) {
2925 d3_timer_timeout = clearTimeout(d3_timer_timeout);
2926 d3_timer_interval = 1;
2927 d3_timer_frame(d3_timer_step);
2928 }
2929 return timer;
2930 }
2931 function d3_timer_step() {
2932 var now = d3_timer_mark(), delay = d3_timer_sweep() - now;
2933 if (delay > 24) {
2934 if (isFinite(delay)) {
2935 clearTimeout(d3_timer_timeout);
2936 d3_timer_timeout = setTimeout(d3_timer_step, delay);
2937 }
2938 d3_timer_interval = 0;
2939 } else {
2940 d3_timer_interval = 1;
2941 d3_timer_frame(d3_timer_step);
2942 }
2943 }
2944 d3.timer.flush = function() {
2945 d3_timer_mark();
2946 d3_timer_sweep();
2947 };
2948 function d3_timer_mark() {
2949 var now = Date.now(), timer = d3_timer_queueHead;
2950 while (timer) {
2951 if (now >= timer.t && timer.c(now - timer.t)) timer.c = null;
2952 timer = timer.n;
2953 }
2954 return now;
2955 }
2956 function d3_timer_sweep() {
2957 var t0, t1 = d3_timer_queueHead, time = Infinity;
2958 while (t1) {
2959 if (t1.c) {
2960 if (t1.t < time) time = t1.t;
2961 t1 = (t0 = t1).n;
2962 } else {
2963 t1 = t0 ? t0.n = t1.n : d3_timer_queueHead = t1.n;
2964 }
2965 }
2966 d3_timer_queueTail = t0;
2967 return time;
2968 }
2969 function d3_format_precision(x, p) {
2970 return p - (x ? Math.ceil(Math.log(x) / Math.LN10) : 1);
2971 }
2972 d3.round = function(x, n) {
2973 return n ? Math.round(x * (n = Math.pow(10, n))) / n : Math.round(x);
2974 };
2975 var d3_formatPrefixes = [ "y", "z", "a", "f", "p", "n", "µ", "m", "", "k", "M", "G", "T", "P", "E", "Z", "Y" ].map(d3_formatPrefix);
2976 d3.formatPrefix = function(value, precision) {
2977 var i = 0;
2978 if (value = +value) {
2979 if (value < 0) value *= -1;
2980 if (precision) value = d3.round(value, d3_format_precision(value, precision));
2981 i = 1 + Math.floor(1e-12 + Math.log(value) / Math.LN10);
2982 i = Math.max(-24, Math.min(24, Math.floor((i - 1) / 3) * 3));
2983 }
2984 return d3_formatPrefixes[8 + i / 3];
2985 };
2986 function d3_formatPrefix(d, i) {
2987 var k = Math.pow(10, abs(8 - i) * 3);
2988 return {
2989 scale: i > 8 ? function(d) {
2990 return d / k;
2991 } : function(d) {
2992 return d * k;
2993 },
2994 symbol: d
2995 };
2996 }
2997 function d3_locale_numberFormat(locale) {
2998 var locale_decimal = locale.decimal, locale_thousands = locale.thousands, locale_grouping = locale.grouping, locale_currency = locale.currency, formatGroup = locale_grouping && locale_thousands ? function(value, width) {
2999 var i = value.length, t = [], j = 0, g = locale_grouping[0], length = 0;
3000 while (i > 0 && g > 0) {
3001 if (length + g + 1 > width) g = Math.max(1, width - length);
3002 t.push(value.substring(i -= g, i + g));
3003 if ((length += g + 1) > width) break;
3004 g = locale_grouping[j = (j + 1) % locale_grouping.length];
3005 }
3006 return t.reverse().join(locale_thousands);
3007 } : d3_identity;
3008 return function(specifier) {
3009 var match = d3_format_re.exec(specifier), fill = match[1] || " ", align = match[2] || ">", sign = match[3] || "-", symbol = match[4] || "", zfill = match[5], width = +match[6], comma = match[7], precision = match[8], type = match[9], scale = 1, prefix = "", suffix = "", integer = false, exponent = true;
3010 if (precision) precision = +precision.substring(1);
3011 if (zfill || fill === "0" && align === "=") {
3012 zfill = fill = "0";
3013 align = "=";
3014 }
3015 switch (type) {
3016 case "n":
3017 comma = true;
3018 type = "g";
3019 break;
3020
3021 case "%":
3022 scale = 100;
3023 suffix = "%";
3024 type = "f";
3025 break;
3026
3027 case "p":
3028 scale = 100;
3029 suffix = "%";
3030 type = "r";
3031 break;
3032
3033 case "b":
3034 case "o":
3035 case "x":
3036 case "X":
3037 if (symbol === "#") prefix = "0" + type.toLowerCase();
3038
3039 case "c":
3040 exponent = false;
3041
3042 case "d":
3043 integer = true;
3044 precision = 0;
3045 break;
3046
3047 case "s":
3048 scale = -1;
3049 type = "r";
3050 break;
3051 }
3052 if (symbol === "$") prefix = locale_currency[0], suffix = locale_currency[1];
3053 if (type == "r" && !precision) type = "g";
3054 if (precision != null) {
3055 if (type == "g") precision = Math.max(1, Math.min(21, precision)); else if (type == "e" || type == "f") precision = Math.max(0, Math.min(20, precision));
3056 }
3057 type = d3_format_types.get(type) || d3_format_typeDefault;
3058 var zcomma = zfill && comma;
3059 return function(value) {
3060 var fullSuffix = suffix;
3061 if (integer && value % 1) return "";
3062 var negative = value < 0 || value === 0 && 1 / value < 0 ? (value = -value, "-") : sign === "-" ? "" : sign;
3063 if (scale < 0) {
3064 var unit = d3.formatPrefix(value, precision);
3065 value = unit.scale(value);
3066 fullSuffix = unit.symbol + suffix;
3067 } else {
3068 value *= scale;
3069 }
3070 value = type(value, precision);
3071 var i = value.lastIndexOf("."), before, after;
3072 if (i < 0) {
3073 var j = exponent ? value.lastIndexOf("e") : -1;
3074 if (j < 0) before = value, after = ""; else before = value.substring(0, j), after = value.substring(j);
3075 } else {
3076 before = value.substring(0, i);
3077 after = locale_decimal + value.substring(i + 1);
3078 }
3079 if (!zfill && comma) before = formatGroup(before, Infinity);
3080 var length = prefix.length + before.length + after.length + (zcomma ? 0 : negative.length), padding = length < width ? new Array(length = width - length + 1).join(fill) : "";
3081 if (zcomma) before = formatGroup(padding + before, padding.length ? width - after.length : Infinity);
3082 negative += prefix;
3083 value = before + after;
3084 return (align === "<" ? negative + value + padding : align === ">" ? padding + negative + value : align === "^" ? padding.substring(0, length >>= 1) + negative + value + padding.substring(length) : negative + (zcomma ? value : padding + value)) + fullSuffix;
3085 };
3086 };
3087 }
3088 var d3_format_re = /(?:([^{])?([<>=^]))?([+\- ])?([$#])?(0)?(\d+)?(,)?(\.-?\d+)?([a-z%])?/i;
3089 var d3_format_types = d3.map({
3090 b: function(x) {
3091 return x.toString(2);
3092 },
3093 c: function(x) {
3094 return String.fromCharCode(x);
3095 },
3096 o: function(x) {
3097 return x.toString(8);
3098 },
3099 x: function(x) {
3100 return x.toString(16);
3101 },
3102 X: function(x) {
3103 return x.toString(16).toUpperCase();
3104 },
3105 g: function(x, p) {
3106 return x.toPrecision(p);
3107 },
3108 e: function(x, p) {
3109 return x.toExponential(p);
3110 },
3111 f: function(x, p) {
3112 return x.toFixed(p);
3113 },
3114 r: function(x, p) {
3115 return (x = d3.round(x, d3_format_precision(x, p))).toFixed(Math.max(0, Math.min(20, d3_format_precision(x * (1 + 1e-15), p))));
3116 }
3117 });
3118 function d3_format_typeDefault(x) {
3119 return x + "";
3120 }
3121 var d3_time = d3.time = {}, d3_date = Date;
3122 function d3_date_utc() {
3123 this._ = new Date(arguments.length > 1 ? Date.UTC.apply(this, arguments) : arguments[0]);
3124 }
3125 d3_date_utc.prototype = {
3126 getDate: function() {
3127 return this._.getUTCDate();
3128 },
3129 getDay: function() {
3130 return this._.getUTCDay();
3131 },
3132 getFullYear: function() {
3133 return this._.getUTCFullYear();
3134 },
3135 getHours: function() {
3136 return this._.getUTCHours();
3137 },
3138 getMilliseconds: function() {
3139 return this._.getUTCMilliseconds();
3140 },
3141 getMinutes: function() {
3142 return this._.getUTCMinutes();
3143 },
3144 getMonth: function() {
3145 return this._.getUTCMonth();
3146 },
3147 getSeconds: function() {
3148 return this._.getUTCSeconds();
3149 },
3150 getTime: function() {
3151 return this._.getTime();
3152 },
3153 getTimezoneOffset: function() {
3154 return 0;
3155 },
3156 valueOf: function() {
3157 return this._.valueOf();
3158 },
3159 setDate: function() {
3160 d3_time_prototype.setUTCDate.apply(this._, arguments);
3161 },
3162 setDay: function() {
3163 d3_time_prototype.setUTCDay.apply(this._, arguments);
3164 },
3165 setFullYear: function() {
3166 d3_time_prototype.setUTCFullYear.apply(this._, arguments);
3167 },
3168 setHours: function() {
3169 d3_time_prototype.setUTCHours.apply(this._, arguments);
3170 },
3171 setMilliseconds: function() {
3172 d3_time_prototype.setUTCMilliseconds.apply(this._, arguments);
3173 },
3174 setMinutes: function() {
3175 d3_time_prototype.setUTCMinutes.apply(this._, arguments);
3176 },
3177 setMonth: function() {
3178 d3_time_prototype.setUTCMonth.apply(this._, arguments);
3179 },
3180 setSeconds: function() {
3181 d3_time_prototype.setUTCSeconds.apply(this._, arguments);
3182 },
3183 setTime: function() {
3184 d3_time_prototype.setTime.apply(this._, arguments);
3185 }
3186 };
3187 var d3_time_prototype = Date.prototype;
3188 function d3_time_interval(local, step, number) {
3189 function round(date) {
3190 var d0 = local(date), d1 = offset(d0, 1);
3191 return date - d0 < d1 - date ? d0 : d1;
3192 }
3193 function ceil(date) {
3194 step(date = local(new d3_date(date - 1)), 1);
3195 return date;
3196 }
3197 function offset(date, k) {
3198 step(date = new d3_date(+date), k);
3199 return date;
3200 }
3201 function range(t0, t1, dt) {
3202 var time = ceil(t0), times = [];
3203 if (dt > 1) {
3204 while (time < t1) {
3205 if (!(number(time) % dt)) times.push(new Date(+time));
3206 step(time, 1);
3207 }
3208 } else {
3209 while (time < t1) times.push(new Date(+time)), step(time, 1);
3210 }
3211 return times;
3212 }
3213 function range_utc(t0, t1, dt) {
3214 try {
3215 d3_date = d3_date_utc;
3216 var utc = new d3_date_utc();
3217 utc._ = t0;
3218 return range(utc, t1, dt);
3219 } finally {
3220 d3_date = Date;
3221 }
3222 }
3223 local.floor = local;
3224 local.round = round;
3225 local.ceil = ceil;
3226 local.offset = offset;
3227 local.range = range;
3228 var utc = local.utc = d3_time_interval_utc(local);
3229 utc.floor = utc;
3230 utc.round = d3_time_interval_utc(round);
3231 utc.ceil = d3_time_interval_utc(ceil);
3232 utc.offset = d3_time_interval_utc(offset);
3233 utc.range = range_utc;
3234 return local;
3235 }
3236 function d3_time_interval_utc(method) {
3237 return function(date, k) {
3238 try {
3239 d3_date = d3_date_utc;
3240 var utc = new d3_date_utc();
3241 utc._ = date;
3242 return method(utc, k)._;
3243 } finally {
3244 d3_date = Date;
3245 }
3246 };
3247 }
3248 d3_time.year = d3_time_interval(function(date) {
3249 date = d3_time.day(date);
3250 date.setMonth(0, 1);
3251 return date;
3252 }, function(date, offset) {
3253 date.setFullYear(date.getFullYear() + offset);
3254 }, function(date) {
3255 return date.getFullYear();
3256 });
3257 d3_time.years = d3_time.year.range;
3258 d3_time.years.utc = d3_time.year.utc.range;
3259 d3_time.day = d3_time_interval(function(date) {
3260 var day = new d3_date(2e3, 0);
3261 day.setFullYear(date.getFullYear(), date.getMonth(), date.getDate());
3262 return day;
3263 }, function(date, offset) {
3264 date.setDate(date.getDate() + offset);
3265 }, function(date) {
3266 return date.getDate() - 1;
3267 });
3268 d3_time.days = d3_time.day.range;
3269 d3_time.days.utc = d3_time.day.utc.range;
3270 d3_time.dayOfYear = function(date) {
3271 var year = d3_time.year(date);
3272 return Math.floor((date - year - (date.getTimezoneOffset() - year.getTimezoneOffset()) * 6e4) / 864e5);
3273 };
3274 [ "sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday" ].forEach(function(day, i) {
3275 i = 7 - i;
3276 var interval = d3_time[day] = d3_time_interval(function(date) {
3277 (date = d3_time.day(date)).setDate(date.getDate() - (date.getDay() + i) % 7);
3278 return date;
3279 }, function(date, offset) {
3280 date.setDate(date.getDate() + Math.floor(offset) * 7);
3281 }, function(date) {
3282 var day = d3_time.year(date).getDay();
3283 return Math.floor((d3_time.dayOfYear(date) + (day + i) % 7) / 7) - (day !== i);
3284 });
3285 d3_time[day + "s"] = interval.range;
3286 d3_time[day + "s"].utc = interval.utc.range;
3287 d3_time[day + "OfYear"] = function(date) {
3288 var day = d3_time.year(date).getDay();
3289 return Math.floor((d3_time.dayOfYear(date) + (day + i) % 7) / 7);
3290 };
3291 });
3292 d3_time.week = d3_time.sunday;
3293 d3_time.weeks = d3_time.sunday.range;
3294 d3_time.weeks.utc = d3_time.sunday.utc.range;
3295 d3_time.weekOfYear = d3_time.sundayOfYear;
3296 function d3_locale_timeFormat(locale) {
3297 var locale_dateTime = locale.dateTime, locale_date = locale.date, locale_time = locale.time, locale_periods = locale.periods, locale_days = locale.days, locale_shortDays = locale.shortDays, locale_months = locale.months, locale_shortMonths = locale.shortMonths;
3298 function d3_time_format(template) {
3299 var n = template.length;
3300 function format(date) {
3301 var string = [], i = -1, j = 0, c, p, f;
3302 while (++i < n) {
3303 if (template.charCodeAt(i) === 37) {
3304 string.push(template.slice(j, i));
3305 if ((p = d3_time_formatPads[c = template.charAt(++i)]) != null) c = template.charAt(++i);
3306 if (f = d3_time_formats[c]) c = f(date, p == null ? c === "e" ? " " : "0" : p);
3307 string.push(c);
3308 j = i + 1;
3309 }
3310 }
3311 string.push(template.slice(j, i));
3312 return string.join("");
3313 }
3314 format.parse = function(string) {
3315 var d = {
3316 y: 1900,
3317 m: 0,
3318 d: 1,
3319 H: 0,
3320 M: 0,
3321 S: 0,
3322 L: 0,
3323 Z: null
3324 }, i = d3_time_parse(d, template, string, 0);
3325 if (i != string.length) return null;
3326 if ("p" in d) d.H = d.H % 12 + d.p * 12;
3327 var localZ = d.Z != null && d3_date !== d3_date_utc, date = new (localZ ? d3_date_utc : d3_date)();
3328 if ("j" in d) date.setFullYear(d.y, 0, d.j); else if ("W" in d || "U" in d) {
3329 if (!("w" in d)) d.w = "W" in d ? 1 : 0;
3330 date.setFullYear(d.y, 0, 1);
3331 date.setFullYear(d.y, 0, "W" in d ? (d.w + 6) % 7 + d.W * 7 - (date.getDay() + 5) % 7 : d.w + d.U * 7 - (date.getDay() + 6) % 7);
3332 } else date.setFullYear(d.y, d.m, d.d);
3333 date.setHours(d.H + (d.Z / 100 | 0), d.M + d.Z % 100, d.S, d.L);
3334 return localZ ? date._ : date;
3335 };
3336 format.toString = function() {
3337 return template;
3338 };
3339 return format;
3340 }
3341 function d3_time_parse(date, template, string, j) {
3342 var c, p, t, i = 0, n = template.length, m = string.length;
3343 while (i < n) {
3344 if (j >= m) return -1;
3345 c = template.charCodeAt(i++);
3346 if (c === 37) {
3347 t = template.charAt(i++);
3348 p = d3_time_parsers[t in d3_time_formatPads ? template.charAt(i++) : t];
3349 if (!p || (j = p(date, string, j)) < 0) return -1;
3350 } else if (c != string.charCodeAt(j++)) {
3351 return -1;
3352 }
3353 }
3354 return j;
3355 }
3356 d3_time_format.utc = function(template) {
3357 var local = d3_time_format(template);
3358 function format(date) {
3359 try {
3360 d3_date = d3_date_utc;
3361 var utc = new d3_date();
3362 utc._ = date;
3363 return local(utc);
3364 } finally {
3365 d3_date = Date;
3366 }
3367 }
3368 format.parse = function(string) {
3369 try {
3370 d3_date = d3_date_utc;
3371 var date = local.parse(string);
3372 return date && date._;
3373 } finally {
3374 d3_date = Date;
3375 }
3376 };
3377 format.toString = local.toString;
3378 return format;
3379 };
3380 d3_time_format.multi = d3_time_format.utc.multi = d3_time_formatMulti;
3381 var d3_time_periodLookup = d3.map(), d3_time_dayRe = d3_time_formatRe(locale_days), d3_time_dayLookup = d3_time_formatLookup(locale_days), d3_time_dayAbbrevRe = d3_time_formatRe(locale_shortDays), d3_time_dayAbbrevLookup = d3_time_formatLookup(locale_shortDays), d3_time_monthRe = d3_time_formatRe(locale_months), d3_time_monthLookup = d3_time_formatLookup(locale_months), d3_time_monthAbbrevRe = d3_time_formatRe(locale_shortMonths), d3_time_monthAbbrevLookup = d3_time_formatLookup(locale_shortMonths);
3382 locale_periods.forEach(function(p, i) {
3383 d3_time_periodLookup.set(p.toLowerCase(), i);
3384 });
3385 var d3_time_formats = {
3386 a: function(d) {
3387 return locale_shortDays[d.getDay()];
3388 },
3389 A: function(d) {
3390 return locale_days[d.getDay()];
3391 },
3392 b: function(d) {
3393 return locale_shortMonths[d.getMonth()];
3394 },
3395 B: function(d) {
3396 return locale_months[d.getMonth()];
3397 },
3398 c: d3_time_format(locale_dateTime),
3399 d: function(d, p) {
3400 return d3_time_formatPad(d.getDate(), p, 2);
3401 },
3402 e: function(d, p) {
3403 return d3_time_formatPad(d.getDate(), p, 2);
3404 },
3405 H: function(d, p) {
3406 return d3_time_formatPad(d.getHours(), p, 2);
3407 },
3408 I: function(d, p) {
3409 return d3_time_formatPad(d.getHours() % 12 || 12, p, 2);
3410 },
3411 j: function(d, p) {
3412 return d3_time_formatPad(1 + d3_time.dayOfYear(d), p, 3);
3413 },
3414 L: function(d, p) {
3415 return d3_time_formatPad(d.getMilliseconds(), p, 3);
3416 },
3417 m: function(d, p) {
3418 return d3_time_formatPad(d.getMonth() + 1, p, 2);
3419 },
3420 M: function(d, p) {
3421 return d3_time_formatPad(d.getMinutes(), p, 2);
3422 },
3423 p: function(d) {
3424 return locale_periods[+(d.getHours() >= 12)];
3425 },
3426 S: function(d, p) {
3427 return d3_time_formatPad(d.getSeconds(), p, 2);
3428 },
3429 U: function(d, p) {
3430 return d3_time_formatPad(d3_time.sundayOfYear(d), p, 2);
3431 },
3432 w: function(d) {
3433 return d.getDay();
3434 },
3435 W: function(d, p) {
3436 return d3_time_formatPad(d3_time.mondayOfYear(d), p, 2);
3437 },
3438 x: d3_time_format(locale_date),
3439 X: d3_time_format(locale_time),
3440 y: function(d, p) {
3441 return d3_time_formatPad(d.getFullYear() % 100, p, 2);
3442 },
3443 Y: function(d, p) {
3444 return d3_time_formatPad(d.getFullYear() % 1e4, p, 4);
3445 },
3446 Z: d3_time_zone,
3447 "%": function() {
3448 return "%";
3449 }
3450 };
3451 var d3_time_parsers = {
3452 a: d3_time_parseWeekdayAbbrev,
3453 A: d3_time_parseWeekday,
3454 b: d3_time_parseMonthAbbrev,
3455 B: d3_time_parseMonth,
3456 c: d3_time_parseLocaleFull,
3457 d: d3_time_parseDay,
3458 e: d3_time_parseDay,
3459 H: d3_time_parseHour24,
3460 I: d3_time_parseHour24,
3461 j: d3_time_parseDayOfYear,
3462 L: d3_time_parseMilliseconds,
3463 m: d3_time_parseMonthNumber,
3464 M: d3_time_parseMinutes,
3465 p: d3_time_parseAmPm,
3466 S: d3_time_parseSeconds,
3467 U: d3_time_parseWeekNumberSunday,
3468 w: d3_time_parseWeekdayNumber,
3469 W: d3_time_parseWeekNumberMonday,
3470 x: d3_time_parseLocaleDate,
3471 X: d3_time_parseLocaleTime,
3472 y: d3_time_parseYear,
3473 Y: d3_time_parseFullYear,
3474 Z: d3_time_parseZone,
3475 "%": d3_time_parseLiteralPercent
3476 };
3477 function d3_time_parseWeekdayAbbrev(date, string, i) {
3478 d3_time_dayAbbrevRe.lastIndex = 0;
3479 var n = d3_time_dayAbbrevRe.exec(string.slice(i));
3480 return n ? (date.w = d3_time_dayAbbrevLookup.get(n[0].toLowerCase()), i + n[0].length) : -1;
3481 }
3482 function d3_time_parseWeekday(date, string, i) {
3483 d3_time_dayRe.lastIndex = 0;
3484 var n = d3_time_dayRe.exec(string.slice(i));
3485 return n ? (date.w = d3_time_dayLookup.get(n[0].toLowerCase()), i + n[0].length) : -1;
3486 }
3487 function d3_time_parseMonthAbbrev(date, string, i) {
3488 d3_time_monthAbbrevRe.lastIndex = 0;
3489 var n = d3_time_monthAbbrevRe.exec(string.slice(i));
3490 return n ? (date.m = d3_time_monthAbbrevLookup.get(n[0].toLowerCase()), i + n[0].length) : -1;
3491 }
3492 function d3_time_parseMonth(date, string, i) {
3493 d3_time_monthRe.lastIndex = 0;
3494 var n = d3_time_monthRe.exec(string.slice(i));
3495 return n ? (date.m = d3_time_monthLookup.get(n[0].toLowerCase()), i + n[0].length) : -1;
3496 }
3497 function d3_time_parseLocaleFull(date, string, i) {
3498 return d3_time_parse(date, d3_time_formats.c.toString(), string, i);
3499 }
3500 function d3_time_parseLocaleDate(date, string, i) {
3501 return d3_time_parse(date, d3_time_formats.x.toString(), string, i);
3502 }
3503 function d3_time_parseLocaleTime(date, string, i) {
3504 return d3_time_parse(date, d3_time_formats.X.toString(), string, i);
3505 }
3506 function d3_time_parseAmPm(date, string, i) {
3507 var n = d3_time_periodLookup.get(string.slice(i, i += 2).toLowerCase());
3508 return n == null ? -1 : (date.p = n, i);
3509 }
3510 return d3_time_format;
3511 }
3512 var d3_time_formatPads = {
3513 "-": "",
3514 _: " ",
3515 "0": "0"
3516 }, d3_time_numberRe = /^\s*\d+/, d3_time_percentRe = /^%/;
3517 function d3_time_formatPad(value, fill, width) {
3518 var sign = value < 0 ? "-" : "", string = (sign ? -value : value) + "", length = string.length;
3519 return sign + (length < width ? new Array(width - length + 1).join(fill) + string : string);
3520 }
3521 function d3_time_formatRe(names) {
3522 return new RegExp("^(?:" + names.map(d3.requote).join("|") + ")", "i");
3523 }
3524 function d3_time_formatLookup(names) {
3525 var map = new d3_Map(), i = -1, n = names.length;
3526 while (++i < n) map.set(names[i].toLowerCase(), i);
3527 return map;
3528 }
3529 function d3_time_parseWeekdayNumber(date, string, i) {
3530 d3_time_numberRe.lastIndex = 0;
3531 var n = d3_time_numberRe.exec(string.slice(i, i + 1));
3532 return n ? (date.w = +n[0], i + n[0].length) : -1;
3533 }
3534 function d3_time_parseWeekNumberSunday(date, string, i) {
3535 d3_time_numberRe.lastIndex = 0;
3536 var n = d3_time_numberRe.exec(string.slice(i));
3537 return n ? (date.U = +n[0], i + n[0].length) : -1;
3538 }
3539 function d3_time_parseWeekNumberMonday(date, string, i) {
3540 d3_time_numberRe.lastIndex = 0;
3541 var n = d3_time_numberRe.exec(string.slice(i));
3542 return n ? (date.W = +n[0], i + n[0].length) : -1;
3543 }
3544 function d3_time_parseFullYear(date, string, i) {
3545 d3_time_numberRe.lastIndex = 0;
3546 var n = d3_time_numberRe.exec(string.slice(i, i + 4));
3547 return n ? (date.y = +n[0], i + n[0].length) : -1;
3548 }
3549 function d3_time_parseYear(date, string, i) {
3550 d3_time_numberRe.lastIndex = 0;
3551 var n = d3_time_numberRe.exec(string.slice(i, i + 2));
3552 return n ? (date.y = d3_time_expandYear(+n[0]), i + n[0].length) : -1;
3553 }
3554 function d3_time_parseZone(date, string, i) {
3555 return /^[+-]\d{4}$/.test(string = string.slice(i, i + 5)) ? (date.Z = -string,
3556 i + 5) : -1;
3557 }
3558 function d3_time_expandYear(d) {
3559 return d + (d > 68 ? 1900 : 2e3);
3560 }
3561 function d3_time_parseMonthNumber(date, string, i) {
3562 d3_time_numberRe.lastIndex = 0;
3563 var n = d3_time_numberRe.exec(string.slice(i, i + 2));
3564 return n ? (date.m = n[0] - 1, i + n[0].length) : -1;
3565 }
3566 function d3_time_parseDay(date, string, i) {
3567 d3_time_numberRe.lastIndex = 0;
3568 var n = d3_time_numberRe.exec(string.slice(i, i + 2));
3569 return n ? (date.d = +n[0], i + n[0].length) : -1;
3570 }
3571 function d3_time_parseDayOfYear(date, string, i) {
3572 d3_time_numberRe.lastIndex = 0;
3573 var n = d3_time_numberRe.exec(string.slice(i, i + 3));
3574 return n ? (date.j = +n[0], i + n[0].length) : -1;
3575 }
3576 function d3_time_parseHour24(date, string, i) {
3577 d3_time_numberRe.lastIndex = 0;
3578 var n = d3_time_numberRe.exec(string.slice(i, i + 2));
3579 return n ? (date.H = +n[0], i + n[0].length) : -1;
3580 }
3581 function d3_time_parseMinutes(date, string, i) {
3582 d3_time_numberRe.lastIndex = 0;
3583 var n = d3_time_numberRe.exec(string.slice(i, i + 2));
3584 return n ? (date.M = +n[0], i + n[0].length) : -1;
3585 }
3586 function d3_time_parseSeconds(date, string, i) {
3587 d3_time_numberRe.lastIndex = 0;
3588 var n = d3_time_numberRe.exec(string.slice(i, i + 2));
3589 return n ? (date.S = +n[0], i + n[0].length) : -1;
3590 }
3591 function d3_time_parseMilliseconds(date, string, i) {
3592 d3_time_numberRe.lastIndex = 0;
3593 var n = d3_time_numberRe.exec(string.slice(i, i + 3));
3594 return n ? (date.L = +n[0], i + n[0].length) : -1;
3595 }
3596 function d3_time_zone(d) {
3597 var z = d.getTimezoneOffset(), zs = z > 0 ? "-" : "+", zh = abs(z) / 60 | 0, zm = abs(z) % 60;
3598 return zs + d3_time_formatPad(zh, "0", 2) + d3_time_formatPad(zm, "0", 2);
3599 }
3600 function d3_time_parseLiteralPercent(date, string, i) {
3601 d3_time_percentRe.lastIndex = 0;
3602 var n = d3_time_percentRe.exec(string.slice(i, i + 1));
3603 return n ? i + n[0].length : -1;
3604 }
3605 function d3_time_formatMulti(formats) {
3606 var n = formats.length, i = -1;
3607 while (++i < n) formats[i][0] = this(formats[i][0]);
3608 return function(date) {
3609 var i = 0, f = formats[i];
3610 while (!f[1](date)) f = formats[++i];
3611 return f[0](date);
3612 };
3613 }
3614 d3.locale = function(locale) {
3615 return {
3616 numberFormat: d3_locale_numberFormat(locale),
3617 timeFormat: d3_locale_timeFormat(locale)
3618 };
3619 };
3620 var d3_locale_enUS = d3.locale({
3621 decimal: ".",
3622 thousands: ",",
3623 grouping: [ 3 ],
3624 currency: [ "$", "" ],
3625 dateTime: "%a %b %e %X %Y",
3626 date: "%m/%d/%Y",
3627 time: "%H:%M:%S",
3628 periods: [ "AM", "PM" ],
3629 days: [ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" ],
3630 shortDays: [ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" ],
3631 months: [ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" ],
3632 shortMonths: [ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" ]
3633 });
3634 d3.format = d3_locale_enUS.numberFormat;
3635 d3.geo = {};
3636 function d3_adder() {}
3637 d3_adder.prototype = {
3638 s: 0,
3639 t: 0,
3640 add: function(y) {
3641 d3_adderSum(y, this.t, d3_adderTemp);
3642 d3_adderSum(d3_adderTemp.s, this.s, this);
3643 if (this.s) this.t += d3_adderTemp.t; else this.s = d3_adderTemp.t;
3644 },
3645 reset: function() {
3646 this.s = this.t = 0;
3647 },
3648 valueOf: function() {
3649 return this.s;
3650 }
3651 };
3652 var d3_adderTemp = new d3_adder();
3653 function d3_adderSum(a, b, o) {
3654 var x = o.s = a + b, bv = x - a, av = x - bv;
3655 o.t = a - av + (b - bv);
3656 }
3657 d3.geo.stream = function(object, listener) {
3658 if (object && d3_geo_streamObjectType.hasOwnProperty(object.type)) {
3659 d3_geo_streamObjectType[object.type](object, listener);
3660 } else {
3661 d3_geo_streamGeometry(object, listener);
3662 }
3663 };
3664 function d3_geo_streamGeometry(geometry, listener) {
3665 if (geometry && d3_geo_streamGeometryType.hasOwnProperty(geometry.type)) {
3666 d3_geo_streamGeometryType[geometry.type](geometry, listener);
3667 }
3668 }
3669 var d3_geo_streamObjectType = {
3670 Feature: function(feature, listener) {
3671 d3_geo_streamGeometry(feature.geometry, listener);
3672 },
3673 FeatureCollection: function(object, listener) {
3674 var features = object.features, i = -1, n = features.length;
3675 while (++i < n) d3_geo_streamGeometry(features[i].geometry, listener);
3676 }
3677 };
3678 var d3_geo_streamGeometryType = {
3679 Sphere: function(object, listener) {
3680 listener.sphere();
3681 },
3682 Point: function(object, listener) {
3683 object = object.coordinates;
3684 listener.point(object[0], object[1], object[2]);
3685 },
3686 MultiPoint: function(object, listener) {
3687 var coordinates = object.coordinates, i = -1, n = coordinates.length;
3688 while (++i < n) object = coordinates[i], listener.point(object[0], object[1], object[2]);
3689 },
3690 LineString: function(object, listener) {
3691 d3_geo_streamLine(object.coordinates, listener, 0);
3692 },
3693 MultiLineString: function(object, listener) {
3694 var coordinates = object.coordinates, i = -1, n = coordinates.length;
3695 while (++i < n) d3_geo_streamLine(coordinates[i], listener, 0);
3696 },
3697 Polygon: function(object, listener) {
3698 d3_geo_streamPolygon(object.coordinates, listener);
3699 },
3700 MultiPolygon: function(object, listener) {
3701 var coordinates = object.coordinates, i = -1, n = coordinates.length;
3702 while (++i < n) d3_geo_streamPolygon(coordinates[i], listener);
3703 },
3704 GeometryCollection: function(object, listener) {
3705 var geometries = object.geometries, i = -1, n = geometries.length;
3706 while (++i < n) d3_geo_streamGeometry(geometries[i], listener);
3707 }
3708 };
3709 function d3_geo_streamLine(coordinates, listener, closed) {
3710 var i = -1, n = coordinates.length - closed, coordinate;
3711 listener.lineStart();
3712 while (++i < n) coordinate = coordinates[i], listener.point(coordinate[0], coordinate[1], coordinate[2]);
3713 listener.lineEnd();
3714 }
3715 function d3_geo_streamPolygon(coordinates, listener) {
3716 var i = -1, n = coordinates.length;
3717 listener.polygonStart();
3718 while (++i < n) d3_geo_streamLine(coordinates[i], listener, 1);
3719 listener.polygonEnd();
3720 }
3721 d3.geo.area = function(object) {
3722 d3_geo_areaSum = 0;
3723 d3.geo.stream(object, d3_geo_area);
3724 return d3_geo_areaSum;
3725 };
3726 var d3_geo_areaSum, d3_geo_areaRingSum = new d3_adder();
3727 var d3_geo_area = {
3728 sphere: function() {
3729 d3_geo_areaSum += 4 * π;
3730 },
3731 point: d3_noop,
3732 lineStart: d3_noop,
3733 lineEnd: d3_noop,
3734 polygonStart: function() {
3735 d3_geo_areaRingSum.reset();
3736 d3_geo_area.lineStart = d3_geo_areaRingStart;
3737 },
3738 polygonEnd: function() {
3739 var area = 2 * d3_geo_areaRingSum;
3740 d3_geo_areaSum += area < 0 ? 4 * π + area : area;
3741 d3_geo_area.lineStart = d3_geo_area.lineEnd = d3_geo_area.point = d3_noop;
3742 }
3743 };
3744 function d3_geo_areaRingStart() {
3745 var λ00, φ00, λ0, cosφ0, sinφ0;
3746 d3_geo_area.point = function(λ, φ) {
3747 d3_geo_area.point = nextPoint;
3748 λ0 = (λ00 = λ) * d3_radians, cosφ0 = Math.cos(φ = (φ00 = φ) * d3_radians / 2 + π / 4),
3749 sinφ0 = Math.sin(φ);
3750 };
3751 function nextPoint(λ, φ) {
3752 λ *= d3_radians;
3753 φ = φ * d3_radians / 2 + π / 4;
3754 var dλ = λ - λ0, sdλ = dλ >= 0 ? 1 : -1, adλ = sdλ * dλ, cosφ = Math.cos(φ), sinφ = Math.sin(φ), k = sinφ0 * sinφ, u = cosφ0 * cosφ + k * Math.cos(adλ), v = k * sdλ * Math.sin(adλ);
3755 d3_geo_areaRingSum.add(Math.atan2(v, u));
3756 λ0 = λ, cosφ0 = cosφ, sinφ0 = sinφ;
3757 }
3758 d3_geo_area.lineEnd = function() {
3759 nextPoint(λ00, φ00);
3760 };
3761 }
3762 function d3_geo_cartesian(spherical) {
3763 var λ = spherical[0], φ = spherical[1], cosφ = Math.cos(φ);
3764 return [ cosφ * Math.cos(λ), cosφ * Math.sin(λ), Math.sin(φ) ];
3765 }
3766 function d3_geo_cartesianDot(a, b) {
3767 return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
3768 }
3769 function d3_geo_cartesianCross(a, b) {
3770 return [ a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0] ];
3771 }
3772 function d3_geo_cartesianAdd(a, b) {
3773 a[0] += b[0];
3774 a[1] += b[1];
3775 a[2] += b[2];
3776 }
3777 function d3_geo_cartesianScale(vector, k) {
3778 return [ vector[0] * k, vector[1] * k, vector[2] * k ];
3779 }
3780 function d3_geo_cartesianNormalize(d) {
3781 var l = Math.sqrt(d[0] * d[0] + d[1] * d[1] + d[2] * d[2]);
3782 d[0] /= l;
3783 d[1] /= l;
3784 d[2] /= l;
3785 }
3786 function d3_geo_spherical(cartesian) {
3787 return [ Math.atan2(cartesian[1], cartesian[0]), d3_asin(cartesian[2]) ];
3788 }
3789 function d3_geo_sphericalEqual(a, b) {
3790 return abs(a[0] - b[0]) < ε && abs(a[1] - b[1]) < ε;
3791 }
3792 d3.geo.bounds = function() {
3793 var λ0, φ0, λ1, φ1, λ_, λ__, φ__, p0, dλSum, ranges, range;
3794 var bound = {
3795 point: point,
3796 lineStart: lineStart,
3797 lineEnd: lineEnd,
3798 polygonStart: function() {
3799 bound.point = ringPoint;
3800 bound.lineStart = ringStart;
3801 bound.lineEnd = ringEnd;
3802 dλSum = 0;
3803 d3_geo_area.polygonStart();
3804 },
3805 polygonEnd: function() {
3806 d3_geo_area.polygonEnd();
3807 bound.point = point;
3808 bound.lineStart = lineStart;
3809 bound.lineEnd = lineEnd;
3810 if (d3_geo_areaRingSum < 0) λ0 = -(λ1 = 180), φ0 = -(φ1 = 90); else if (dλSum > ε) φ1 = 90; else if (dλSum < -ε) φ0 = -90;
3811 range[0] = λ0, range[1] = λ1;
3812 }
3813 };
3814 function point(λ, φ) {
3815 ranges.push(range = [ λ0 = λ, λ1 = λ ]);
3816 if (φ < φ0) φ0 = φ;
3817 if (φ > φ1) φ1 = φ;
3818 }
3819 function linePoint(λ, φ) {
3820 var p = d3_geo_cartesian([ λ * d3_radians, φ * d3_radians ]);
3821 if (p0) {
3822 var normal = d3_geo_cartesianCross(p0, p), equatorial = [ normal[1], -normal[0], 0 ], inflection = d3_geo_cartesianCross(equatorial, normal);
3823 d3_geo_cartesianNormalize(inflection);
3824 inflection = d3_geo_spherical(inflection);
3825 var dλ = λ - λ_, s = dλ > 0 ? 1 : -1, λi = inflection[0] * d3_degrees * s, antimeridian = abs(dλ) > 180;
3826 if (antimeridian ^ (s * λ_ < λi && λi < s * λ)) {
3827 var φi = inflection[1] * d3_degrees;
3828 if (φi > φ1) φ1 = φi;
3829 } else if (λi = (λi + 360) % 360 - 180, antimeridian ^ (s * λ_ < λi && λi < s * λ)) {
3830 var φi = -inflection[1] * d3_degrees;
3831 if (φi < φ0) φ0 = φi;
3832 } else {
3833 if (φ < φ0) φ0 = φ;
3834 if (φ > φ1) φ1 = φ;
3835 }
3836 if (antimeridian) {
3837 if (λ < λ_) {
3838 if (angle(λ0, λ) > angle(λ0, λ1)) λ1 = λ;
3839 } else {
3840 if (angle(λ, λ1) > angle(λ0, λ1)) λ0 = λ;
3841 }
3842 } else {
3843 if (λ1 >= λ0) {
3844 if (λ < λ0) λ0 = λ;
3845 if (λ > λ1) λ1 = λ;
3846 } else {
3847 if (λ > λ_) {
3848 if (angle(λ0, λ) > angle(λ0, λ1)) λ1 = λ;
3849 } else {
3850 if (angle(λ, λ1) > angle(λ0, λ1)) λ0 = λ;
3851 }
3852 }
3853 }
3854 } else {
3855 point(λ, φ);
3856 }
3857 p0 = p, λ_ = λ;
3858 }
3859 function lineStart() {
3860 bound.point = linePoint;
3861 }
3862 function lineEnd() {
3863 range[0] = λ0, range[1] = λ1;
3864 bound.point = point;
3865 p0 = null;
3866 }
3867 function ringPoint(λ, φ) {
3868 if (p0) {
3869 var dλ = λ - λ_;
3870 dλSum += abs(dλ) > 180 ? dλ + (dλ > 0 ? 360 : -360) : dλ;
3871 } else λ__ = λ, φ__ = φ;
3872 d3_geo_area.point(λ, φ);
3873 linePoint(λ, φ);
3874 }
3875 function ringStart() {
3876 d3_geo_area.lineStart();
3877 }
3878 function ringEnd() {
3879 ringPoint(λ__, φ__);
3880 d3_geo_area.lineEnd();
3881 if (abs(dλSum) > ε) λ0 = -(λ1 = 180);
3882 range[0] = λ0, range[1] = λ1;
3883 p0 = null;
3884 }
3885 function angle(λ0, λ1) {
3886 return (λ1 -= λ0) < 0 ? λ1 + 360 : λ1;
3887 }
3888 function compareRanges(a, b) {
3889 return a[0] - b[0];
3890 }
3891 function withinRange(x, range) {
3892 return range[0] <= range[1] ? range[0] <= x && x <= range[1] : x < range[0] || range[1] < x;
3893 }
3894 return function(feature) {
3895 φ1 = λ1 = -(λ0 = φ0 = Infinity);
3896 ranges = [];
3897 d3.geo.stream(feature, bound);
3898 var n = ranges.length;
3899 if (n) {
3900 ranges.sort(compareRanges);
3901 for (var i = 1, a = ranges[0], b, merged = [ a ]; i < n; ++i) {
3902 b = ranges[i];
3903 if (withinRange(b[0], a) || withinRange(b[1], a)) {
3904 if (angle(a[0], b[1]) > angle(a[0], a[1])) a[1] = b[1];
3905 if (angle(b[0], a[1]) > angle(a[0], a[1])) a[0] = b[0];
3906 } else {
3907 merged.push(a = b);
3908 }
3909 }
3910 var best = -Infinity, dλ;
3911 for (var n = merged.length - 1, i = 0, a = merged[n], b; i <= n; a = b, ++i) {
3912 b = merged[i];
3913 if ((dλ = angle(a[1], b[0])) > best) best = dλ, λ0 = b[0], λ1 = a[1];
3914 }
3915 }
3916 ranges = range = null;
3917 return λ0 === Infinity || φ0 === Infinity ? [ [ NaN, NaN ], [ NaN, NaN ] ] : [ [ λ0, φ0 ], [ λ1, φ1 ] ];
3918 };
3919 }();
3920 d3.geo.centroid = function(object) {
3921 d3_geo_centroidW0 = d3_geo_centroidW1 = d3_geo_centroidX0 = d3_geo_centroidY0 = d3_geo_centroidZ0 = d3_geo_centroidX1 = d3_geo_centroidY1 = d3_geo_centroidZ1 = d3_geo_centroidX2 = d3_geo_centroidY2 = d3_geo_centroidZ2 = 0;
3922 d3.geo.stream(object, d3_geo_centroid);
3923 var x = d3_geo_centroidX2, y = d3_geo_centroidY2, z = d3_geo_centroidZ2, m = x * x + y * y + z * z;
3924 if (m < ε2) {
3925 x = d3_geo_centroidX1, y = d3_geo_centroidY1, z = d3_geo_centroidZ1;
3926 if (d3_geo_centroidW1 < ε) x = d3_geo_centroidX0, y = d3_geo_centroidY0, z = d3_geo_centroidZ0;
3927 m = x * x + y * y + z * z;
3928 if (m < ε2) return [ NaN, NaN ];
3929 }
3930 return [ Math.atan2(y, x) * d3_degrees, d3_asin(z / Math.sqrt(m)) * d3_degrees ];
3931 };
3932 var d3_geo_centroidW0, d3_geo_centroidW1, d3_geo_centroidX0, d3_geo_centroidY0, d3_geo_centroidZ0, d3_geo_centroidX1, d3_geo_centroidY1, d3_geo_centroidZ1, d3_geo_centroidX2, d3_geo_centroidY2, d3_geo_centroidZ2;
3933 var d3_geo_centroid = {
3934 sphere: d3_noop,
3935 point: d3_geo_centroidPoint,
3936 lineStart: d3_geo_centroidLineStart,
3937 lineEnd: d3_geo_centroidLineEnd,
3938 polygonStart: function() {
3939 d3_geo_centroid.lineStart = d3_geo_centroidRingStart;
3940 },
3941 polygonEnd: function() {
3942 d3_geo_centroid.lineStart = d3_geo_centroidLineStart;
3943 }
3944 };
3945 function d3_geo_centroidPoint(λ, φ) {
3946 λ *= d3_radians;
3947 var cosφ = Math.cos(φ *= d3_radians);
3948 d3_geo_centroidPointXYZ(cosφ * Math.cos(λ), cosφ * Math.sin(λ), Math.sin(φ));
3949 }
3950 function d3_geo_centroidPointXYZ(x, y, z) {
3951 ++d3_geo_centroidW0;
3952 d3_geo_centroidX0 += (x - d3_geo_centroidX0) / d3_geo_centroidW0;
3953 d3_geo_centroidY0 += (y - d3_geo_centroidY0) / d3_geo_centroidW0;
3954 d3_geo_centroidZ0 += (z - d3_geo_centroidZ0) / d3_geo_centroidW0;
3955 }
3956 function d3_geo_centroidLineStart() {
3957 var x0, y0, z0;
3958 d3_geo_centroid.point = function(λ, φ) {
3959 λ *= d3_radians;
3960 var cosφ = Math.cos(φ *= d3_radians);
3961 x0 = cosφ * Math.cos(λ);
3962 y0 = cosφ * Math.sin(λ);
3963 z0 = Math.sin(φ);
3964 d3_geo_centroid.point = nextPoint;
3965 d3_geo_centroidPointXYZ(x0, y0, z0);
3966 };
3967 function nextPoint(λ, φ) {
3968 λ *= d3_radians;
3969 var cosφ = Math.cos(φ *= d3_radians), x = cosφ * Math.cos(λ), y = cosφ * Math.sin(λ), z = Math.sin(φ), w = Math.atan2(Math.sqrt((w = y0 * z - z0 * y) * w + (w = z0 * x - x0 * z) * w + (w = x0 * y - y0 * x) * w), x0 * x + y0 * y + z0 * z);
3970 d3_geo_centroidW1 += w;
3971 d3_geo_centroidX1 += w * (x0 + (x0 = x));
3972 d3_geo_centroidY1 += w * (y0 + (y0 = y));
3973 d3_geo_centroidZ1 += w * (z0 + (z0 = z));
3974 d3_geo_centroidPointXYZ(x0, y0, z0);
3975 }
3976 }
3977 function d3_geo_centroidLineEnd() {
3978 d3_geo_centroid.point = d3_geo_centroidPoint;
3979 }
3980 function d3_geo_centroidRingStart() {
3981 var λ00, φ00, x0, y0, z0;
3982 d3_geo_centroid.point = function(λ, φ) {
3983 λ00 = λ, φ00 = φ;
3984 d3_geo_centroid.point = nextPoint;
3985 λ *= d3_radians;
3986 var cosφ = Math.cos(φ *= d3_radians);
3987 x0 = cosφ * Math.cos(λ);
3988 y0 = cosφ * Math.sin(λ);
3989 z0 = Math.sin(φ);
3990 d3_geo_centroidPointXYZ(x0, y0, z0);
3991 };
3992 d3_geo_centroid.lineEnd = function() {
3993 nextPoint(λ00, φ00);
3994 d3_geo_centroid.lineEnd = d3_geo_centroidLineEnd;
3995 d3_geo_centroid.point = d3_geo_centroidPoint;
3996 };
3997 function nextPoint(λ, φ) {
3998 λ *= d3_radians;
3999 var cosφ = Math.cos(φ *= d3_radians), x = cosφ * Math.cos(λ), y = cosφ * Math.sin(λ), z = Math.sin(φ), cx = y0 * z - z0 * y, cy = z0 * x - x0 * z, cz = x0 * y - y0 * x, m = Math.sqrt(cx * cx + cy * cy + cz * cz), u = x0 * x + y0 * y + z0 * z, v = m && -d3_acos(u) / m, w = Math.atan2(m, u);
4000 d3_geo_centroidX2 += v * cx;
4001 d3_geo_centroidY2 += v * cy;
4002 d3_geo_centroidZ2 += v * cz;
4003 d3_geo_centroidW1 += w;
4004 d3_geo_centroidX1 += w * (x0 + (x0 = x));
4005 d3_geo_centroidY1 += w * (y0 + (y0 = y));
4006 d3_geo_centroidZ1 += w * (z0 + (z0 = z));
4007 d3_geo_centroidPointXYZ(x0, y0, z0);
4008 }
4009 }
4010 function d3_geo_compose(a, b) {
4011 function compose(x, y) {
4012 return x = a(x, y), b(x[0], x[1]);
4013 }
4014 if (a.invert && b.invert) compose.invert = function(x, y) {
4015 return x = b.invert(x, y), x && a.invert(x[0], x[1]);
4016 };
4017 return compose;
4018 }
4019 function d3_true() {
4020 return true;
4021 }
4022 function d3_geo_clipPolygon(segments, compare, clipStartInside, interpolate, listener) {
4023 var subject = [], clip = [];
4024 segments.forEach(function(segment) {
4025 if ((n = segment.length - 1) <= 0) return;
4026 var n, p0 = segment[0], p1 = segment[n];
4027 if (d3_geo_sphericalEqual(p0, p1)) {
4028 listener.lineStart();
4029 for (var i = 0; i < n; ++i) listener.point((p0 = segment[i])[0], p0[1]);
4030 listener.lineEnd();
4031 return;
4032 }
4033 var a = new d3_geo_clipPolygonIntersection(p0, segment, null, true), b = new d3_geo_clipPolygonIntersection(p0, null, a, false);
4034 a.o = b;
4035 subject.push(a);
4036 clip.push(b);
4037 a = new d3_geo_clipPolygonIntersection(p1, segment, null, false);
4038 b = new d3_geo_clipPolygonIntersection(p1, null, a, true);
4039 a.o = b;
4040 subject.push(a);
4041 clip.push(b);
4042 });
4043 clip.sort(compare);
4044 d3_geo_clipPolygonLinkCircular(subject);
4045 d3_geo_clipPolygonLinkCircular(clip);
4046 if (!subject.length) return;
4047 for (var i = 0, entry = clipStartInside, n = clip.length; i < n; ++i) {
4048 clip[i].e = entry = !entry;
4049 }
4050 var start = subject[0], points, point;
4051 while (1) {
4052 var current = start, isSubject = true;
4053 while (current.v) if ((current = current.n) === start) return;
4054 points = current.z;
4055 listener.lineStart();
4056 do {
4057 current.v = current.o.v = true;
4058 if (current.e) {
4059 if (isSubject) {
4060 for (var i = 0, n = points.length; i < n; ++i) listener.point((point = points[i])[0], point[1]);
4061 } else {
4062 interpolate(current.x, current.n.x, 1, listener);
4063 }
4064 current = current.n;
4065 } else {
4066 if (isSubject) {
4067 points = current.p.z;
4068 for (var i = points.length - 1; i >= 0; --i) listener.point((point = points[i])[0], point[1]);
4069 } else {
4070 interpolate(current.x, current.p.x, -1, listener);
4071 }
4072 current = current.p;
4073 }
4074 current = current.o;
4075 points = current.z;
4076 isSubject = !isSubject;
4077 } while (!current.v);
4078 listener.lineEnd();
4079 }
4080 }
4081 function d3_geo_clipPolygonLinkCircular(array) {
4082 if (!(n = array.length)) return;
4083 var n, i = 0, a = array[0], b;
4084 while (++i < n) {
4085 a.n = b = array[i];
4086 b.p = a;
4087 a = b;
4088 }
4089 a.n = b = array[0];
4090 b.p = a;
4091 }
4092 function d3_geo_clipPolygonIntersection(point, points, other, entry) {
4093 this.x = point;
4094 this.z = points;
4095 this.o = other;
4096 this.e = entry;
4097 this.v = false;
4098 this.n = this.p = null;
4099 }
4100 function d3_geo_clip(pointVisible, clipLine, interpolate, clipStart) {
4101 return function(rotate, listener) {
4102 var line = clipLine(listener), rotatedClipStart = rotate.invert(clipStart[0], clipStart[1]);
4103 var clip = {
4104 point: point,
4105 lineStart: lineStart,
4106 lineEnd: lineEnd,
4107 polygonStart: function() {
4108 clip.point = pointRing;
4109 clip.lineStart = ringStart;
4110 clip.lineEnd = ringEnd;
4111 segments = [];
4112 polygon = [];
4113 },
4114 polygonEnd: function() {
4115 clip.point = point;
4116 clip.lineStart = lineStart;
4117 clip.lineEnd = lineEnd;
4118 segments = d3.merge(segments);
4119 var clipStartInside = d3_geo_pointInPolygon(rotatedClipStart, polygon);
4120 if (segments.length) {
4121 if (!polygonStarted) listener.polygonStart(), polygonStarted = true;
4122 d3_geo_clipPolygon(segments, d3_geo_clipSort, clipStartInside, interpolate, listener);
4123 } else if (clipStartInside) {
4124 if (!polygonStarted) listener.polygonStart(), polygonStarted = true;
4125 listener.lineStart();
4126 interpolate(null, null, 1, listener);
4127 listener.lineEnd();
4128 }
4129 if (polygonStarted) listener.polygonEnd(), polygonStarted = false;
4130 segments = polygon = null;
4131 },
4132 sphere: function() {
4133 listener.polygonStart();
4134 listener.lineStart();
4135 interpolate(null, null, 1, listener);
4136 listener.lineEnd();
4137 listener.polygonEnd();
4138 }
4139 };
4140 function point(λ, φ) {
4141 var point = rotate(λ, φ);
4142 if (pointVisible(λ = point[0], φ = point[1])) listener.point(λ, φ);
4143 }
4144 function pointLine(λ, φ) {
4145 var point = rotate(λ, φ);
4146 line.point(point[0], point[1]);
4147 }
4148 function lineStart() {
4149 clip.point = pointLine;
4150 line.lineStart();
4151 }
4152 function lineEnd() {
4153 clip.point = point;
4154 line.lineEnd();
4155 }
4156 var segments;
4157 var buffer = d3_geo_clipBufferListener(), ringListener = clipLine(buffer), polygonStarted = false, polygon, ring;
4158 function pointRing(λ, φ) {
4159 ring.push([ λ, φ ]);
4160 var point = rotate(λ, φ);
4161 ringListener.point(point[0], point[1]);
4162 }
4163 function ringStart() {
4164 ringListener.lineStart();
4165 ring = [];
4166 }
4167 function ringEnd() {
4168 pointRing(ring[0][0], ring[0][1]);
4169 ringListener.lineEnd();
4170 var clean = ringListener.clean(), ringSegments = buffer.buffer(), segment, n = ringSegments.length;
4171 ring.pop();
4172 polygon.push(ring);
4173 ring = null;
4174 if (!n) return;
4175 if (clean & 1) {
4176 segment = ringSegments[0];
4177 var n = segment.length - 1, i = -1, point;
4178 if (n > 0) {
4179 if (!polygonStarted) listener.polygonStart(), polygonStarted = true;
4180 listener.lineStart();
4181 while (++i < n) listener.point((point = segment[i])[0], point[1]);
4182 listener.lineEnd();
4183 }
4184 return;
4185 }
4186 if (n > 1 && clean & 2) ringSegments.push(ringSegments.pop().concat(ringSegments.shift()));
4187 segments.push(ringSegments.filter(d3_geo_clipSegmentLength1));
4188 }
4189 return clip;
4190 };
4191 }
4192 function d3_geo_clipSegmentLength1(segment) {
4193 return segment.length > 1;
4194 }
4195 function d3_geo_clipBufferListener() {
4196 var lines = [], line;
4197 return {
4198 lineStart: function() {
4199 lines.push(line = []);
4200 },
4201 point: function(λ, φ) {
4202 line.push([ λ, φ ]);
4203 },
4204 lineEnd: d3_noop,
4205 buffer: function() {
4206 var buffer = lines;
4207 lines = [];
4208 line = null;
4209 return buffer;
4210 },
4211 rejoin: function() {
4212 if (lines.length > 1) lines.push(lines.pop().concat(lines.shift()));
4213 }
4214 };
4215 }
4216 function d3_geo_clipSort(a, b) {
4217 return ((a = a.x)[0] < 0 ? a[1] - halfπ - ε : halfπ - a[1]) - ((b = b.x)[0] < 0 ? b[1] - halfπ - ε : halfπ - b[1]);
4218 }
4219 var d3_geo_clipAntimeridian = d3_geo_clip(d3_true, d3_geo_clipAntimeridianLine, d3_geo_clipAntimeridianInterpolate, [ -π, -π / 2 ]);
4220 function d3_geo_clipAntimeridianLine(listener) {
4221 var λ0 = NaN, φ0 = NaN, sλ0 = NaN, clean;
4222 return {
4223 lineStart: function() {
4224 listener.lineStart();
4225 clean = 1;
4226 },
4227 point: function(λ1, φ1) {
4228 var sλ1 = λ1 > 0 ? π : -π, dλ = abs(λ1 - λ0);
4229 if (abs(dλ - π) < ε) {
4230 listener.point(λ0, φ0 = (φ0 + φ1) / 2 > 0 ? halfπ : -halfπ);
4231 listener.point(sλ0, φ0);
4232 listener.lineEnd();
4233 listener.lineStart();
4234 listener.point(sλ1, φ0);
4235 listener.point(λ1, φ0);
4236 clean = 0;
4237 } else if (sλ0 !== sλ1 && dλ >= π) {
4238 if (abs(λ0 - sλ0) < ε) λ0 -= sλ0 * ε;
4239 if (abs(λ1 - sλ1) < ε) λ1 -= sλ1 * ε;
4240 φ0 = d3_geo_clipAntimeridianIntersect(λ0, φ0, λ1, φ1);
4241 listener.point(sλ0, φ0);
4242 listener.lineEnd();
4243 listener.lineStart();
4244 listener.point(sλ1, φ0);
4245 clean = 0;
4246 }
4247 listener.point(λ0 = λ1, φ0 = φ1);
4248 sλ0 = sλ1;
4249 },
4250 lineEnd: function() {
4251 listener.lineEnd();
4252 λ0 = φ0 = NaN;
4253 },
4254 clean: function() {
4255 return 2 - clean;
4256 }
4257 };
4258 }
4259 function d3_geo_clipAntimeridianIntersect(λ0, φ0, λ1, φ1) {
4260 var cosφ0, cosφ1, sinλ0_λ1 = Math.sin(λ0 - λ1);
4261 return abs(sinλ0_λ1) > ε ? Math.atan((Math.sin(φ0) * (cosφ1 = Math.cos(φ1)) * Math.sin(λ1) - Math.sin(φ1) * (cosφ0 = Math.cos(φ0)) * Math.sin(λ0)) / (cosφ0 * cosφ1 * sinλ0_λ1)) : (φ0 + φ1) / 2;
4262 }
4263 function d3_geo_clipAntimeridianInterpolate(from, to, direction, listener) {
4264 var φ;
4265 if (from == null) {
4266 φ = direction * halfπ;
4267 listener.point(-π, φ);
4268 listener.point(0, φ);
4269 listener.point(π, φ);
4270 listener.point(π, 0);
4271 listener.point(π, -φ);
4272 listener.point(0, -φ);
4273 listener.point(-π, -φ);
4274 listener.point(-π, 0);
4275 listener.point(-π, φ);
4276 } else if (abs(from[0] - to[0]) > ε) {
4277 var s = from[0] < to[0] ? π : -π;
4278 φ = direction * s / 2;
4279 listener.point(-s, φ);
4280 listener.point(0, φ);
4281 listener.point(s, φ);
4282 } else {
4283 listener.point(to[0], to[1]);
4284 }
4285 }
4286 function d3_geo_pointInPolygon(point, polygon) {
4287 var meridian = point[0], parallel = point[1], meridianNormal = [ Math.sin(meridian), -Math.cos(meridian), 0 ], polarAngle = 0, winding = 0;
4288 d3_geo_areaRingSum.reset();
4289 for (var i = 0, n = polygon.length; i < n; ++i) {
4290 var ring = polygon[i], m = ring.length;
4291 if (!m) continue;
4292 var point0 = ring[0], λ0 = point0[0], φ0 = point0[1] / 2 + π / 4, sinφ0 = Math.sin(φ0), cosφ0 = Math.cos(φ0), j = 1;
4293 while (true) {
4294 if (j === m) j = 0;
4295 point = ring[j];
4296 var λ = point[0], φ = point[1] / 2 + π / 4, sinφ = Math.sin(φ), cosφ = Math.cos(φ), dλ = λ - λ0, sdλ = dλ >= 0 ? 1 : -1, adλ = sdλ * dλ, antimeridian = adλ > π, k = sinφ0 * sinφ;
4297 d3_geo_areaRingSum.add(Math.atan2(k * sdλ * Math.sin(adλ), cosφ0 * cosφ + k * Math.cos(adλ)));
4298 polarAngle += antimeridian ? dλ + sdλ * τ : dλ;
4299 if (antimeridian ^ λ0 >= meridian ^ λ >= meridian) {
4300 var arc = d3_geo_cartesianCross(d3_geo_cartesian(point0), d3_geo_cartesian(point));
4301 d3_geo_cartesianNormalize(arc);
4302 var intersection = d3_geo_cartesianCross(meridianNormal, arc);
4303 d3_geo_cartesianNormalize(intersection);
4304 var φarc = (antimeridian ^ dλ >= 0 ? -1 : 1) * d3_asin(intersection[2]);
4305 if (parallel > φarc || parallel === φarc && (arc[0] || arc[1])) {
4306 winding += antimeridian ^ dλ >= 0 ? 1 : -1;
4307 }
4308 }
4309 if (!j++) break;
4310 λ0 = λ, sinφ0 = sinφ, cosφ0 = cosφ, point0 = point;
4311 }
4312 }
4313 return (polarAngle < -ε || polarAngle < ε && d3_geo_areaRingSum < -ε) ^ winding & 1;
4314 }
4315 function d3_geo_clipCircle(radius) {
4316 var cr = Math.cos(radius), smallRadius = cr > 0, notHemisphere = abs(cr) > ε, interpolate = d3_geo_circleInterpolate(radius, 6 * d3_radians);
4317 return d3_geo_clip(visible, clipLine, interpolate, smallRadius ? [ 0, -radius ] : [ -π, radius - π ]);
4318 function visible(λ, φ) {
4319 return Math.cos(λ) * Math.cos(φ) > cr;
4320 }
4321 function clipLine(listener) {
4322 var point0, c0, v0, v00, clean;
4323 return {
4324 lineStart: function() {
4325 v00 = v0 = false;
4326 clean = 1;
4327 },
4328 point: function(λ, φ) {
4329 var point1 = [ λ, φ ], point2, v = visible(λ, φ), c = smallRadius ? v ? 0 : code(λ, φ) : v ? code(λ + (λ < 0 ? π : -π), φ) : 0;
4330 if (!point0 && (v00 = v0 = v)) listener.lineStart();
4331 if (v !== v0) {
4332 point2 = intersect(point0, point1);
4333 if (d3_geo_sphericalEqual(point0, point2) || d3_geo_sphericalEqual(point1, point2)) {
4334 point1[0] += ε;
4335 point1[1] += ε;
4336 v = visible(point1[0], point1[1]);
4337 }
4338 }
4339 if (v !== v0) {
4340 clean = 0;
4341 if (v) {
4342 listener.lineStart();
4343 point2 = intersect(point1, point0);
4344 listener.point(point2[0], point2[1]);
4345 } else {
4346 point2 = intersect(point0, point1);
4347 listener.point(point2[0], point2[1]);
4348 listener.lineEnd();
4349 }
4350 point0 = point2;
4351 } else if (notHemisphere && point0 && smallRadius ^ v) {
4352 var t;
4353 if (!(c & c0) && (t = intersect(point1, point0, true))) {
4354 clean = 0;
4355 if (smallRadius) {
4356 listener.lineStart();
4357 listener.point(t[0][0], t[0][1]);
4358 listener.point(t[1][0], t[1][1]);
4359 listener.lineEnd();
4360 } else {
4361 listener.point(t[1][0], t[1][1]);
4362 listener.lineEnd();
4363 listener.lineStart();
4364 listener.point(t[0][0], t[0][1]);
4365 }
4366 }
4367 }
4368 if (v && (!point0 || !d3_geo_sphericalEqual(point0, point1))) {
4369 listener.point(point1[0], point1[1]);
4370 }
4371 point0 = point1, v0 = v, c0 = c;
4372 },
4373 lineEnd: function() {
4374 if (v0) listener.lineEnd();
4375 point0 = null;
4376 },
4377 clean: function() {
4378 return clean | (v00 && v0) << 1;
4379 }
4380 };
4381 }
4382 function intersect(a, b, two) {
4383 var pa = d3_geo_cartesian(a), pb = d3_geo_cartesian(b);
4384 var n1 = [ 1, 0, 0 ], n2 = d3_geo_cartesianCross(pa, pb), n2n2 = d3_geo_cartesianDot(n2, n2), n1n2 = n2[0], determinant = n2n2 - n1n2 * n1n2;
4385 if (!determinant) return !two && a;
4386 var c1 = cr * n2n2 / determinant, c2 = -cr * n1n2 / determinant, n1xn2 = d3_geo_cartesianCross(n1, n2), A = d3_geo_cartesianScale(n1, c1), B = d3_geo_cartesianScale(n2, c2);
4387 d3_geo_cartesianAdd(A, B);
4388 var u = n1xn2, w = d3_geo_cartesianDot(A, u), uu = d3_geo_cartesianDot(u, u), t2 = w * w - uu * (d3_geo_cartesianDot(A, A) - 1);
4389 if (t2 < 0) return;
4390 var t = Math.sqrt(t2), q = d3_geo_cartesianScale(u, (-w - t) / uu);
4391 d3_geo_cartesianAdd(q, A);
4392 q = d3_geo_spherical(q);
4393 if (!two) return q;
4394 var λ0 = a[0], λ1 = b[0], φ0 = a[1], φ1 = b[1], z;
4395 if (λ1 < λ0) z = λ0, λ0 = λ1, λ1 = z;
4396 var δλ = λ1 - λ0, polar = abs(δλ - π) < ε, meridian = polar || δλ < ε;
4397 if (!polar && φ1 < φ0) z = φ0, φ0 = φ1, φ1 = z;
4398 if (meridian ? polar ? φ0 + φ1 > 0 ^ q[1] < (abs(q[0] - λ0) < ε ? φ0 : φ1) : φ0 <= q[1] && q[1] <= φ1 : δλ > π ^ (λ0 <= q[0] && q[0] <= λ1)) {
4399 var q1 = d3_geo_cartesianScale(u, (-w + t) / uu);
4400 d3_geo_cartesianAdd(q1, A);
4401 return [ q, d3_geo_spherical(q1) ];
4402 }
4403 }
4404 function code(λ, φ) {
4405 var r = smallRadius ? radius : π - radius, code = 0;
4406 if (λ < -r) code |= 1; else if (λ > r) code |= 2;
4407 if (φ < -r) code |= 4; else if (φ > r) code |= 8;
4408 return code;
4409 }
4410 }
4411 function d3_geom_clipLine(x0, y0, x1, y1) {
4412 return function(line) {
4413 var a = line.a, b = line.b, ax = a.x, ay = a.y, bx = b.x, by = b.y, t0 = 0, t1 = 1, dx = bx - ax, dy = by - ay, r;
4414 r = x0 - ax;
4415 if (!dx && r > 0) return;
4416 r /= dx;
4417 if (dx < 0) {
4418 if (r < t0) return;
4419 if (r < t1) t1 = r;
4420 } else if (dx > 0) {
4421 if (r > t1) return;
4422 if (r > t0) t0 = r;
4423 }
4424 r = x1 - ax;
4425 if (!dx && r < 0) return;
4426 r /= dx;
4427 if (dx < 0) {
4428 if (r > t1) return;
4429 if (r > t0) t0 = r;
4430 } else if (dx > 0) {
4431 if (r < t0) return;
4432 if (r < t1) t1 = r;
4433 }
4434 r = y0 - ay;
4435 if (!dy && r > 0) return;
4436 r /= dy;
4437 if (dy < 0) {
4438 if (r < t0) return;
4439 if (r < t1) t1 = r;
4440 } else if (dy > 0) {
4441 if (r > t1) return;
4442 if (r > t0) t0 = r;
4443 }
4444 r = y1 - ay;
4445 if (!dy && r < 0) return;
4446 r /= dy;
4447 if (dy < 0) {
4448 if (r > t1) return;
4449 if (r > t0) t0 = r;
4450 } else if (dy > 0) {
4451 if (r < t0) return;
4452 if (r < t1) t1 = r;
4453 }
4454 if (t0 > 0) line.a = {
4455 x: ax + t0 * dx,
4456 y: ay + t0 * dy
4457 };
4458 if (t1 < 1) line.b = {
4459 x: ax + t1 * dx,
4460 y: ay + t1 * dy
4461 };
4462 return line;
4463 };
4464 }
4465 var d3_geo_clipExtentMAX = 1e9;
4466 d3.geo.clipExtent = function() {
4467 var x0, y0, x1, y1, stream, clip, clipExtent = {
4468 stream: function(output) {
4469 if (stream) stream.valid = false;
4470 stream = clip(output);
4471 stream.valid = true;
4472 return stream;
4473 },
4474 extent: function(_) {
4475 if (!arguments.length) return [ [ x0, y0 ], [ x1, y1 ] ];
4476 clip = d3_geo_clipExtent(x0 = +_[0][0], y0 = +_[0][1], x1 = +_[1][0], y1 = +_[1][1]);
4477 if (stream) stream.valid = false, stream = null;
4478 return clipExtent;
4479 }
4480 };
4481 return clipExtent.extent([ [ 0, 0 ], [ 960, 500 ] ]);
4482 };
4483 function d3_geo_clipExtent(x0, y0, x1, y1) {
4484 return function(listener) {
4485 var listener_ = listener, bufferListener = d3_geo_clipBufferListener(), clipLine = d3_geom_clipLine(x0, y0, x1, y1), segments, polygon, ring;
4486 var clip = {
4487 point: point,
4488 lineStart: lineStart,
4489 lineEnd: lineEnd,
4490 polygonStart: function() {
4491 listener = bufferListener;
4492 segments = [];
4493 polygon = [];
4494 clean = true;
4495 },
4496 polygonEnd: function() {
4497 listener = listener_;
4498 segments = d3.merge(segments);
4499 var clipStartInside = insidePolygon([ x0, y1 ]), inside = clean && clipStartInside, visible = segments.length;
4500 if (inside || visible) {
4501 listener.polygonStart();
4502 if (inside) {
4503 listener.lineStart();
4504 interpolate(null, null, 1, listener);
4505 listener.lineEnd();
4506 }
4507 if (visible) {
4508 d3_geo_clipPolygon(segments, compare, clipStartInside, interpolate, listener);
4509 }
4510 listener.polygonEnd();
4511 }
4512 segments = polygon = ring = null;
4513 }
4514 };
4515 function insidePolygon(p) {
4516 var wn = 0, n = polygon.length, y = p[1];
4517 for (var i = 0; i < n; ++i) {
4518 for (var j = 1, v = polygon[i], m = v.length, a = v[0], b; j < m; ++j) {
4519 b = v[j];
4520 if (a[1] <= y) {
4521 if (b[1] > y && d3_cross2d(a, b, p) > 0) ++wn;
4522 } else {
4523 if (b[1] <= y && d3_cross2d(a, b, p) < 0) --wn;
4524 }
4525 a = b;
4526 }
4527 }
4528 return wn !== 0;
4529 }
4530 function interpolate(from, to, direction, listener) {
4531 var a = 0, a1 = 0;
4532 if (from == null || (a = corner(from, direction)) !== (a1 = corner(to, direction)) || comparePoints(from, to) < 0 ^ direction > 0) {
4533 do {
4534 listener.point(a === 0 || a === 3 ? x0 : x1, a > 1 ? y1 : y0);
4535 } while ((a = (a + direction + 4) % 4) !== a1);
4536 } else {
4537 listener.point(to[0], to[1]);
4538 }
4539 }
4540 function pointVisible(x, y) {
4541 return x0 <= x && x <= x1 && y0 <= y && y <= y1;
4542 }
4543 function point(x, y) {
4544 if (pointVisible(x, y)) listener.point(x, y);
4545 }
4546 var x__, y__, v__, x_, y_, v_, first, clean;
4547 function lineStart() {
4548 clip.point = linePoint;
4549 if (polygon) polygon.push(ring = []);
4550 first = true;
4551 v_ = false;
4552 x_ = y_ = NaN;
4553 }
4554 function lineEnd() {
4555 if (segments) {
4556 linePoint(x__, y__);
4557 if (v__ && v_) bufferListener.rejoin();
4558 segments.push(bufferListener.buffer());
4559 }
4560 clip.point = point;
4561 if (v_) listener.lineEnd();
4562 }
4563 function linePoint(x, y) {
4564 x = Math.max(-d3_geo_clipExtentMAX, Math.min(d3_geo_clipExtentMAX, x));
4565 y = Math.max(-d3_geo_clipExtentMAX, Math.min(d3_geo_clipExtentMAX, y));
4566 var v = pointVisible(x, y);
4567 if (polygon) ring.push([ x, y ]);
4568 if (first) {
4569 x__ = x, y__ = y, v__ = v;
4570 first = false;
4571 if (v) {
4572 listener.lineStart();
4573 listener.point(x, y);
4574 }
4575 } else {
4576 if (v && v_) listener.point(x, y); else {
4577 var l = {
4578 a: {
4579 x: x_,
4580 y: y_
4581 },
4582 b: {
4583 x: x,
4584 y: y
4585 }
4586 };
4587 if (clipLine(l)) {
4588 if (!v_) {
4589 listener.lineStart();
4590 listener.point(l.a.x, l.a.y);
4591 }
4592 listener.point(l.b.x, l.b.y);
4593 if (!v) listener.lineEnd();
4594 clean = false;
4595 } else if (v) {
4596 listener.lineStart();
4597 listener.point(x, y);
4598 clean = false;
4599 }
4600 }
4601 }
4602 x_ = x, y_ = y, v_ = v;
4603 }
4604 return clip;
4605 };
4606 function corner(p, direction) {
4607 return abs(p[0] - x0) < ε ? direction > 0 ? 0 : 3 : abs(p[0] - x1) < ε ? direction > 0 ? 2 : 1 : abs(p[1] - y0) < ε ? direction > 0 ? 1 : 0 : direction > 0 ? 3 : 2;
4608 }
4609 function compare(a, b) {
4610 return comparePoints(a.x, b.x);
4611 }
4612 function comparePoints(a, b) {
4613 var ca = corner(a, 1), cb = corner(b, 1);
4614 return ca !== cb ? ca - cb : ca === 0 ? b[1] - a[1] : ca === 1 ? a[0] - b[0] : ca === 2 ? a[1] - b[1] : b[0] - a[0];
4615 }
4616 }
4617 function d3_geo_conic(projectAt) {
4618 var φ0 = 0, φ1 = π / 3, m = d3_geo_projectionMutator(projectAt), p = m(φ0, φ1);
4619 p.parallels = function(_) {
4620 if (!arguments.length) return [ φ0 / π * 180, φ1 / π * 180 ];
4621 return m(φ0 = _[0] * π / 180, φ1 = _[1] * π / 180);
4622 };
4623 return p;
4624 }
4625 function d3_geo_conicEqualArea(φ0, φ1) {
4626 var sinφ0 = Math.sin(φ0), n = (sinφ0 + Math.sin(φ1)) / 2, C = 1 + sinφ0 * (2 * n - sinφ0), ρ0 = Math.sqrt(C) / n;
4627 function forward(λ, φ) {
4628 var ρ = Math.sqrt(C - 2 * n * Math.sin(φ)) / n;
4629 return [ ρ * Math.sin(λ *= n), ρ0 - ρ * Math.cos(λ) ];
4630 }
4631 forward.invert = function(x, y) {
4632 var ρ0_y = ρ0 - y;
4633 return [ Math.atan2(x, ρ0_y) / n, d3_asin((C - (x * x + ρ0_y * ρ0_y) * n * n) / (2 * n)) ];
4634 };
4635 return forward;
4636 }
4637 (d3.geo.conicEqualArea = function() {
4638 return d3_geo_conic(d3_geo_conicEqualArea);
4639 }).raw = d3_geo_conicEqualArea;
4640 d3.geo.albers = function() {
4641 return d3.geo.conicEqualArea().rotate([ 96, 0 ]).center([ -.6, 38.7 ]).parallels([ 29.5, 45.5 ]).scale(1070);
4642 };
4643 d3.geo.albersUsa = function() {
4644 var lower48 = d3.geo.albers();
4645 var alaska = d3.geo.conicEqualArea().rotate([ 154, 0 ]).center([ -2, 58.5 ]).parallels([ 55, 65 ]);
4646 var hawaii = d3.geo.conicEqualArea().rotate([ 157, 0 ]).center([ -3, 19.9 ]).parallels([ 8, 18 ]);
4647 var point, pointStream = {
4648 point: function(x, y) {
4649 point = [ x, y ];
4650 }
4651 }, lower48Point, alaskaPoint, hawaiiPoint;
4652 function albersUsa(coordinates) {
4653 var x = coordinates[0], y = coordinates[1];
4654 point = null;
4655 (lower48Point(x, y), point) || (alaskaPoint(x, y), point) || hawaiiPoint(x, y);
4656 return point;
4657 }
4658 albersUsa.invert = function(coordinates) {
4659 var k = lower48.scale(), t = lower48.translate(), x = (coordinates[0] - t[0]) / k, y = (coordinates[1] - t[1]) / k;
4660 return (y >= .12 && y < .234 && x >= -.425 && x < -.214 ? alaska : y >= .166 && y < .234 && x >= -.214 && x < -.115 ? hawaii : lower48).invert(coordinates);
4661 };
4662 albersUsa.stream = function(stream) {
4663 var lower48Stream = lower48.stream(stream), alaskaStream = alaska.stream(stream), hawaiiStream = hawaii.stream(stream);
4664 return {
4665 point: function(x, y) {
4666 lower48Stream.point(x, y);
4667 alaskaStream.point(x, y);
4668 hawaiiStream.point(x, y);
4669 },
4670 sphere: function() {
4671 lower48Stream.sphere();
4672 alaskaStream.sphere();
4673 hawaiiStream.sphere();
4674 },
4675 lineStart: function() {
4676 lower48Stream.lineStart();
4677 alaskaStream.lineStart();
4678 hawaiiStream.lineStart();
4679 },
4680 lineEnd: function() {
4681 lower48Stream.lineEnd();
4682 alaskaStream.lineEnd();
4683 hawaiiStream.lineEnd();
4684 },
4685 polygonStart: function() {
4686 lower48Stream.polygonStart();
4687 alaskaStream.polygonStart();
4688 hawaiiStream.polygonStart();
4689 },
4690 polygonEnd: function() {
4691 lower48Stream.polygonEnd();
4692 alaskaStream.polygonEnd();
4693 hawaiiStream.polygonEnd();
4694 }
4695 };
4696 };
4697 albersUsa.precision = function(_) {
4698 if (!arguments.length) return lower48.precision();
4699 lower48.precision(_);
4700 alaska.precision(_);
4701 hawaii.precision(_);
4702 return albersUsa;
4703 };
4704 albersUsa.scale = function(_) {
4705 if (!arguments.length) return lower48.scale();
4706 lower48.scale(_);
4707 alaska.scale(_ * .35);
4708 hawaii.scale(_);
4709 return albersUsa.translate(lower48.translate());
4710 };
4711 albersUsa.translate = function(_) {
4712 if (!arguments.length) return lower48.translate();
4713 var k = lower48.scale(), x = +_[0], y = +_[1];
4714 lower48Point = lower48.translate(_).clipExtent([ [ x - .455 * k, y - .238 * k ], [ x + .455 * k, y + .238 * k ] ]).stream(pointStream).point;
4715 alaskaPoint = alaska.translate([ x - .307 * k, y + .201 * k ]).clipExtent([ [ x - .425 * k + ε, y + .12 * k + ε ], [ x - .214 * k - ε, y + .234 * k - ε ] ]).stream(pointStream).point;
4716 hawaiiPoint = hawaii.translate([ x - .205 * k, y + .212 * k ]).clipExtent([ [ x - .214 * k + ε, y + .166 * k + ε ], [ x - .115 * k - ε, y + .234 * k - ε ] ]).stream(pointStream).point;
4717 return albersUsa;
4718 };
4719 return albersUsa.scale(1070);
4720 };
4721 var d3_geo_pathAreaSum, d3_geo_pathAreaPolygon, d3_geo_pathArea = {
4722 point: d3_noop,
4723 lineStart: d3_noop,
4724 lineEnd: d3_noop,
4725 polygonStart: function() {
4726 d3_geo_pathAreaPolygon = 0;
4727 d3_geo_pathArea.lineStart = d3_geo_pathAreaRingStart;
4728 },
4729 polygonEnd: function() {
4730 d3_geo_pathArea.lineStart = d3_geo_pathArea.lineEnd = d3_geo_pathArea.point = d3_noop;
4731 d3_geo_pathAreaSum += abs(d3_geo_pathAreaPolygon / 2);
4732 }
4733 };
4734 function d3_geo_pathAreaRingStart() {
4735 var x00, y00, x0, y0;
4736 d3_geo_pathArea.point = function(x, y) {
4737 d3_geo_pathArea.point = nextPoint;
4738 x00 = x0 = x, y00 = y0 = y;
4739 };
4740 function nextPoint(x, y) {
4741 d3_geo_pathAreaPolygon += y0 * x - x0 * y;
4742 x0 = x, y0 = y;
4743 }
4744 d3_geo_pathArea.lineEnd = function() {
4745 nextPoint(x00, y00);
4746 };
4747 }
4748 var d3_geo_pathBoundsX0, d3_geo_pathBoundsY0, d3_geo_pathBoundsX1, d3_geo_pathBoundsY1;
4749 var d3_geo_pathBounds = {
4750 point: d3_geo_pathBoundsPoint,
4751 lineStart: d3_noop,
4752 lineEnd: d3_noop,
4753 polygonStart: d3_noop,
4754 polygonEnd: d3_noop
4755 };
4756 function d3_geo_pathBoundsPoint(x, y) {
4757 if (x < d3_geo_pathBoundsX0) d3_geo_pathBoundsX0 = x;
4758 if (x > d3_geo_pathBoundsX1) d3_geo_pathBoundsX1 = x;
4759 if (y < d3_geo_pathBoundsY0) d3_geo_pathBoundsY0 = y;
4760 if (y > d3_geo_pathBoundsY1) d3_geo_pathBoundsY1 = y;
4761 }
4762 function d3_geo_pathBuffer() {
4763 var pointCircle = d3_geo_pathBufferCircle(4.5), buffer = [];
4764 var stream = {
4765 point: point,
4766 lineStart: function() {
4767 stream.point = pointLineStart;
4768 },
4769 lineEnd: lineEnd,
4770 polygonStart: function() {
4771 stream.lineEnd = lineEndPolygon;
4772 },
4773 polygonEnd: function() {
4774 stream.lineEnd = lineEnd;
4775 stream.point = point;
4776 },
4777 pointRadius: function(_) {
4778 pointCircle = d3_geo_pathBufferCircle(_);
4779 return stream;
4780 },
4781 result: function() {
4782 if (buffer.length) {
4783 var result = buffer.join("");
4784 buffer = [];
4785 return result;
4786 }
4787 }
4788 };
4789 function point(x, y) {
4790 buffer.push("M", x, ",", y, pointCircle);
4791 }
4792 function pointLineStart(x, y) {
4793 buffer.push("M", x, ",", y);
4794 stream.point = pointLine;
4795 }
4796 function pointLine(x, y) {
4797 buffer.push("L", x, ",", y);
4798 }
4799 function lineEnd() {
4800 stream.point = point;
4801 }
4802 function lineEndPolygon() {
4803 buffer.push("Z");
4804 }
4805 return stream;
4806 }
4807 function d3_geo_pathBufferCircle(radius) {
4808 return "m0," + radius + "a" + radius + "," + radius + " 0 1,1 0," + -2 * radius + "a" + radius + "," + radius + " 0 1,1 0," + 2 * radius + "z";
4809 }
4810 var d3_geo_pathCentroid = {
4811 point: d3_geo_pathCentroidPoint,
4812 lineStart: d3_geo_pathCentroidLineStart,
4813 lineEnd: d3_geo_pathCentroidLineEnd,
4814 polygonStart: function() {
4815 d3_geo_pathCentroid.lineStart = d3_geo_pathCentroidRingStart;
4816 },
4817 polygonEnd: function() {
4818 d3_geo_pathCentroid.point = d3_geo_pathCentroidPoint;
4819 d3_geo_pathCentroid.lineStart = d3_geo_pathCentroidLineStart;
4820 d3_geo_pathCentroid.lineEnd = d3_geo_pathCentroidLineEnd;
4821 }
4822 };
4823 function d3_geo_pathCentroidPoint(x, y) {
4824 d3_geo_centroidX0 += x;
4825 d3_geo_centroidY0 += y;
4826 ++d3_geo_centroidZ0;
4827 }
4828 function d3_geo_pathCentroidLineStart() {
4829 var x0, y0;
4830 d3_geo_pathCentroid.point = function(x, y) {
4831 d3_geo_pathCentroid.point = nextPoint;
4832 d3_geo_pathCentroidPoint(x0 = x, y0 = y);
4833 };
4834 function nextPoint(x, y) {
4835 var dx = x - x0, dy = y - y0, z = Math.sqrt(dx * dx + dy * dy);
4836 d3_geo_centroidX1 += z * (x0 + x) / 2;
4837 d3_geo_centroidY1 += z * (y0 + y) / 2;
4838 d3_geo_centroidZ1 += z;
4839 d3_geo_pathCentroidPoint(x0 = x, y0 = y);
4840 }
4841 }
4842 function d3_geo_pathCentroidLineEnd() {
4843 d3_geo_pathCentroid.point = d3_geo_pathCentroidPoint;
4844 }
4845 function d3_geo_pathCentroidRingStart() {
4846 var x00, y00, x0, y0;
4847 d3_geo_pathCentroid.point = function(x, y) {
4848 d3_geo_pathCentroid.point = nextPoint;
4849 d3_geo_pathCentroidPoint(x00 = x0 = x, y00 = y0 = y);
4850 };
4851 function nextPoint(x, y) {
4852 var dx = x - x0, dy = y - y0, z = Math.sqrt(dx * dx + dy * dy);
4853 d3_geo_centroidX1 += z * (x0 + x) / 2;
4854 d3_geo_centroidY1 += z * (y0 + y) / 2;
4855 d3_geo_centroidZ1 += z;
4856 z = y0 * x - x0 * y;
4857 d3_geo_centroidX2 += z * (x0 + x);
4858 d3_geo_centroidY2 += z * (y0 + y);
4859 d3_geo_centroidZ2 += z * 3;
4860 d3_geo_pathCentroidPoint(x0 = x, y0 = y);
4861 }
4862 d3_geo_pathCentroid.lineEnd = function() {
4863 nextPoint(x00, y00);
4864 };
4865 }
4866 function d3_geo_pathContext(context) {
4867 var pointRadius = 4.5;
4868 var stream = {
4869 point: point,
4870 lineStart: function() {
4871 stream.point = pointLineStart;
4872 },
4873 lineEnd: lineEnd,
4874 polygonStart: function() {
4875 stream.lineEnd = lineEndPolygon;
4876 },
4877 polygonEnd: function() {
4878 stream.lineEnd = lineEnd;
4879 stream.point = point;
4880 },
4881 pointRadius: function(_) {
4882 pointRadius = _;
4883 return stream;
4884 },
4885 result: d3_noop
4886 };
4887 function point(x, y) {
4888 context.moveTo(x + pointRadius, y);
4889 context.arc(x, y, pointRadius, 0, τ);
4890 }
4891 function pointLineStart(x, y) {
4892 context.moveTo(x, y);
4893 stream.point = pointLine;
4894 }
4895 function pointLine(x, y) {
4896 context.lineTo(x, y);
4897 }
4898 function lineEnd() {
4899 stream.point = point;
4900 }
4901 function lineEndPolygon() {
4902 context.closePath();
4903 }
4904 return stream;
4905 }
4906 function d3_geo_resample(project) {
4907 var δ2 = .5, cosMinDistance = Math.cos(30 * d3_radians), maxDepth = 16;
4908 function resample(stream) {
4909 return (maxDepth ? resampleRecursive : resampleNone)(stream);
4910 }
4911 function resampleNone(stream) {
4912 return d3_geo_transformPoint(stream, function(x, y) {
4913 x = project(x, y);
4914 stream.point(x[0], x[1]);
4915 });
4916 }
4917 function resampleRecursive(stream) {
4918 var λ00, φ00, x00, y00, a00, b00, c00, λ0, x0, y0, a0, b0, c0;
4919 var resample = {
4920 point: point,
4921 lineStart: lineStart,
4922 lineEnd: lineEnd,
4923 polygonStart: function() {
4924 stream.polygonStart();
4925 resample.lineStart = ringStart;
4926 },
4927 polygonEnd: function() {
4928 stream.polygonEnd();
4929 resample.lineStart = lineStart;
4930 }
4931 };
4932 function point(x, y) {
4933 x = project(x, y);
4934 stream.point(x[0], x[1]);
4935 }
4936 function lineStart() {
4937 x0 = NaN;
4938 resample.point = linePoint;
4939 stream.lineStart();
4940 }
4941 function linePoint(λ, φ) {
4942 var c = d3_geo_cartesian([ λ, φ ]), p = project(λ, φ);
4943 resampleLineTo(x0, y0, λ0, a0, b0, c0, x0 = p[0], y0 = p[1], λ0 = λ, a0 = c[0], b0 = c[1], c0 = c[2], maxDepth, stream);
4944 stream.point(x0, y0);
4945 }
4946 function lineEnd() {
4947 resample.point = point;
4948 stream.lineEnd();
4949 }
4950 function ringStart() {
4951 lineStart();
4952 resample.point = ringPoint;
4953 resample.lineEnd = ringEnd;
4954 }
4955 function ringPoint(λ, φ) {
4956 linePoint(λ00 = λ, φ00 = φ), x00 = x0, y00 = y0, a00 = a0, b00 = b0, c00 = c0;
4957 resample.point = linePoint;
4958 }
4959 function ringEnd() {
4960 resampleLineTo(x0, y0, λ0, a0, b0, c0, x00, y00, λ00, a00, b00, c00, maxDepth, stream);
4961 resample.lineEnd = lineEnd;
4962 lineEnd();
4963 }
4964 return resample;
4965 }
4966 function resampleLineTo(x0, y0, λ0, a0, b0, c0, x1, y1, λ1, a1, b1, c1, depth, stream) {
4967 var dx = x1 - x0, dy = y1 - y0, d2 = dx * dx + dy * dy;
4968 if (d2 > 4 * δ2 && depth--) {
4969 var a = a0 + a1, b = b0 + b1, c = c0 + c1, m = Math.sqrt(a * a + b * b + c * c), φ2 = Math.asin(c /= m), λ2 = abs(abs(c) - 1) < ε || abs0 - λ1) < ε ? (λ0 + λ1) / 2 : Math.atan2(b, a), p = project(λ2, φ2), x2 = p[0], y2 = p[1], dx2 = x2 - x0, dy2 = y2 - y0, dz = dy * dx2 - dx * dy2;
4970 if (dz * dz / d2 > δ2 || abs((dx * dx2 + dy * dy2) / d2 - .5) > .3 || a0 * a1 + b0 * b1 + c0 * c1 < cosMinDistance) {
4971 resampleLineTo(x0, y0, λ0, a0, b0, c0, x2, y2, λ2, a /= m, b /= m, c, depth, stream);
4972 stream.point(x2, y2);
4973 resampleLineTo(x2, y2, λ2, a, b, c, x1, y1, λ1, a1, b1, c1, depth, stream);
4974 }
4975 }
4976 }
4977 resample.precision = function(_) {
4978 if (!arguments.length) return Math.sqrt(δ2);
4979 maxDepth = (δ2 = _ * _) > 0 && 16;
4980 return resample;
4981 };
4982 return resample;
4983 }
4984 d3.geo.path = function() {
4985 var pointRadius = 4.5, projection, context, projectStream, contextStream, cacheStream;
4986 function path(object) {
4987 if (object) {
4988 if (typeof pointRadius === "function") contextStream.pointRadius(+pointRadius.apply(this, arguments));
4989 if (!cacheStream || !cacheStream.valid) cacheStream = projectStream(contextStream);
4990 d3.geo.stream(object, cacheStream);
4991 }
4992 return contextStream.result();
4993 }
4994 path.area = function(object) {
4995 d3_geo_pathAreaSum = 0;
4996 d3.geo.stream(object, projectStream(d3_geo_pathArea));
4997 return d3_geo_pathAreaSum;
4998 };
4999 path.centroid = function(object) {
5000 d3_geo_centroidX0 = d3_geo_centroidY0 = d3_geo_centroidZ0 = d3_geo_centroidX1 = d3_geo_centroidY1 = d3_geo_centroidZ1 = d3_geo_centroidX2 = d3_geo_centroidY2 = d3_geo_centroidZ2 = 0;
5001 d3.geo.stream(object, projectStream(d3_geo_pathCentroid));
5002 return d3_geo_centroidZ2 ? [ d3_geo_centroidX2 / d3_geo_centroidZ2, d3_geo_centroidY2 / d3_geo_centroidZ2 ] : d3_geo_centroidZ1 ? [ d3_geo_centroidX1 / d3_geo_centroidZ1, d3_geo_centroidY1 / d3_geo_centroidZ1 ] : d3_geo_centroidZ0 ? [ d3_geo_centroidX0 / d3_geo_centroidZ0, d3_geo_centroidY0 / d3_geo_centroidZ0 ] : [ NaN, NaN ];
5003 };
5004 path.bounds = function(object) {
5005 d3_geo_pathBoundsX1 = d3_geo_pathBoundsY1 = -(d3_geo_pathBoundsX0 = d3_geo_pathBoundsY0 = Infinity);
5006 d3.geo.stream(object, projectStream(d3_geo_pathBounds));
5007 return [ [ d3_geo_pathBoundsX0, d3_geo_pathBoundsY0 ], [ d3_geo_pathBoundsX1, d3_geo_pathBoundsY1 ] ];
5008 };
5009 path.projection = function(_) {
5010 if (!arguments.length) return projection;
5011 projectStream = (projection = _) ? _.stream || d3_geo_pathProjectStream(_) : d3_identity;
5012 return reset();
5013 };
5014 path.context = function(_) {
5015 if (!arguments.length) return context;
5016 contextStream = (context = _) == null ? new d3_geo_pathBuffer() : new d3_geo_pathContext(_);
5017 if (typeof pointRadius !== "function") contextStream.pointRadius(pointRadius);
5018 return reset();
5019 };
5020 path.pointRadius = function(_) {
5021 if (!arguments.length) return pointRadius;
5022 pointRadius = typeof _ === "function" ? _ : (contextStream.pointRadius(+_), +_);
5023 return path;
5024 };
5025 function reset() {
5026 cacheStream = null;
5027 return path;
5028 }
5029 return path.projection(d3.geo.albersUsa()).context(null);
5030 };
5031 function d3_geo_pathProjectStream(project) {
5032 var resample = d3_geo_resample(function(x, y) {
5033 return project([ x * d3_degrees, y * d3_degrees ]);
5034 });
5035 return function(stream) {
5036 return d3_geo_projectionRadians(resample(stream));
5037 };
5038 }
5039 d3.geo.transform = function(methods) {
5040 return {
5041 stream: function(stream) {
5042 var transform = new d3_geo_transform(stream);
5043 for (var k in methods) transform[k] = methods[k];
5044 return transform;
5045 }
5046 };
5047 };
5048 function d3_geo_transform(stream) {
5049 this.stream = stream;
5050 }
5051 d3_geo_transform.prototype = {
5052 point: function(x, y) {
5053 this.stream.point(x, y);
5054 },
5055 sphere: function() {
5056 this.stream.sphere();
5057 },
5058 lineStart: function() {
5059 this.stream.lineStart();
5060 },
5061 lineEnd: function() {
5062 this.stream.lineEnd();
5063 },
5064 polygonStart: function() {
5065 this.stream.polygonStart();
5066 },
5067 polygonEnd: function() {
5068 this.stream.polygonEnd();
5069 }
5070 };
5071 function d3_geo_transformPoint(stream, point) {
5072 return {
5073 point: point,
5074 sphere: function() {
5075 stream.sphere();
5076 },
5077 lineStart: function() {
5078 stream.lineStart();
5079 },
5080 lineEnd: function() {
5081 stream.lineEnd();
5082 },
5083 polygonStart: function() {
5084 stream.polygonStart();
5085 },
5086 polygonEnd: function() {
5087 stream.polygonEnd();
5088 }
5089 };
5090 }
5091 d3.geo.projection = d3_geo_projection;
5092 d3.geo.projectionMutator = d3_geo_projectionMutator;
5093 function d3_geo_projection(project) {
5094 return d3_geo_projectionMutator(function() {
5095 return project;
5096 })();
5097 }
5098 function d3_geo_projectionMutator(projectAt) {
5099 var project, rotate, projectRotate, projectResample = d3_geo_resample(function(x, y) {
5100 x = project(x, y);
5101 return [ x[0] * k + δx, δy - x[1] * k ];
5102 }), k = 150, x = 480, y = 250, λ = 0, φ = 0, δλ = 0, δφ = 0, δγ = 0, δx, δy, preclip = d3_geo_clipAntimeridian, postclip = d3_identity, clipAngle = null, clipExtent = null, stream;
5103 function projection(point) {
5104 point = projectRotate(point[0] * d3_radians, point[1] * d3_radians);
5105 return [ point[0] * k + δx, δy - point[1] * k ];
5106 }
5107 function invert(point) {
5108 point = projectRotate.invert((point[0] - δx) / k, (δy - point[1]) / k);
5109 return point && [ point[0] * d3_degrees, point[1] * d3_degrees ];
5110 }
5111 projection.stream = function(output) {
5112 if (stream) stream.valid = false;
5113 stream = d3_geo_projectionRadians(preclip(rotate, projectResample(postclip(output))));
5114 stream.valid = true;
5115 return stream;
5116 };
5117 projection.clipAngle = function(_) {
5118 if (!arguments.length) return clipAngle;
5119 preclip = _ == null ? (clipAngle = _, d3_geo_clipAntimeridian) : d3_geo_clipCircle((clipAngle = +_) * d3_radians);
5120 return invalidate();
5121 };
5122 projection.clipExtent = function(_) {
5123 if (!arguments.length) return clipExtent;
5124 clipExtent = _;
5125 postclip = _ ? d3_geo_clipExtent(_[0][0], _[0][1], _[1][0], _[1][1]) : d3_identity;
5126 return invalidate();
5127 };
5128 projection.scale = function(_) {
5129 if (!arguments.length) return k;
5130 k = +_;
5131 return reset();
5132 };
5133 projection.translate = function(_) {
5134 if (!arguments.length) return [ x, y ];
5135 x = +_[0];
5136 y = +_[1];
5137 return reset();
5138 };
5139 projection.center = function(_) {
5140 if (!arguments.length) return [ λ * d3_degrees, φ * d3_degrees ];
5141 λ = _[0] % 360 * d3_radians;
5142 φ = _[1] % 360 * d3_radians;
5143 return reset();
5144 };
5145 projection.rotate = function(_) {
5146 if (!arguments.length) return [ δλ * d3_degrees, δφ * d3_degrees, δγ * d3_degrees ];
5147 δλ = _[0] % 360 * d3_radians;
5148 δφ = _[1] % 360 * d3_radians;
5149 δγ = _.length > 2 ? _[2] % 360 * d3_radians : 0;
5150 return reset();
5151 };
5152 d3.rebind(projection, projectResample, "precision");
5153 function reset() {
5154 projectRotate = d3_geo_compose(rotate = d3_geo_rotation(δλ, δφ, δγ), project);
5155 var center = project(λ, φ);
5156 δx = x - center[0] * k;
5157 δy = y + center[1] * k;
5158 return invalidate();
5159 }
5160 function invalidate() {
5161 if (stream) stream.valid = false, stream = null;
5162 return projection;
5163 }
5164 return function() {
5165 project = projectAt.apply(this, arguments);
5166 projection.invert = project.invert && invert;
5167 return reset();
5168 };
5169 }
5170 function d3_geo_projectionRadians(stream) {
5171 return d3_geo_transformPoint(stream, function(x, y) {
5172 stream.point(x * d3_radians, y * d3_radians);
5173 });
5174 }
5175 function d3_geo_equirectangular(λ, φ) {
5176 return [ λ, φ ];
5177 }
5178 (d3.geo.equirectangular = function() {
5179 return d3_geo_projection(d3_geo_equirectangular);
5180 }).raw = d3_geo_equirectangular.invert = d3_geo_equirectangular;
5181 d3.geo.rotation = function(rotate) {
5182 rotate = d3_geo_rotation(rotate[0] % 360 * d3_radians, rotate[1] * d3_radians, rotate.length > 2 ? rotate[2] * d3_radians : 0);
5183 function forward(coordinates) {
5184 coordinates = rotate(coordinates[0] * d3_radians, coordinates[1] * d3_radians);
5185 return coordinates[0] *= d3_degrees, coordinates[1] *= d3_degrees, coordinates;
5186 }
5187 forward.invert = function(coordinates) {
5188 coordinates = rotate.invert(coordinates[0] * d3_radians, coordinates[1] * d3_radians);
5189 return coordinates[0] *= d3_degrees, coordinates[1] *= d3_degrees, coordinates;
5190 };
5191 return forward;
5192 };
5193 function d3_geo_identityRotation(λ, φ) {
5194 return [ λ > π ? λ - τ : λ < -π ? λ + τ : λ, φ ];
5195 }
5196 d3_geo_identityRotation.invert = d3_geo_equirectangular;
5197 function d3_geo_rotation(δλ, δφ, δγ) {
5198 return δλ ? δφ || δγ ? d3_geo_compose(d3_geo_rotationλ(δλ), d3_geo_rotationφγ(δφ, δγ)) : d3_geo_rotationλ(δλ) : δφ || δγ ? d3_geo_rotationφγ(δφ, δγ) : d3_geo_identityRotation;
5199 }
5200 function d3_geo_forwardRotationλ(δλ) {
5201 return function(λ, φ) {
5202 return λ += δλ, [ λ > π ? λ - τ : λ < -π ? λ + τ : λ, φ ];
5203 };
5204 }
5205 function d3_geo_rotationλ(δλ) {
5206 var rotation = d3_geo_forwardRotationλ(δλ);
5207 rotation.invert = d3_geo_forwardRotationλ(-δλ);
5208 return rotation;
5209 }
5210 function d3_geo_rotationφγ(δφ, δγ) {
5211 var cosδφ = Math.cos(δφ), sinδφ = Math.sin(δφ), cosδγ = Math.cos(δγ), sinδγ = Math.sin(δγ);
5212 function rotation(λ, φ) {
5213 var cosφ = Math.cos(φ), x = Math.cos(λ) * cosφ, y = Math.sin(λ) * cosφ, z = Math.sin(φ), k = z * cosδφ + x * sinδφ;
5214 return [ Math.atan2(y * cosδγ - k * sinδγ, x * cosδφ - z * sinδφ), d3_asin(k * cosδγ + y * sinδγ) ];
5215 }
5216 rotation.invert = function(λ, φ) {
5217 var cosφ = Math.cos(φ), x = Math.cos(λ) * cosφ, y = Math.sin(λ) * cosφ, z = Math.sin(φ), k = z * cosδγ - y * sinδγ;
5218 return [ Math.atan2(y * cosδγ + z * sinδγ, x * cosδφ + k * sinδφ), d3_asin(k * cosδφ - x * sinδφ) ];
5219 };
5220 return rotation;
5221 }
5222 d3.geo.circle = function() {
5223 var origin = [ 0, 0 ], angle, precision = 6, interpolate;
5224 function circle() {
5225 var center = typeof origin === "function" ? origin.apply(this, arguments) : origin, rotate = d3_geo_rotation(-center[0] * d3_radians, -center[1] * d3_radians, 0).invert, ring = [];
5226 interpolate(null, null, 1, {
5227 point: function(x, y) {
5228 ring.push(x = rotate(x, y));
5229 x[0] *= d3_degrees, x[1] *= d3_degrees;
5230 }
5231 });
5232 return {
5233 type: "Polygon",
5234 coordinates: [ ring ]
5235 };
5236 }
5237 circle.origin = function(x) {
5238 if (!arguments.length) return origin;
5239 origin = x;
5240 return circle;
5241 };
5242 circle.angle = function(x) {
5243 if (!arguments.length) return angle;
5244 interpolate = d3_geo_circleInterpolate((angle = +x) * d3_radians, precision * d3_radians);
5245 return circle;
5246 };
5247 circle.precision = function(_) {
5248 if (!arguments.length) return precision;
5249 interpolate = d3_geo_circleInterpolate(angle * d3_radians, (precision = +_) * d3_radians);
5250 return circle;
5251 };
5252 return circle.angle(90);
5253 };
5254 function d3_geo_circleInterpolate(radius, precision) {
5255 var cr = Math.cos(radius), sr = Math.sin(radius);
5256 return function(from, to, direction, listener) {
5257 var step = direction * precision;
5258 if (from != null) {
5259 from = d3_geo_circleAngle(cr, from);
5260 to = d3_geo_circleAngle(cr, to);
5261 if (direction > 0 ? from < to : from > to) from += direction * τ;
5262 } else {
5263 from = radius + direction * τ;
5264 to = radius - .5 * step;
5265 }
5266 for (var point, t = from; direction > 0 ? t > to : t < to; t -= step) {
5267 listener.point((point = d3_geo_spherical([ cr, -sr * Math.cos(t), -sr * Math.sin(t) ]))[0], point[1]);
5268 }
5269 };
5270 }
5271 function d3_geo_circleAngle(cr, point) {
5272 var a = d3_geo_cartesian(point);
5273 a[0] -= cr;
5274 d3_geo_cartesianNormalize(a);
5275 var angle = d3_acos(-a[1]);
5276 return ((-a[2] < 0 ? -angle : angle) + 2 * Math.PI - ε) % (2 * Math.PI);
5277 }
5278 d3.geo.distance = function(a, b) {
5279 var Δλ = (b[0] - a[0]) * d3_radians, φ0 = a[1] * d3_radians, φ1 = b[1] * d3_radians, sinΔλ = Math.sin(Δλ), cosΔλ = Math.cos(Δλ), sinφ0 = Math.sin(φ0), cosφ0 = Math.cos(φ0), sinφ1 = Math.sin(φ1), cosφ1 = Math.cos(φ1), t;
5280 return Math.atan2(Math.sqrt((t = cosφ1 * sinΔλ) * t + (t = cosφ0 * sinφ1 - sinφ0 * cosφ1 * cosΔλ) * t), sinφ0 * sinφ1 + cosφ0 * cosφ1 * cosΔλ);
5281 };
5282 d3.geo.graticule = function() {
5283 var x1, x0, X1, X0, y1, y0, Y1, Y0, dx = 10, dy = dx, DX = 90, DY = 360, x, y, X, Y, precision = 2.5;
5284 function graticule() {
5285 return {
5286 type: "MultiLineString",
5287 coordinates: lines()
5288 };
5289 }
5290 function lines() {
5291 return d3.range(Math.ceil(X0 / DX) * DX, X1, DX).map(X).concat(d3.range(Math.ceil(Y0 / DY) * DY, Y1, DY).map(Y)).concat(d3.range(Math.ceil(x0 / dx) * dx, x1, dx).filter(function(x) {
5292 return abs(x % DX) > ε;
5293 }).map(x)).concat(d3.range(Math.ceil(y0 / dy) * dy, y1, dy).filter(function(y) {
5294 return abs(y % DY) > ε;
5295 }).map(y));
5296 }
5297 graticule.lines = function() {
5298 return lines().map(function(coordinates) {
5299 return {
5300 type: "LineString",
5301 coordinates: coordinates
5302 };
5303 });
5304 };
5305 graticule.outline = function() {
5306 return {
5307 type: "Polygon",
5308 coordinates: [ X(X0).concat(Y(Y1).slice(1), X(X1).reverse().slice(1), Y(Y0).reverse().slice(1)) ]
5309 };
5310 };
5311 graticule.extent = function(_) {
5312 if (!arguments.length) return graticule.minorExtent();
5313 return graticule.majorExtent(_).minorExtent(_);
5314 };
5315 graticule.majorExtent = function(_) {
5316 if (!arguments.length) return [ [ X0, Y0 ], [ X1, Y1 ] ];
5317 X0 = +_[0][0], X1 = +_[1][0];
5318 Y0 = +_[0][1], Y1 = +_[1][1];
5319 if (X0 > X1) _ = X0, X0 = X1, X1 = _;
5320 if (Y0 > Y1) _ = Y0, Y0 = Y1, Y1 = _;
5321 return graticule.precision(precision);
5322 };
5323 graticule.minorExtent = function(_) {
5324 if (!arguments.length) return [ [ x0, y0 ], [ x1, y1 ] ];
5325 x0 = +_[0][0], x1 = +_[1][0];
5326 y0 = +_[0][1], y1 = +_[1][1];
5327 if (x0 > x1) _ = x0, x0 = x1, x1 = _;
5328 if (y0 > y1) _ = y0, y0 = y1, y1 = _;
5329 return graticule.precision(precision);
5330 };
5331 graticule.step = function(_) {
5332 if (!arguments.length) return graticule.minorStep();
5333 return graticule.majorStep(_).minorStep(_);
5334 };
5335 graticule.majorStep = function(_) {
5336 if (!arguments.length) return [ DX, DY ];
5337 DX = +_[0], DY = +_[1];
5338 return graticule;
5339 };
5340 graticule.minorStep = function(_) {
5341 if (!arguments.length) return [ dx, dy ];
5342 dx = +_[0], dy = +_[1];
5343 return graticule;
5344 };
5345 graticule.precision = function(_) {
5346 if (!arguments.length) return precision;
5347 precision = +_;
5348 x = d3_geo_graticuleX(y0, y1, 90);
5349 y = d3_geo_graticuleY(x0, x1, precision);
5350 X = d3_geo_graticuleX(Y0, Y1, 90);
5351 Y = d3_geo_graticuleY(X0, X1, precision);
5352 return graticule;
5353 };
5354 return graticule.majorExtent([ [ -180, -90 + ε ], [ 180, 90 - ε ] ]).minorExtent([ [ -180, -80 - ε ], [ 180, 80 + ε ] ]);
5355 };
5356 function d3_geo_graticuleX(y0, y1, dy) {
5357 var y = d3.range(y0, y1 - ε, dy).concat(y1);
5358 return function(x) {
5359 return y.map(function(y) {
5360 return [ x, y ];
5361 });
5362 };
5363 }
5364 function d3_geo_graticuleY(x0, x1, dx) {
5365 var x = d3.range(x0, x1 - ε, dx).concat(x1);
5366 return function(y) {
5367 return x.map(function(x) {
5368 return [ x, y ];
5369 });
5370 };
5371 }
5372 function d3_source(d) {
5373 return d.source;
5374 }
5375 function d3_target(d) {
5376 return d.target;
5377 }
5378 d3.geo.greatArc = function() {
5379 var source = d3_source, source_, target = d3_target, target_;
5380 function greatArc() {
5381 return {
5382 type: "LineString",
5383 coordinates: [ source_ || source.apply(this, arguments), target_ || target.apply(this, arguments) ]
5384 };
5385 }
5386 greatArc.distance = function() {
5387 return d3.geo.distance(source_ || source.apply(this, arguments), target_ || target.apply(this, arguments));
5388 };
5389 greatArc.source = function(_) {
5390 if (!arguments.length) return source;
5391 source = _, source_ = typeof _ === "function" ? null : _;
5392 return greatArc;
5393 };
5394 greatArc.target = function(_) {
5395 if (!arguments.length) return target;
5396 target = _, target_ = typeof _ === "function" ? null : _;
5397 return greatArc;
5398 };
5399 greatArc.precision = function() {
5400 return arguments.length ? greatArc : 0;
5401 };
5402 return greatArc;
5403 };
5404 d3.geo.interpolate = function(source, target) {
5405 return d3_geo_interpolate(source[0] * d3_radians, source[1] * d3_radians, target[0] * d3_radians, target[1] * d3_radians);
5406 };
5407 function d3_geo_interpolate(x0, y0, x1, y1) {
5408 var cy0 = Math.cos(y0), sy0 = Math.sin(y0), cy1 = Math.cos(y1), sy1 = Math.sin(y1), kx0 = cy0 * Math.cos(x0), ky0 = cy0 * Math.sin(x0), kx1 = cy1 * Math.cos(x1), ky1 = cy1 * Math.sin(x1), d = 2 * Math.asin(Math.sqrt(d3_haversin(y1 - y0) + cy0 * cy1 * d3_haversin(x1 - x0))), k = 1 / Math.sin(d);
5409 var interpolate = d ? function(t) {
5410 var B = Math.sin(t *= d) * k, A = Math.sin(d - t) * k, x = A * kx0 + B * kx1, y = A * ky0 + B * ky1, z = A * sy0 + B * sy1;
5411 return [ Math.atan2(y, x) * d3_degrees, Math.atan2(z, Math.sqrt(x * x + y * y)) * d3_degrees ];
5412 } : function() {
5413 return [ x0 * d3_degrees, y0 * d3_degrees ];
5414 };
5415 interpolate.distance = d;
5416 return interpolate;
5417 }
5418 d3.geo.length = function(object) {
5419 d3_geo_lengthSum = 0;
5420 d3.geo.stream(object, d3_geo_length);
5421 return d3_geo_lengthSum;
5422 };
5423 var d3_geo_lengthSum;
5424 var d3_geo_length = {
5425 sphere: d3_noop,
5426 point: d3_noop,
5427 lineStart: d3_geo_lengthLineStart,
5428 lineEnd: d3_noop,
5429 polygonStart: d3_noop,
5430 polygonEnd: d3_noop
5431 };
5432 function d3_geo_lengthLineStart() {
5433 var λ0, sinφ0, cosφ0;
5434 d3_geo_length.point = function(λ, φ) {
5435 λ0 = λ * d3_radians, sinφ0 = Math.sin(φ *= d3_radians), cosφ0 = Math.cos(φ);
5436 d3_geo_length.point = nextPoint;
5437 };
5438 d3_geo_length.lineEnd = function() {
5439 d3_geo_length.point = d3_geo_length.lineEnd = d3_noop;
5440 };
5441 function nextPoint(λ, φ) {
5442 var sinφ = Math.sin(φ *= d3_radians), cosφ = Math.cos(φ), t = abs((λ *= d3_radians) - λ0), cosΔλ = Math.cos(t);
5443 d3_geo_lengthSum += Math.atan2(Math.sqrt((t = cosφ * Math.sin(t)) * t + (t = cosφ0 * sinφ - sinφ0 * cosφ * cosΔλ) * t), sinφ0 * sinφ + cosφ0 * cosφ * cosΔλ);
5444 λ0 = λ, sinφ0 = sinφ, cosφ0 = cosφ;
5445 }
5446 }
5447 function d3_geo_azimuthal(scale, angle) {
5448 function azimuthal(λ, φ) {
5449 var cosλ = Math.cos(λ), cosφ = Math.cos(φ), k = scale(cosλ * cosφ);
5450 return [ k * cosφ * Math.sin(λ), k * Math.sin(φ) ];
5451 }
5452 azimuthal.invert = function(x, y) {
5453 var ρ = Math.sqrt(x * x + y * y), c = angle(ρ), sinc = Math.sin(c), cosc = Math.cos(c);
5454 return [ Math.atan2(x * sinc, ρ * cosc), Math.asin(ρ && y * sinc / ρ) ];
5455 };
5456 return azimuthal;
5457 }
5458 var d3_geo_azimuthalEqualArea = d3_geo_azimuthal(function(cosλcosφ) {
5459 return Math.sqrt(2 / (1 + cosλcosφ));
5460 }, function(ρ) {
5461 return 2 * Math.asin(ρ / 2);
5462 });
5463 (d3.geo.azimuthalEqualArea = function() {
5464 return d3_geo_projection(d3_geo_azimuthalEqualArea);
5465 }).raw = d3_geo_azimuthalEqualArea;
5466 var d3_geo_azimuthalEquidistant = d3_geo_azimuthal(function(cosλcosφ) {
5467 var c = Math.acos(cosλcosφ);
5468 return c && c / Math.sin(c);
5469 }, d3_identity);
5470 (d3.geo.azimuthalEquidistant = function() {
5471 return d3_geo_projection(d3_geo_azimuthalEquidistant);
5472 }).raw = d3_geo_azimuthalEquidistant;
5473 function d3_geo_conicConformal(φ0, φ1) {
5474 var cosφ0 = Math.cos(φ0), t = function(φ) {
5475 return Math.tan(π / 4 + φ / 2);
5476 }, n = φ0 === φ1 ? Math.sin(φ0) : Math.log(cosφ0 / Math.cos(φ1)) / Math.log(t(φ1) / t(φ0)), F = cosφ0 * Math.pow(t(φ0), n) / n;
5477 if (!n) return d3_geo_mercator;
5478 function forward(λ, φ) {
5479 if (F > 0) {
5480 if (φ < -halfπ + ε) φ = -halfπ + ε;
5481 } else {
5482 if (φ > halfπ - ε) φ = halfπ - ε;
5483 }
5484 var ρ = F / Math.pow(t(φ), n);
5485 return [ ρ * Math.sin(n * λ), F - ρ * Math.cos(n * λ) ];
5486 }
5487 forward.invert = function(x, y) {
5488 var ρ0_y = F - y, ρ = d3_sgn(n) * Math.sqrt(x * x + ρ0_y * ρ0_y);
5489 return [ Math.atan2(x, ρ0_y) / n, 2 * Math.atan(Math.pow(F / ρ, 1 / n)) - halfπ ];
5490 };
5491 return forward;
5492 }
5493 (d3.geo.conicConformal = function() {
5494 return d3_geo_conic(d3_geo_conicConformal);
5495 }).raw = d3_geo_conicConformal;
5496 function d3_geo_conicEquidistant(φ0, φ1) {
5497 var cosφ0 = Math.cos(φ0), n = φ0 === φ1 ? Math.sin(φ0) : (cosφ0 - Math.cos(φ1)) / (φ1 - φ0), G = cosφ0 / n + φ0;
5498 if (abs(n) < ε) return d3_geo_equirectangular;
5499 function forward(λ, φ) {
5500 var ρ = G - φ;
5501 return [ ρ * Math.sin(n * λ), G - ρ * Math.cos(n * λ) ];
5502 }
5503 forward.invert = function(x, y) {
5504 var ρ0_y = G - y;
5505 return [ Math.atan2(x, ρ0_y) / n, G - d3_sgn(n) * Math.sqrt(x * x + ρ0_y * ρ0_y) ];
5506 };
5507 return forward;
5508 }
5509 (d3.geo.conicEquidistant = function() {
5510 return d3_geo_conic(d3_geo_conicEquidistant);
5511 }).raw = d3_geo_conicEquidistant;
5512 var d3_geo_gnomonic = d3_geo_azimuthal(function(cosλcosφ) {
5513 return 1 / cosλcosφ;
5514 }, Math.atan);
5515 (d3.geo.gnomonic = function() {
5516 return d3_geo_projection(d3_geo_gnomonic);
5517 }).raw = d3_geo_gnomonic;
5518 function d3_geo_mercator(λ, φ) {
5519 return [ λ, Math.log(Math.tan(π / 4 + φ / 2)) ];
5520 }
5521 d3_geo_mercator.invert = function(x, y) {
5522 return [ x, 2 * Math.atan(Math.exp(y)) - halfπ ];
5523 };
5524 function d3_geo_mercatorProjection(project) {
5525 var m = d3_geo_projection(project), scale = m.scale, translate = m.translate, clipExtent = m.clipExtent, clipAuto;
5526 m.scale = function() {
5527 var v = scale.apply(m, arguments);
5528 return v === m ? clipAuto ? m.clipExtent(null) : m : v;
5529 };
5530 m.translate = function() {
5531 var v = translate.apply(m, arguments);
5532 return v === m ? clipAuto ? m.clipExtent(null) : m : v;
5533 };
5534 m.clipExtent = function(_) {
5535 var v = clipExtent.apply(m, arguments);
5536 if (v === m) {
5537 if (clipAuto = _ == null) {
5538 var k = π * scale(), t = translate();
5539 clipExtent([ [ t[0] - k, t[1] - k ], [ t[0] + k, t[1] + k ] ]);
5540 }
5541 } else if (clipAuto) {
5542 v = null;
5543 }
5544 return v;
5545 };
5546 return m.clipExtent(null);
5547 }
5548 (d3.geo.mercator = function() {
5549 return d3_geo_mercatorProjection(d3_geo_mercator);
5550 }).raw = d3_geo_mercator;
5551 var d3_geo_orthographic = d3_geo_azimuthal(function() {
5552 return 1;
5553 }, Math.asin);
5554 (d3.geo.orthographic = function() {
5555 return d3_geo_projection(d3_geo_orthographic);
5556 }).raw = d3_geo_orthographic;
5557 var d3_geo_stereographic = d3_geo_azimuthal(function(cosλcosφ) {
5558 return 1 / (1 + cosλcosφ);
5559 }, function(ρ) {
5560 return 2 * Math.atan(ρ);
5561 });
5562 (d3.geo.stereographic = function() {
5563 return d3_geo_projection(d3_geo_stereographic);
5564 }).raw = d3_geo_stereographic;
5565 function d3_geo_transverseMercator(λ, φ) {
5566 return [ Math.log(Math.tan(π / 4 + φ / 2)), -λ ];
5567 }
5568 d3_geo_transverseMercator.invert = function(x, y) {
5569 return [ -y, 2 * Math.atan(Math.exp(x)) - halfπ ];
5570 };
5571 (d3.geo.transverseMercator = function() {
5572 var projection = d3_geo_mercatorProjection(d3_geo_transverseMercator), center = projection.center, rotate = projection.rotate;
5573 projection.center = function(_) {
5574 return _ ? center([ -_[1], _[0] ]) : (_ = center(), [ _[1], -_[0] ]);
5575 };
5576 projection.rotate = function(_) {
5577 return _ ? rotate([ _[0], _[1], _.length > 2 ? _[2] + 90 : 90 ]) : (_ = rotate(),
5578 [ _[0], _[1], _[2] - 90 ]);
5579 };
5580 return rotate([ 0, 0, 90 ]);
5581 }).raw = d3_geo_transverseMercator;
5582 d3.geom = {};
5583 function d3_geom_pointX(d) {
5584 return d[0];
5585 }
5586 function d3_geom_pointY(d) {
5587 return d[1];
5588 }
5589 d3.geom.hull = function(vertices) {
5590 var x = d3_geom_pointX, y = d3_geom_pointY;
5591 if (arguments.length) return hull(vertices);
5592 function hull(data) {
5593 if (data.length < 3) return [];
5594 var fx = d3_functor(x), fy = d3_functor(y), i, n = data.length, points = [], flippedPoints = [];
5595 for (i = 0; i < n; i++) {
5596 points.push([ +fx.call(this, data[i], i), +fy.call(this, data[i], i), i ]);
5597 }
5598 points.sort(d3_geom_hullOrder);
5599 for (i = 0; i < n; i++) flippedPoints.push([ points[i][0], -points[i][1] ]);
5600 var upper = d3_geom_hullUpper(points), lower = d3_geom_hullUpper(flippedPoints);
5601 var skipLeft = lower[0] === upper[0], skipRight = lower[lower.length - 1] === upper[upper.length - 1], polygon = [];
5602 for (i = upper.length - 1; i >= 0; --i) polygon.push(data[points[upper[i]][2]]);
5603 for (i = +skipLeft; i < lower.length - skipRight; ++i) polygon.push(data[points[lower[i]][2]]);
5604 return polygon;
5605 }
5606 hull.x = function(_) {
5607 return arguments.length ? (x = _, hull) : x;
5608 };
5609 hull.y = function(_) {
5610 return arguments.length ? (y = _, hull) : y;
5611 };
5612 return hull;
5613 };
5614 function d3_geom_hullUpper(points) {
5615 var n = points.length, hull = [ 0, 1 ], hs = 2;
5616 for (var i = 2; i < n; i++) {
5617 while (hs > 1 && d3_cross2d(points[hull[hs - 2]], points[hull[hs - 1]], points[i]) <= 0) --hs;
5618 hull[hs++] = i;
5619 }
5620 return hull.slice(0, hs);
5621 }
5622 function d3_geom_hullOrder(a, b) {
5623 return a[0] - b[0] || a[1] - b[1];
5624 }
5625 d3.geom.polygon = function(coordinates) {
5626 d3_subclass(coordinates, d3_geom_polygonPrototype);
5627 return coordinates;
5628 };
5629 var d3_geom_polygonPrototype = d3.geom.polygon.prototype = [];
5630 d3_geom_polygonPrototype.area = function() {
5631 var i = -1, n = this.length, a, b = this[n - 1], area = 0;
5632 while (++i < n) {
5633 a = b;
5634 b = this[i];
5635 area += a[1] * b[0] - a[0] * b[1];
5636 }
5637 return area * .5;
5638 };
5639 d3_geom_polygonPrototype.centroid = function(k) {
5640 var i = -1, n = this.length, x = 0, y = 0, a, b = this[n - 1], c;
5641 if (!arguments.length) k = -1 / (6 * this.area());
5642 while (++i < n) {
5643 a = b;
5644 b = this[i];
5645 c = a[0] * b[1] - b[0] * a[1];
5646 x += (a[0] + b[0]) * c;
5647 y += (a[1] + b[1]) * c;
5648 }
5649 return [ x * k, y * k ];
5650 };
5651 d3_geom_polygonPrototype.clip = function(subject) {
5652 var input, closed = d3_geom_polygonClosed(subject), i = -1, n = this.length - d3_geom_polygonClosed(this), j, m, a = this[n - 1], b, c, d;
5653 while (++i < n) {
5654 input = subject.slice();
5655 subject.length = 0;
5656 b = this[i];
5657 c = input[(m = input.length - closed) - 1];
5658 j = -1;
5659 while (++j < m) {
5660 d = input[j];
5661 if (d3_geom_polygonInside(d, a, b)) {
5662 if (!d3_geom_polygonInside(c, a, b)) {
5663 subject.push(d3_geom_polygonIntersect(c, d, a, b));
5664 }
5665 subject.push(d);
5666 } else if (d3_geom_polygonInside(c, a, b)) {
5667 subject.push(d3_geom_polygonIntersect(c, d, a, b));
5668 }
5669 c = d;
5670 }
5671 if (closed) subject.push(subject[0]);
5672 a = b;
5673 }
5674 return subject;
5675 };
5676 function d3_geom_polygonInside(p, a, b) {
5677 return (b[0] - a[0]) * (p[1] - a[1]) < (b[1] - a[1]) * (p[0] - a[0]);
5678 }
5679 function d3_geom_polygonIntersect(c, d, a, b) {
5680 var x1 = c[0], x3 = a[0], x21 = d[0] - x1, x43 = b[0] - x3, y1 = c[1], y3 = a[1], y21 = d[1] - y1, y43 = b[1] - y3, ua = (x43 * (y1 - y3) - y43 * (x1 - x3)) / (y43 * x21 - x43 * y21);
5681 return [ x1 + ua * x21, y1 + ua * y21 ];
5682 }
5683 function d3_geom_polygonClosed(coordinates) {
5684 var a = coordinates[0], b = coordinates[coordinates.length - 1];
5685 return !(a[0] - b[0] || a[1] - b[1]);
5686 }
5687 var d3_geom_voronoiEdges, d3_geom_voronoiCells, d3_geom_voronoiBeaches, d3_geom_voronoiBeachPool = [], d3_geom_voronoiFirstCircle, d3_geom_voronoiCircles, d3_geom_voronoiCirclePool = [];
5688 function d3_geom_voronoiBeach() {
5689 d3_geom_voronoiRedBlackNode(this);
5690 this.edge = this.site = this.circle = null;
5691 }
5692 function d3_geom_voronoiCreateBeach(site) {
5693 var beach = d3_geom_voronoiBeachPool.pop() || new d3_geom_voronoiBeach();
5694 beach.site = site;
5695 return beach;
5696 }
5697 function d3_geom_voronoiDetachBeach(beach) {
5698 d3_geom_voronoiDetachCircle(beach);
5699 d3_geom_voronoiBeaches.remove(beach);
5700 d3_geom_voronoiBeachPool.push(beach);
5701 d3_geom_voronoiRedBlackNode(beach);
5702 }
5703 function d3_geom_voronoiRemoveBeach(beach) {
5704 var circle = beach.circle, x = circle.x, y = circle.cy, vertex = {
5705 x: x,
5706 y: y
5707 }, previous = beach.P, next = beach.N, disappearing = [ beach ];
5708 d3_geom_voronoiDetachBeach(beach);
5709 var lArc = previous;
5710 while (lArc.circle && abs(x - lArc.circle.x) < ε && abs(y - lArc.circle.cy) < ε) {
5711 previous = lArc.P;
5712 disappearing.unshift(lArc);
5713 d3_geom_voronoiDetachBeach(lArc);
5714 lArc = previous;
5715 }
5716 disappearing.unshift(lArc);
5717 d3_geom_voronoiDetachCircle(lArc);
5718 var rArc = next;
5719 while (rArc.circle && abs(x - rArc.circle.x) < ε && abs(y - rArc.circle.cy) < ε) {
5720 next = rArc.N;
5721 disappearing.push(rArc);
5722 d3_geom_voronoiDetachBeach(rArc);
5723 rArc = next;
5724 }
5725 disappearing.push(rArc);
5726 d3_geom_voronoiDetachCircle(rArc);
5727 var nArcs = disappearing.length, iArc;
5728 for (iArc = 1; iArc < nArcs; ++iArc) {
5729 rArc = disappearing[iArc];
5730 lArc = disappearing[iArc - 1];
5731 d3_geom_voronoiSetEdgeEnd(rArc.edge, lArc.site, rArc.site, vertex);
5732 }
5733 lArc = disappearing[0];
5734 rArc = disappearing[nArcs - 1];
5735 rArc.edge = d3_geom_voronoiCreateEdge(lArc.site, rArc.site, null, vertex);
5736 d3_geom_voronoiAttachCircle(lArc);
5737 d3_geom_voronoiAttachCircle(rArc);
5738 }
5739 function d3_geom_voronoiAddBeach(site) {
5740 var x = site.x, directrix = site.y, lArc, rArc, dxl, dxr, node = d3_geom_voronoiBeaches._;
5741 while (node) {
5742 dxl = d3_geom_voronoiLeftBreakPoint(node, directrix) - x;
5743 if (dxl > ε) node = node.L; else {
5744 dxr = x - d3_geom_voronoiRightBreakPoint(node, directrix);
5745 if (dxr > ε) {
5746 if (!node.R) {
5747 lArc = node;
5748 break;
5749 }
5750 node = node.R;
5751 } else {
5752 if (dxl > -ε) {
5753 lArc = node.P;
5754 rArc = node;
5755 } else if (dxr > -ε) {
5756 lArc = node;
5757 rArc = node.N;
5758 } else {
5759 lArc = rArc = node;
5760 }
5761 break;
5762 }
5763 }
5764 }
5765 var newArc = d3_geom_voronoiCreateBeach(site);
5766 d3_geom_voronoiBeaches.insert(lArc, newArc);
5767 if (!lArc && !rArc) return;
5768 if (lArc === rArc) {
5769 d3_geom_voronoiDetachCircle(lArc);
5770 rArc = d3_geom_voronoiCreateBeach(lArc.site);
5771 d3_geom_voronoiBeaches.insert(newArc, rArc);
5772 newArc.edge = rArc.edge = d3_geom_voronoiCreateEdge(lArc.site, newArc.site);
5773 d3_geom_voronoiAttachCircle(lArc);
5774 d3_geom_voronoiAttachCircle(rArc);
5775 return;
5776 }
5777 if (!rArc) {
5778 newArc.edge = d3_geom_voronoiCreateEdge(lArc.site, newArc.site);
5779 return;
5780 }
5781 d3_geom_voronoiDetachCircle(lArc);
5782 d3_geom_voronoiDetachCircle(rArc);
5783 var lSite = lArc.site, ax = lSite.x, ay = lSite.y, bx = site.x - ax, by = site.y - ay, rSite = rArc.site, cx = rSite.x - ax, cy = rSite.y - ay, d = 2 * (bx * cy - by * cx), hb = bx * bx + by * by, hc = cx * cx + cy * cy, vertex = {
5784 x: (cy * hb - by * hc) / d + ax,
5785 y: (bx * hc - cx * hb) / d + ay
5786 };
5787 d3_geom_voronoiSetEdgeEnd(rArc.edge, lSite, rSite, vertex);
5788 newArc.edge = d3_geom_voronoiCreateEdge(lSite, site, null, vertex);
5789 rArc.edge = d3_geom_voronoiCreateEdge(site, rSite, null, vertex);
5790 d3_geom_voronoiAttachCircle(lArc);
5791 d3_geom_voronoiAttachCircle(rArc);
5792 }
5793 function d3_geom_voronoiLeftBreakPoint(arc, directrix) {
5794 var site = arc.site, rfocx = site.x, rfocy = site.y, pby2 = rfocy - directrix;
5795 if (!pby2) return rfocx;
5796 var lArc = arc.P;
5797 if (!lArc) return -Infinity;
5798 site = lArc.site;
5799 var lfocx = site.x, lfocy = site.y, plby2 = lfocy - directrix;
5800 if (!plby2) return lfocx;
5801 var hl = lfocx - rfocx, aby2 = 1 / pby2 - 1 / plby2, b = hl / plby2;
5802 if (aby2) return (-b + Math.sqrt(b * b - 2 * aby2 * (hl * hl / (-2 * plby2) - lfocy + plby2 / 2 + rfocy - pby2 / 2))) / aby2 + rfocx;
5803 return (rfocx + lfocx) / 2;
5804 }
5805 function d3_geom_voronoiRightBreakPoint(arc, directrix) {
5806 var rArc = arc.N;
5807 if (rArc) return d3_geom_voronoiLeftBreakPoint(rArc, directrix);
5808 var site = arc.site;
5809 return site.y === directrix ? site.x : Infinity;
5810 }
5811 function d3_geom_voronoiCell(site) {
5812 this.site = site;
5813 this.edges = [];
5814 }
5815 d3_geom_voronoiCell.prototype.prepare = function() {
5816 var halfEdges = this.edges, iHalfEdge = halfEdges.length, edge;
5817 while (iHalfEdge--) {
5818 edge = halfEdges[iHalfEdge].edge;
5819 if (!edge.b || !edge.a) halfEdges.splice(iHalfEdge, 1);
5820 }
5821 halfEdges.sort(d3_geom_voronoiHalfEdgeOrder);
5822 return halfEdges.length;
5823 };
5824 function d3_geom_voronoiCloseCells(extent) {
5825 var x0 = extent[0][0], x1 = extent[1][0], y0 = extent[0][1], y1 = extent[1][1], x2, y2, x3, y3, cells = d3_geom_voronoiCells, iCell = cells.length, cell, iHalfEdge, halfEdges, nHalfEdges, start, end;
5826 while (iCell--) {
5827 cell = cells[iCell];
5828 if (!cell || !cell.prepare()) continue;
5829 halfEdges = cell.edges;
5830 nHalfEdges = halfEdges.length;
5831 iHalfEdge = 0;
5832 while (iHalfEdge < nHalfEdges) {
5833 end = halfEdges[iHalfEdge].end(), x3 = end.x, y3 = end.y;
5834 start = halfEdges[++iHalfEdge % nHalfEdges].start(), x2 = start.x, y2 = start.y;
5835 if (abs(x3 - x2) > ε || abs(y3 - y2) > ε) {
5836 halfEdges.splice(iHalfEdge, 0, new d3_geom_voronoiHalfEdge(d3_geom_voronoiCreateBorderEdge(cell.site, end, abs(x3 - x0) < ε && y1 - y3 > ε ? {
5837 x: x0,
5838 y: abs(x2 - x0) < ε ? y2 : y1
5839 } : abs(y3 - y1) < ε && x1 - x3 > ε ? {
5840 x: abs(y2 - y1) < ε ? x2 : x1,
5841 y: y1
5842 } : abs(x3 - x1) < ε && y3 - y0 > ε ? {
5843 x: x1,
5844 y: abs(x2 - x1) < ε ? y2 : y0
5845 } : abs(y3 - y0) < ε && x3 - x0 > ε ? {
5846 x: abs(y2 - y0) < ε ? x2 : x0,
5847 y: y0
5848 } : null), cell.site, null));
5849 ++nHalfEdges;
5850 }
5851 }
5852 }
5853 }
5854 function d3_geom_voronoiHalfEdgeOrder(a, b) {
5855 return b.angle - a.angle;
5856 }
5857 function d3_geom_voronoiCircle() {
5858 d3_geom_voronoiRedBlackNode(this);
5859 this.x = this.y = this.arc = this.site = this.cy = null;
5860 }
5861 function d3_geom_voronoiAttachCircle(arc) {
5862 var lArc = arc.P, rArc = arc.N;
5863 if (!lArc || !rArc) return;
5864 var lSite = lArc.site, cSite = arc.site, rSite = rArc.site;
5865 if (lSite === rSite) return;
5866 var bx = cSite.x, by = cSite.y, ax = lSite.x - bx, ay = lSite.y - by, cx = rSite.x - bx, cy = rSite.y - by;
5867 var d = 2 * (ax * cy - ay * cx);
5868 if (d >= -ε2) return;
5869 var ha = ax * ax + ay * ay, hc = cx * cx + cy * cy, x = (cy * ha - ay * hc) / d, y = (ax * hc - cx * ha) / d, cy = y + by;
5870 var circle = d3_geom_voronoiCirclePool.pop() || new d3_geom_voronoiCircle();
5871 circle.arc = arc;
5872 circle.site = cSite;
5873 circle.x = x + bx;
5874 circle.y = cy + Math.sqrt(x * x + y * y);
5875 circle.cy = cy;
5876 arc.circle = circle;
5877 var before = null, node = d3_geom_voronoiCircles._;
5878 while (node) {
5879 if (circle.y < node.y || circle.y === node.y && circle.x <= node.x) {
5880 if (node.L) node = node.L; else {
5881 before = node.P;
5882 break;
5883 }
5884 } else {
5885 if (node.R) node = node.R; else {
5886 before = node;
5887 break;
5888 }
5889 }
5890 }
5891 d3_geom_voronoiCircles.insert(before, circle);
5892 if (!before) d3_geom_voronoiFirstCircle = circle;
5893 }
5894 function d3_geom_voronoiDetachCircle(arc) {
5895 var circle = arc.circle;
5896 if (circle) {
5897 if (!circle.P) d3_geom_voronoiFirstCircle = circle.N;
5898 d3_geom_voronoiCircles.remove(circle);
5899 d3_geom_voronoiCirclePool.push(circle);
5900 d3_geom_voronoiRedBlackNode(circle);
5901 arc.circle = null;
5902 }
5903 }
5904 function d3_geom_voronoiClipEdges(extent) {
5905 var edges = d3_geom_voronoiEdges, clip = d3_geom_clipLine(extent[0][0], extent[0][1], extent[1][0], extent[1][1]), i = edges.length, e;
5906 while (i--) {
5907 e = edges[i];
5908 if (!d3_geom_voronoiConnectEdge(e, extent) || !clip(e) || abs(e.a.x - e.b.x) < ε && abs(e.a.y - e.b.y) < ε) {
5909 e.a = e.b = null;
5910 edges.splice(i, 1);
5911 }
5912 }
5913 }
5914 function d3_geom_voronoiConnectEdge(edge, extent) {
5915 var vb = edge.b;
5916 if (vb) return true;
5917 var va = edge.a, x0 = extent[0][0], x1 = extent[1][0], y0 = extent[0][1], y1 = extent[1][1], lSite = edge.l, rSite = edge.r, lx = lSite.x, ly = lSite.y, rx = rSite.x, ry = rSite.y, fx = (lx + rx) / 2, fy = (ly + ry) / 2, fm, fb;
5918 if (ry === ly) {
5919 if (fx < x0 || fx >= x1) return;
5920 if (lx > rx) {
5921 if (!va) va = {
5922 x: fx,
5923 y: y0
5924 }; else if (va.y >= y1) return;
5925 vb = {
5926 x: fx,
5927 y: y1
5928 };
5929 } else {
5930 if (!va) va = {
5931 x: fx,
5932 y: y1
5933 }; else if (va.y < y0) return;
5934 vb = {
5935 x: fx,
5936 y: y0
5937 };
5938 }
5939 } else {
5940 fm = (lx - rx) / (ry - ly);
5941 fb = fy - fm * fx;
5942 if (fm < -1 || fm > 1) {
5943 if (lx > rx) {
5944 if (!va) va = {
5945 x: (y0 - fb) / fm,
5946 y: y0
5947 }; else if (va.y >= y1) return;
5948 vb = {
5949 x: (y1 - fb) / fm,
5950 y: y1
5951 };
5952 } else {
5953 if (!va) va = {
5954 x: (y1 - fb) / fm,
5955 y: y1
5956 }; else if (va.y < y0) return;
5957 vb = {
5958 x: (y0 - fb) / fm,
5959 y: y0
5960 };
5961 }
5962 } else {
5963 if (ly < ry) {
5964 if (!va) va = {
5965 x: x0,
5966 y: fm * x0 + fb
5967 }; else if (va.x >= x1) return;
5968 vb = {
5969 x: x1,
5970 y: fm * x1 + fb
5971 };
5972 } else {
5973 if (!va) va = {
5974 x: x1,
5975 y: fm * x1 + fb
5976 }; else if (va.x < x0) return;
5977 vb = {
5978 x: x0,
5979 y: fm * x0 + fb
5980 };
5981 }
5982 }
5983 }
5984 edge.a = va;
5985 edge.b = vb;
5986 return true;
5987 }
5988 function d3_geom_voronoiEdge(lSite, rSite) {
5989 this.l = lSite;
5990 this.r = rSite;
5991 this.a = this.b = null;
5992 }
5993 function d3_geom_voronoiCreateEdge(lSite, rSite, va, vb) {
5994 var edge = new d3_geom_voronoiEdge(lSite, rSite);
5995 d3_geom_voronoiEdges.push(edge);
5996 if (va) d3_geom_voronoiSetEdgeEnd(edge, lSite, rSite, va);
5997 if (vb) d3_geom_voronoiSetEdgeEnd(edge, rSite, lSite, vb);
5998 d3_geom_voronoiCells[lSite.i].edges.push(new d3_geom_voronoiHalfEdge(edge, lSite, rSite));
5999 d3_geom_voronoiCells[rSite.i].edges.push(new d3_geom_voronoiHalfEdge(edge, rSite, lSite));
6000 return edge;
6001 }
6002 function d3_geom_voronoiCreateBorderEdge(lSite, va, vb) {
6003 var edge = new d3_geom_voronoiEdge(lSite, null);
6004 edge.a = va;
6005 edge.b = vb;
6006 d3_geom_voronoiEdges.push(edge);
6007 return edge;
6008 }
6009 function d3_geom_voronoiSetEdgeEnd(edge, lSite, rSite, vertex) {
6010 if (!edge.a && !edge.b) {
6011 edge.a = vertex;
6012 edge.l = lSite;
6013 edge.r = rSite;
6014 } else if (edge.l === rSite) {
6015 edge.b = vertex;
6016 } else {
6017 edge.a = vertex;
6018 }
6019 }
6020 function d3_geom_voronoiHalfEdge(edge, lSite, rSite) {
6021 var va = edge.a, vb = edge.b;
6022 this.edge = edge;
6023 this.site = lSite;
6024 this.angle = rSite ? Math.atan2(rSite.y - lSite.y, rSite.x - lSite.x) : edge.l === lSite ? Math.atan2(vb.x - va.x, va.y - vb.y) : Math.atan2(va.x - vb.x, vb.y - va.y);
6025 }
6026 d3_geom_voronoiHalfEdge.prototype = {
6027 start: function() {
6028 return this.edge.l === this.site ? this.edge.a : this.edge.b;
6029 },
6030 end: function() {
6031 return this.edge.l === this.site ? this.edge.b : this.edge.a;
6032 }
6033 };
6034 function d3_geom_voronoiRedBlackTree() {
6035 this._ = null;
6036 }
6037 function d3_geom_voronoiRedBlackNode(node) {
6038 node.U = node.C = node.L = node.R = node.P = node.N = null;
6039 }
6040 d3_geom_voronoiRedBlackTree.prototype = {
6041 insert: function(after, node) {
6042 var parent, grandpa, uncle;
6043 if (after) {
6044 node.P = after;
6045 node.N = after.N;
6046 if (after.N) after.N.P = node;
6047 after.N = node;
6048 if (after.R) {
6049 after = after.R;
6050 while (after.L) after = after.L;
6051 after.L = node;
6052 } else {
6053 after.R = node;
6054 }
6055 parent = after;
6056 } else if (this._) {
6057 after = d3_geom_voronoiRedBlackFirst(this._);
6058 node.P = null;
6059 node.N = after;
6060 after.P = after.L = node;
6061 parent = after;
6062 } else {
6063 node.P = node.N = null;
6064 this._ = node;
6065 parent = null;
6066 }
6067 node.L = node.R = null;
6068 node.U = parent;
6069 node.C = true;
6070 after = node;
6071 while (parent && parent.C) {
6072 grandpa = parent.U;
6073 if (parent === grandpa.L) {
6074 uncle = grandpa.R;
6075 if (uncle && uncle.C) {
6076 parent.C = uncle.C = false;
6077 grandpa.C = true;
6078 after = grandpa;
6079 } else {
6080 if (after === parent.R) {
6081 d3_geom_voronoiRedBlackRotateLeft(this, parent);
6082 after = parent;
6083 parent = after.U;
6084 }
6085 parent.C = false;
6086 grandpa.C = true;
6087 d3_geom_voronoiRedBlackRotateRight(this, grandpa);
6088 }
6089 } else {
6090 uncle = grandpa.L;
6091 if (uncle && uncle.C) {
6092 parent.C = uncle.C = false;
6093 grandpa.C = true;
6094 after = grandpa;
6095 } else {
6096 if (after === parent.L) {
6097 d3_geom_voronoiRedBlackRotateRight(this, parent);
6098 after = parent;
6099 parent = after.U;
6100 }
6101 parent.C = false;
6102 grandpa.C = true;
6103 d3_geom_voronoiRedBlackRotateLeft(this, grandpa);
6104 }
6105 }
6106 parent = after.U;
6107 }
6108 this._.C = false;
6109 },
6110 remove: function(node) {
6111 if (node.N) node.N.P = node.P;
6112 if (node.P) node.P.N = node.N;
6113 node.N = node.P = null;
6114 var parent = node.U, sibling, left = node.L, right = node.R, next, red;
6115 if (!left) next = right; else if (!right) next = left; else next = d3_geom_voronoiRedBlackFirst(right);
6116 if (parent) {
6117 if (parent.L === node) parent.L = next; else parent.R = next;
6118 } else {
6119 this._ = next;
6120 }
6121 if (left && right) {
6122 red = next.C;
6123 next.C = node.C;
6124 next.L = left;
6125 left.U = next;
6126 if (next !== right) {
6127 parent = next.U;
6128 next.U = node.U;
6129 node = next.R;
6130 parent.L = node;
6131 next.R = right;
6132 right.U = next;
6133 } else {
6134 next.U = parent;
6135 parent = next;
6136 node = next.R;
6137 }
6138 } else {
6139 red = node.C;
6140 node = next;
6141 }
6142 if (node) node.U = parent;
6143 if (red) return;
6144 if (node && node.C) {
6145 node.C = false;
6146 return;
6147 }
6148 do {
6149 if (node === this._) break;
6150 if (node === parent.L) {
6151 sibling = parent.R;
6152 if (sibling.C) {
6153 sibling.C = false;
6154 parent.C = true;
6155 d3_geom_voronoiRedBlackRotateLeft(this, parent);
6156 sibling = parent.R;
6157 }
6158 if (sibling.L && sibling.L.C || sibling.R && sibling.R.C) {
6159 if (!sibling.R || !sibling.R.C) {
6160 sibling.L.C = false;
6161 sibling.C = true;
6162 d3_geom_voronoiRedBlackRotateRight(this, sibling);
6163 sibling = parent.R;
6164 }
6165 sibling.C = parent.C;
6166 parent.C = sibling.R.C = false;
6167 d3_geom_voronoiRedBlackRotateLeft(this, parent);
6168 node = this._;
6169 break;
6170 }
6171 } else {
6172 sibling = parent.L;
6173 if (sibling.C) {
6174 sibling.C = false;
6175 parent.C = true;
6176 d3_geom_voronoiRedBlackRotateRight(this, parent);
6177 sibling = parent.L;
6178 }
6179 if (sibling.L && sibling.L.C || sibling.R && sibling.R.C) {
6180 if (!sibling.L || !sibling.L.C) {
6181 sibling.R.C = false;
6182 sibling.C = true;
6183 d3_geom_voronoiRedBlackRotateLeft(this, sibling);
6184 sibling = parent.L;
6185 }
6186 sibling.C = parent.C;
6187 parent.C = sibling.L.C = false;
6188 d3_geom_voronoiRedBlackRotateRight(this, parent);
6189 node = this._;
6190 break;
6191 }
6192 }
6193 sibling.C = true;
6194 node = parent;
6195 parent = parent.U;
6196 } while (!node.C);
6197 if (node) node.C = false;
6198 }
6199 };
6200 function d3_geom_voronoiRedBlackRotateLeft(tree, node) {
6201 var p = node, q = node.R, parent = p.U;
6202 if (parent) {
6203 if (parent.L === p) parent.L = q; else parent.R = q;
6204 } else {
6205 tree._ = q;
6206 }
6207 q.U = parent;
6208 p.U = q;
6209 p.R = q.L;
6210 if (p.R) p.R.U = p;
6211 q.L = p;
6212 }
6213 function d3_geom_voronoiRedBlackRotateRight(tree, node) {
6214 var p = node, q = node.L, parent = p.U;
6215 if (parent) {
6216 if (parent.L === p) parent.L = q; else parent.R = q;
6217 } else {
6218 tree._ = q;
6219 }
6220 q.U = parent;
6221 p.U = q;
6222 p.L = q.R;
6223 if (p.L) p.L.U = p;
6224 q.R = p;
6225 }
6226 function d3_geom_voronoiRedBlackFirst(node) {
6227 while (node.L) node = node.L;
6228 return node;
6229 }
6230 function d3_geom_voronoi(sites, bbox) {
6231 var site = sites.sort(d3_geom_voronoiVertexOrder).pop(), x0, y0, circle;
6232 d3_geom_voronoiEdges = [];
6233 d3_geom_voronoiCells = new Array(sites.length);
6234 d3_geom_voronoiBeaches = new d3_geom_voronoiRedBlackTree();
6235 d3_geom_voronoiCircles = new d3_geom_voronoiRedBlackTree();
6236 while (true) {
6237 circle = d3_geom_voronoiFirstCircle;
6238 if (site && (!circle || site.y < circle.y || site.y === circle.y && site.x < circle.x)) {
6239 if (site.x !== x0 || site.y !== y0) {
6240 d3_geom_voronoiCells[site.i] = new d3_geom_voronoiCell(site);
6241 d3_geom_voronoiAddBeach(site);
6242 x0 = site.x, y0 = site.y;
6243 }
6244 site = sites.pop();
6245 } else if (circle) {
6246 d3_geom_voronoiRemoveBeach(circle.arc);
6247 } else {
6248 break;
6249 }
6250 }
6251 if (bbox) d3_geom_voronoiClipEdges(bbox), d3_geom_voronoiCloseCells(bbox);
6252 var diagram = {
6253 cells: d3_geom_voronoiCells,
6254 edges: d3_geom_voronoiEdges
6255 };
6256 d3_geom_voronoiBeaches = d3_geom_voronoiCircles = d3_geom_voronoiEdges = d3_geom_voronoiCells = null;
6257 return diagram;
6258 }
6259 function d3_geom_voronoiVertexOrder(a, b) {
6260 return b.y - a.y || b.x - a.x;
6261 }
6262 d3.geom.voronoi = function(points) {
6263 var x = d3_geom_pointX, y = d3_geom_pointY, fx = x, fy = y, clipExtent = d3_geom_voronoiClipExtent;
6264 if (points) return voronoi(points);
6265 function voronoi(data) {
6266 var polygons = new Array(data.length), x0 = clipExtent[0][0], y0 = clipExtent[0][1], x1 = clipExtent[1][0], y1 = clipExtent[1][1];
6267 d3_geom_voronoi(sites(data), clipExtent).cells.forEach(function(cell, i) {
6268 var edges = cell.edges, site = cell.site, polygon = polygons[i] = edges.length ? edges.map(function(e) {
6269 var s = e.start();
6270 return [ s.x, s.y ];
6271 }) : site.x >= x0 && site.x <= x1 && site.y >= y0 && site.y <= y1 ? [ [ x0, y1 ], [ x1, y1 ], [ x1, y0 ], [ x0, y0 ] ] : [];
6272 polygon.point = data[i];
6273 });
6274 return polygons;
6275 }
6276 function sites(data) {
6277 return data.map(function(d, i) {
6278 return {
6279 x: Math.round(fx(d, i) / ε) * ε,
6280 y: Math.round(fy(d, i) / ε) * ε,
6281 i: i
6282 };
6283 });
6284 }
6285 voronoi.links = function(data) {
6286 return d3_geom_voronoi(sites(data)).edges.filter(function(edge) {
6287 return edge.l && edge.r;
6288 }).map(function(edge) {
6289 return {
6290 source: data[edge.l.i],
6291 target: data[edge.r.i]
6292 };
6293 });
6294 };
6295 voronoi.triangles = function(data) {
6296 var triangles = [];
6297 d3_geom_voronoi(sites(data)).cells.forEach(function(cell, i) {
6298 var site = cell.site, edges = cell.edges.sort(d3_geom_voronoiHalfEdgeOrder), j = -1, m = edges.length, e0, s0, e1 = edges[m - 1].edge, s1 = e1.l === site ? e1.r : e1.l;
6299 while (++j < m) {
6300 e0 = e1;
6301 s0 = s1;
6302 e1 = edges[j].edge;
6303 s1 = e1.l === site ? e1.r : e1.l;
6304 if (i < s0.i && i < s1.i && d3_geom_voronoiTriangleArea(site, s0, s1) < 0) {
6305 triangles.push([ data[i], data[s0.i], data[s1.i] ]);
6306 }
6307 }
6308 });
6309 return triangles;
6310 };
6311 voronoi.x = function(_) {
6312 return arguments.length ? (fx = d3_functor(x = _), voronoi) : x;
6313 };
6314 voronoi.y = function(_) {
6315 return arguments.length ? (fy = d3_functor(y = _), voronoi) : y;
6316 };
6317 voronoi.clipExtent = function(_) {
6318 if (!arguments.length) return clipExtent === d3_geom_voronoiClipExtent ? null : clipExtent;
6319 clipExtent = _ == null ? d3_geom_voronoiClipExtent : _;
6320 return voronoi;
6321 };
6322 voronoi.size = function(_) {
6323 if (!arguments.length) return clipExtent === d3_geom_voronoiClipExtent ? null : clipExtent && clipExtent[1];
6324 return voronoi.clipExtent(_ && [ [ 0, 0 ], _ ]);
6325 };
6326 return voronoi;
6327 };
6328 var d3_geom_voronoiClipExtent = [ [ -1e6, -1e6 ], [ 1e6, 1e6 ] ];
6329 function d3_geom_voronoiTriangleArea(a, b, c) {
6330 return (a.x - c.x) * (b.y - a.y) - (a.x - b.x) * (c.y - a.y);
6331 }
6332 d3.geom.delaunay = function(vertices) {
6333 return d3.geom.voronoi().triangles(vertices);
6334 };
6335 d3.geom.quadtree = function(points, x1, y1, x2, y2) {
6336 var x = d3_geom_pointX, y = d3_geom_pointY, compat;
6337 if (compat = arguments.length) {
6338 x = d3_geom_quadtreeCompatX;
6339 y = d3_geom_quadtreeCompatY;
6340 if (compat === 3) {
6341 y2 = y1;
6342 x2 = x1;
6343 y1 = x1 = 0;
6344 }
6345 return quadtree(points);
6346 }
6347 function quadtree(data) {
6348 var d, fx = d3_functor(x), fy = d3_functor(y), xs, ys, i, n, x1_, y1_, x2_, y2_;
6349 if (x1 != null) {
6350 x1_ = x1, y1_ = y1, x2_ = x2, y2_ = y2;
6351 } else {
6352 x2_ = y2_ = -(x1_ = y1_ = Infinity);
6353 xs = [], ys = [];
6354 n = data.length;
6355 if (compat) for (i = 0; i < n; ++i) {
6356 d = data[i];
6357 if (d.x < x1_) x1_ = d.x;
6358 if (d.y < y1_) y1_ = d.y;
6359 if (d.x > x2_) x2_ = d.x;
6360 if (d.y > y2_) y2_ = d.y;
6361 xs.push(d.x);
6362 ys.push(d.y);
6363 } else for (i = 0; i < n; ++i) {
6364 var x_ = +fx(d = data[i], i), y_ = +fy(d, i);
6365 if (x_ < x1_) x1_ = x_;
6366 if (y_ < y1_) y1_ = y_;
6367 if (x_ > x2_) x2_ = x_;
6368 if (y_ > y2_) y2_ = y_;
6369 xs.push(x_);
6370 ys.push(y_);
6371 }
6372 }
6373 var dx = x2_ - x1_, dy = y2_ - y1_;
6374 if (dx > dy) y2_ = y1_ + dx; else x2_ = x1_ + dy;
6375 function insert(n, d, x, y, x1, y1, x2, y2) {
6376 if (isNaN(x) || isNaN(y)) return;
6377 if (n.leaf) {
6378 var nx = n.x, ny = n.y;
6379 if (nx != null) {
6380 if (abs(nx - x) + abs(ny - y) < .01) {
6381 insertChild(n, d, x, y, x1, y1, x2, y2);
6382 } else {
6383 var nPoint = n.point;
6384 n.x = n.y = n.point = null;
6385 insertChild(n, nPoint, nx, ny, x1, y1, x2, y2);
6386 insertChild(n, d, x, y, x1, y1, x2, y2);
6387 }
6388 } else {
6389 n.x = x, n.y = y, n.point = d;
6390 }
6391 } else {
6392 insertChild(n, d, x, y, x1, y1, x2, y2);
6393 }
6394 }
6395 function insertChild(n, d, x, y, x1, y1, x2, y2) {
6396 var xm = (x1 + x2) * .5, ym = (y1 + y2) * .5, right = x >= xm, below = y >= ym, i = below << 1 | right;
6397 n.leaf = false;
6398 n = n.nodes[i] || (n.nodes[i] = d3_geom_quadtreeNode());
6399 if (right) x1 = xm; else x2 = xm;
6400 if (below) y1 = ym; else y2 = ym;
6401 insert(n, d, x, y, x1, y1, x2, y2);
6402 }
6403 var root = d3_geom_quadtreeNode();
6404 root.add = function(d) {
6405 insert(root, d, +fx(d, ++i), +fy(d, i), x1_, y1_, x2_, y2_);
6406 };
6407 root.visit = function(f) {
6408 d3_geom_quadtreeVisit(f, root, x1_, y1_, x2_, y2_);
6409 };
6410 root.find = function(point) {
6411 return d3_geom_quadtreeFind(root, point[0], point[1], x1_, y1_, x2_, y2_);
6412 };
6413 i = -1;
6414 if (x1 == null) {
6415 while (++i < n) {
6416 insert(root, data[i], xs[i], ys[i], x1_, y1_, x2_, y2_);
6417 }
6418 --i;
6419 } else data.forEach(root.add);
6420 xs = ys = data = d = null;
6421 return root;
6422 }
6423 quadtree.x = function(_) {
6424 return arguments.length ? (x = _, quadtree) : x;
6425 };
6426 quadtree.y = function(_) {
6427 return arguments.length ? (y = _, quadtree) : y;
6428 };
6429 quadtree.extent = function(_) {
6430 if (!arguments.length) return x1 == null ? null : [ [ x1, y1 ], [ x2, y2 ] ];
6431 if (_ == null) x1 = y1 = x2 = y2 = null; else x1 = +_[0][0], y1 = +_[0][1], x2 = +_[1][0],
6432 y2 = +_[1][1];
6433 return quadtree;
6434 };
6435 quadtree.size = function(_) {
6436 if (!arguments.length) return x1 == null ? null : [ x2 - x1, y2 - y1 ];
6437 if (_ == null) x1 = y1 = x2 = y2 = null; else x1 = y1 = 0, x2 = +_[0], y2 = +_[1];
6438 return quadtree;
6439 };
6440 return quadtree;
6441 };
6442 function d3_geom_quadtreeCompatX(d) {
6443 return d.x;
6444 }
6445 function d3_geom_quadtreeCompatY(d) {
6446 return d.y;
6447 }
6448 function d3_geom_quadtreeNode() {
6449 return {
6450 leaf: true,
6451 nodes: [],
6452 point: null,
6453 x: null,
6454 y: null
6455 };
6456 }
6457 function d3_geom_quadtreeVisit(f, node, x1, y1, x2, y2) {
6458 if (!f(node, x1, y1, x2, y2)) {
6459 var sx = (x1 + x2) * .5, sy = (y1 + y2) * .5, children = node.nodes;
6460 if (children[0]) d3_geom_quadtreeVisit(f, children[0], x1, y1, sx, sy);
6461 if (children[1]) d3_geom_quadtreeVisit(f, children[1], sx, y1, x2, sy);
6462 if (children[2]) d3_geom_quadtreeVisit(f, children[2], x1, sy, sx, y2);
6463 if (children[3]) d3_geom_quadtreeVisit(f, children[3], sx, sy, x2, y2);
6464 }
6465 }
6466 function d3_geom_quadtreeFind(root, x, y, x0, y0, x3, y3) {
6467 var minDistance2 = Infinity, closestPoint;
6468 (function find(node, x1, y1, x2, y2) {
6469 if (x1 > x3 || y1 > y3 || x2 < x0 || y2 < y0) return;
6470 if (point = node.point) {
6471 var point, dx = x - node.x, dy = y - node.y, distance2 = dx * dx + dy * dy;
6472 if (distance2 < minDistance2) {
6473 var distance = Math.sqrt(minDistance2 = distance2);
6474 x0 = x - distance, y0 = y - distance;
6475 x3 = x + distance, y3 = y + distance;
6476 closestPoint = point;
6477 }
6478 }
6479 var children = node.nodes, xm = (x1 + x2) * .5, ym = (y1 + y2) * .5, right = x >= xm, below = y >= ym;
6480 for (var i = below << 1 | right, j = i + 4; i < j; ++i) {
6481 if (node = children[i & 3]) switch (i & 3) {
6482 case 0:
6483 find(node, x1, y1, xm, ym);
6484 break;
6485
6486 case 1:
6487 find(node, xm, y1, x2, ym);
6488 break;
6489
6490 case 2:
6491 find(node, x1, ym, xm, y2);
6492 break;
6493
6494 case 3:
6495 find(node, xm, ym, x2, y2);
6496 break;
6497 }
6498 }
6499 })(root, x0, y0, x3, y3);
6500 return closestPoint;
6501 }
6502 d3.interpolateRgb = d3_interpolateRgb;
6503 function d3_interpolateRgb(a, b) {
6504 a = d3.rgb(a);
6505 b = d3.rgb(b);
6506 var ar = a.r, ag = a.g, ab = a.b, br = b.r - ar, bg = b.g - ag, bb = b.b - ab;
6507 return function(t) {
6508 return "#" + d3_rgb_hex(Math.round(ar + br * t)) + d3_rgb_hex(Math.round(ag + bg * t)) + d3_rgb_hex(Math.round(ab + bb * t));
6509 };
6510 }
6511 d3.interpolateObject = d3_interpolateObject;
6512 function d3_interpolateObject(a, b) {
6513 var i = {}, c = {}, k;
6514 for (k in a) {
6515 if (k in b) {
6516 i[k] = d3_interpolate(a[k], b[k]);
6517 } else {
6518 c[k] = a[k];
6519 }
6520 }
6521 for (k in b) {
6522 if (!(k in a)) {
6523 c[k] = b[k];
6524 }
6525 }
6526 return function(t) {
6527 for (k in i) c[k] = i[k](t);
6528 return c;
6529 };
6530 }
6531 d3.interpolateNumber = d3_interpolateNumber;
6532 function d3_interpolateNumber(a, b) {
6533 a = +a, b = +b;
6534 return function(t) {
6535 return a * (1 - t) + b * t;
6536 };
6537 }
6538 d3.interpolateString = d3_interpolateString;
6539 function d3_interpolateString(a, b) {
6540 var bi = d3_interpolate_numberA.lastIndex = d3_interpolate_numberB.lastIndex = 0, am, bm, bs, i = -1, s = [], q = [];
6541 a = a + "", b = b + "";
6542 while ((am = d3_interpolate_numberA.exec(a)) && (bm = d3_interpolate_numberB.exec(b))) {
6543 if ((bs = bm.index) > bi) {
6544 bs = b.slice(bi, bs);
6545 if (s[i]) s[i] += bs; else s[++i] = bs;
6546 }
6547 if ((am = am[0]) === (bm = bm[0])) {
6548 if (s[i]) s[i] += bm; else s[++i] = bm;
6549 } else {
6550 s[++i] = null;
6551 q.push({
6552 i: i,
6553 x: d3_interpolateNumber(am, bm)
6554 });
6555 }
6556 bi = d3_interpolate_numberB.lastIndex;
6557 }
6558 if (bi < b.length) {
6559 bs = b.slice(bi);
6560 if (s[i]) s[i] += bs; else s[++i] = bs;
6561 }
6562 return s.length < 2 ? q[0] ? (b = q[0].x, function(t) {
6563 return b(t) + "";
6564 }) : function() {
6565 return b;
6566 } : (b = q.length, function(t) {
6567 for (var i = 0, o; i < b; ++i) s[(o = q[i]).i] = o.x(t);
6568 return s.join("");
6569 });
6570 }
6571 var d3_interpolate_numberA = /[-+]?(?:\d+\.?\d*|\.?\d+)(?:[eE][-+]?\d+)?/g, d3_interpolate_numberB = new RegExp(d3_interpolate_numberA.source, "g");
6572 d3.interpolate = d3_interpolate;
6573 function d3_interpolate(a, b) {
6574 var i = d3.interpolators.length, f;
6575 while (--i >= 0 && !(f = d3.interpolators[i](a, b))) ;
6576 return f;
6577 }
6578 d3.interpolators = [ function(a, b) {
6579 var t = typeof b;
6580 return (t === "string" ? d3_rgb_names.has(b.toLowerCase()) || /^(#|rgb\(|hsl\()/i.test(b) ? d3_interpolateRgb : d3_interpolateString : b instanceof d3_color ? d3_interpolateRgb : Array.isArray(b) ? d3_interpolateArray : t === "object" && isNaN(b) ? d3_interpolateObject : d3_interpolateNumber)(a, b);
6581 } ];
6582 d3.interpolateArray = d3_interpolateArray;
6583 function d3_interpolateArray(a, b) {
6584 var x = [], c = [], na = a.length, nb = b.length, n0 = Math.min(a.length, b.length), i;
6585 for (i = 0; i < n0; ++i) x.push(d3_interpolate(a[i], b[i]));
6586 for (;i < na; ++i) c[i] = a[i];
6587 for (;i < nb; ++i) c[i] = b[i];
6588 return function(t) {
6589 for (i = 0; i < n0; ++i) c[i] = x[i](t);
6590 return c;
6591 };
6592 }
6593 var d3_ease_default = function() {
6594 return d3_identity;
6595 };
6596 var d3_ease = d3.map({
6597 linear: d3_ease_default,
6598 poly: d3_ease_poly,
6599 quad: function() {
6600 return d3_ease_quad;
6601 },
6602 cubic: function() {
6603 return d3_ease_cubic;
6604 },
6605 sin: function() {
6606 return d3_ease_sin;
6607 },
6608 exp: function() {
6609 return d3_ease_exp;
6610 },
6611 circle: function() {
6612 return d3_ease_circle;
6613 },
6614 elastic: d3_ease_elastic,
6615 back: d3_ease_back,
6616 bounce: function() {
6617 return d3_ease_bounce;
6618 }
6619 });
6620 var d3_ease_mode = d3.map({
6621 "in": d3_identity,
6622 out: d3_ease_reverse,
6623 "in-out": d3_ease_reflect,
6624 "out-in": function(f) {
6625 return d3_ease_reflect(d3_ease_reverse(f));
6626 }
6627 });
6628 d3.ease = function(name) {
6629 var i = name.indexOf("-"), t = i >= 0 ? name.slice(0, i) : name, m = i >= 0 ? name.slice(i + 1) : "in";
6630 t = d3_ease.get(t) || d3_ease_default;
6631 m = d3_ease_mode.get(m) || d3_identity;
6632 return d3_ease_clamp(m(t.apply(null, d3_arraySlice.call(arguments, 1))));
6633 };
6634 function d3_ease_clamp(f) {
6635 return function(t) {
6636 return t <= 0 ? 0 : t >= 1 ? 1 : f(t);
6637 };
6638 }
6639 function d3_ease_reverse(f) {
6640 return function(t) {
6641 return 1 - f(1 - t);
6642 };
6643 }
6644 function d3_ease_reflect(f) {
6645 return function(t) {
6646 return .5 * (t < .5 ? f(2 * t) : 2 - f(2 - 2 * t));
6647 };
6648 }
6649 function d3_ease_quad(t) {
6650 return t * t;
6651 }
6652 function d3_ease_cubic(t) {
6653 return t * t * t;
6654 }
6655 function d3_ease_cubicInOut(t) {
6656 if (t <= 0) return 0;
6657 if (t >= 1) return 1;
6658 var t2 = t * t, t3 = t2 * t;
6659 return 4 * (t < .5 ? t3 : 3 * (t - t2) + t3 - .75);
6660 }
6661 function d3_ease_poly(e) {
6662 return function(t) {
6663 return Math.pow(t, e);
6664 };
6665 }
6666 function d3_ease_sin(t) {
6667 return 1 - Math.cos(t * halfπ);
6668 }
6669 function d3_ease_exp(t) {
6670 return Math.pow(2, 10 * (t - 1));
6671 }
6672 function d3_ease_circle(t) {
6673 return 1 - Math.sqrt(1 - t * t);
6674 }
6675 function d3_ease_elastic(a, p) {
6676 var s;
6677 if (arguments.length < 2) p = .45;
6678 if (arguments.length) s = p / τ * Math.asin(1 / a); else a = 1, s = p / 4;
6679 return function(t) {
6680 return 1 + a * Math.pow(2, -10 * t) * Math.sin((t - s) * τ / p);
6681 };
6682 }
6683 function d3_ease_back(s) {
6684 if (!s) s = 1.70158;
6685 return function(t) {
6686 return t * t * ((s + 1) * t - s);
6687 };
6688 }
6689 function d3_ease_bounce(t) {
6690 return t < 1 / 2.75 ? 7.5625 * t * t : t < 2 / 2.75 ? 7.5625 * (t -= 1.5 / 2.75) * t + .75 : t < 2.5 / 2.75 ? 7.5625 * (t -= 2.25 / 2.75) * t + .9375 : 7.5625 * (t -= 2.625 / 2.75) * t + .984375;
6691 }
6692 d3.interpolateHcl = d3_interpolateHcl;
6693 function d3_interpolateHcl(a, b) {
6694 a = d3.hcl(a);
6695 b = d3.hcl(b);
6696 var ah = a.h, ac = a.c, al = a.l, bh = b.h - ah, bc = b.c - ac, bl = b.l - al;
6697 if (isNaN(bc)) bc = 0, ac = isNaN(ac) ? b.c : ac;
6698 if (isNaN(bh)) bh = 0, ah = isNaN(ah) ? b.h : ah; else if (bh > 180) bh -= 360; else if (bh < -180) bh += 360;
6699 return function(t) {
6700 return d3_hcl_lab(ah + bh * t, ac + bc * t, al + bl * t) + "";
6701 };
6702 }
6703 d3.interpolateHsl = d3_interpolateHsl;
6704 function d3_interpolateHsl(a, b) {
6705 a = d3.hsl(a);
6706 b = d3.hsl(b);
6707 var ah = a.h, as = a.s, al = a.l, bh = b.h - ah, bs = b.s - as, bl = b.l - al;
6708 if (isNaN(bs)) bs = 0, as = isNaN(as) ? b.s : as;
6709 if (isNaN(bh)) bh = 0, ah = isNaN(ah) ? b.h : ah; else if (bh > 180) bh -= 360; else if (bh < -180) bh += 360;
6710 return function(t) {
6711 return d3_hsl_rgb(ah + bh * t, as + bs * t, al + bl * t) + "";
6712 };
6713 }
6714 d3.interpolateLab = d3_interpolateLab;
6715 function d3_interpolateLab(a, b) {
6716 a = d3.lab(a);
6717 b = d3.lab(b);
6718 var al = a.l, aa = a.a, ab = a.b, bl = b.l - al, ba = b.a - aa, bb = b.b - ab;
6719 return function(t) {
6720 return d3_lab_rgb(al + bl * t, aa + ba * t, ab + bb * t) + "";
6721 };
6722 }
6723 d3.interpolateRound = d3_interpolateRound;
6724 function d3_interpolateRound(a, b) {
6725 b -= a;
6726 return function(t) {
6727 return Math.round(a + b * t);
6728 };
6729 }
6730 d3.transform = function(string) {
6731 var g = d3_document.createElementNS(d3.ns.prefix.svg, "g");
6732 return (d3.transform = function(string) {
6733 if (string != null) {
6734 g.setAttribute("transform", string);
6735 var t = g.transform.baseVal.consolidate();
6736 }
6737 return new d3_transform(t ? t.matrix : d3_transformIdentity);
6738 })(string);
6739 };
6740 function d3_transform(m) {
6741 var r0 = [ m.a, m.b ], r1 = [ m.c, m.d ], kx = d3_transformNormalize(r0), kz = d3_transformDot(r0, r1), ky = d3_transformNormalize(d3_transformCombine(r1, r0, -kz)) || 0;
6742 if (r0[0] * r1[1] < r1[0] * r0[1]) {
6743 r0[0] *= -1;
6744 r0[1] *= -1;
6745 kx *= -1;
6746 kz *= -1;
6747 }
6748 this.rotate = (kx ? Math.atan2(r0[1], r0[0]) : Math.atan2(-r1[0], r1[1])) * d3_degrees;
6749 this.translate = [ m.e, m.f ];
6750 this.scale = [ kx, ky ];
6751 this.skew = ky ? Math.atan2(kz, ky) * d3_degrees : 0;
6752 }
6753 d3_transform.prototype.toString = function() {
6754 return "translate(" + this.translate + ")rotate(" + this.rotate + ")skewX(" + this.skew + ")scale(" + this.scale + ")";
6755 };
6756 function d3_transformDot(a, b) {
6757 return a[0] * b[0] + a[1] * b[1];
6758 }
6759 function d3_transformNormalize(a) {
6760 var k = Math.sqrt(d3_transformDot(a, a));
6761 if (k) {
6762 a[0] /= k;
6763 a[1] /= k;
6764 }
6765 return k;
6766 }
6767 function d3_transformCombine(a, b, k) {
6768 a[0] += k * b[0];
6769 a[1] += k * b[1];
6770 return a;
6771 }
6772 var d3_transformIdentity = {
6773 a: 1,
6774 b: 0,
6775 c: 0,
6776 d: 1,
6777 e: 0,
6778 f: 0
6779 };
6780 d3.interpolateTransform = d3_interpolateTransform;
6781 function d3_interpolateTransformPop(s) {
6782 return s.length ? s.pop() + "," : "";
6783 }
6784 function d3_interpolateTranslate(ta, tb, s, q) {
6785 if (ta[0] !== tb[0] || ta[1] !== tb[1]) {
6786 var i = s.push("translate(", null, ",", null, ")");
6787 q.push({
6788 i: i - 4,
6789 x: d3_interpolateNumber(ta[0], tb[0])
6790 }, {
6791 i: i - 2,
6792 x: d3_interpolateNumber(ta[1], tb[1])
6793 });
6794 } else if (tb[0] || tb[1]) {
6795 s.push("translate(" + tb + ")");
6796 }
6797 }
6798 function d3_interpolateRotate(ra, rb, s, q) {
6799 if (ra !== rb) {
6800 if (ra - rb > 180) rb += 360; else if (rb - ra > 180) ra += 360;
6801 q.push({
6802 i: s.push(d3_interpolateTransformPop(s) + "rotate(", null, ")") - 2,
6803 x: d3_interpolateNumber(ra, rb)
6804 });
6805 } else if (rb) {
6806 s.push(d3_interpolateTransformPop(s) + "rotate(" + rb + ")");
6807 }
6808 }
6809 function d3_interpolateSkew(wa, wb, s, q) {
6810 if (wa !== wb) {
6811 q.push({
6812 i: s.push(d3_interpolateTransformPop(s) + "skewX(", null, ")") - 2,
6813 x: d3_interpolateNumber(wa, wb)
6814 });
6815 } else if (wb) {
6816 s.push(d3_interpolateTransformPop(s) + "skewX(" + wb + ")");
6817 }
6818 }
6819 function d3_interpolateScale(ka, kb, s, q) {
6820 if (ka[0] !== kb[0] || ka[1] !== kb[1]) {
6821 var i = s.push(d3_interpolateTransformPop(s) + "scale(", null, ",", null, ")");
6822 q.push({
6823 i: i - 4,
6824 x: d3_interpolateNumber(ka[0], kb[0])
6825 }, {
6826 i: i - 2,
6827 x: d3_interpolateNumber(ka[1], kb[1])
6828 });
6829 } else if (kb[0] !== 1 || kb[1] !== 1) {
6830 s.push(d3_interpolateTransformPop(s) + "scale(" + kb + ")");
6831 }
6832 }
6833 function d3_interpolateTransform(a, b) {
6834 var s = [], q = [];
6835 a = d3.transform(a), b = d3.transform(b);
6836 d3_interpolateTranslate(a.translate, b.translate, s, q);
6837 d3_interpolateRotate(a.rotate, b.rotate, s, q);
6838 d3_interpolateSkew(a.skew, b.skew, s, q);
6839 d3_interpolateScale(a.scale, b.scale, s, q);
6840 a = b = null;
6841 return function(t) {
6842 var i = -1, n = q.length, o;
6843 while (++i < n) s[(o = q[i]).i] = o.x(t);
6844 return s.join("");
6845 };
6846 }
6847 function d3_uninterpolateNumber(a, b) {
6848 b = (b -= a = +a) || 1 / b;
6849 return function(x) {
6850 return (x - a) / b;
6851 };
6852 }
6853 function d3_uninterpolateClamp(a, b) {
6854 b = (b -= a = +a) || 1 / b;
6855 return function(x) {
6856 return Math.max(0, Math.min(1, (x - a) / b));
6857 };
6858 }
6859 d3.layout = {};
6860 d3.layout.bundle = function() {
6861 return function(links) {
6862 var paths = [], i = -1, n = links.length;
6863 while (++i < n) paths.push(d3_layout_bundlePath(links[i]));
6864 return paths;
6865 };
6866 };
6867 function d3_layout_bundlePath(link) {
6868 var start = link.source, end = link.target, lca = d3_layout_bundleLeastCommonAncestor(start, end), points = [ start ];
6869 while (start !== lca) {
6870 start = start.parent;
6871 points.push(start);
6872 }
6873 var k = points.length;
6874 while (end !== lca) {
6875 points.splice(k, 0, end);
6876 end = end.parent;
6877 }
6878 return points;
6879 }
6880 function d3_layout_bundleAncestors(node) {
6881 var ancestors = [], parent = node.parent;
6882 while (parent != null) {
6883 ancestors.push(node);
6884 node = parent;
6885 parent = parent.parent;
6886 }
6887 ancestors.push(node);
6888 return ancestors;
6889 }
6890 function d3_layout_bundleLeastCommonAncestor(a, b) {
6891 if (a === b) return a;
6892 var aNodes = d3_layout_bundleAncestors(a), bNodes = d3_layout_bundleAncestors(b), aNode = aNodes.pop(), bNode = bNodes.pop(), sharedNode = null;
6893 while (aNode === bNode) {
6894 sharedNode = aNode;
6895 aNode = aNodes.pop();
6896 bNode = bNodes.pop();
6897 }
6898 return sharedNode;
6899 }
6900 d3.layout.chord = function() {
6901 var chord = {}, chords, groups, matrix, n, padding = 0, sortGroups, sortSubgroups, sortChords;
6902 function relayout() {
6903 var subgroups = {}, groupSums = [], groupIndex = d3.range(n), subgroupIndex = [], k, x, x0, i, j;
6904 chords = [];
6905 groups = [];
6906 k = 0, i = -1;
6907 while (++i < n) {
6908 x = 0, j = -1;
6909 while (++j < n) {
6910 x += matrix[i][j];
6911 }
6912 groupSums.push(x);
6913 subgroupIndex.push(d3.range(n));
6914 k += x;
6915 }
6916 if (sortGroups) {
6917 groupIndex.sort(function(a, b) {
6918 return sortGroups(groupSums[a], groupSums[b]);
6919 });
6920 }
6921 if (sortSubgroups) {
6922 subgroupIndex.forEach(function(d, i) {
6923 d.sort(function(a, b) {
6924 return sortSubgroups(matrix[i][a], matrix[i][b]);
6925 });
6926 });
6927 }
6928 k = (τ - padding * n) / k;
6929 x = 0, i = -1;
6930 while (++i < n) {
6931 x0 = x, j = -1;
6932 while (++j < n) {
6933 var di = groupIndex[i], dj = subgroupIndex[di][j], v = matrix[di][dj], a0 = x, a1 = x += v * k;
6934 subgroups[di + "-" + dj] = {
6935 index: di,
6936 subindex: dj,
6937 startAngle: a0,
6938 endAngle: a1,
6939 value: v
6940 };
6941 }
6942 groups[di] = {
6943 index: di,
6944 startAngle: x0,
6945 endAngle: x,
6946 value: groupSums[di]
6947 };
6948 x += padding;
6949 }
6950 i = -1;
6951 while (++i < n) {
6952 j = i - 1;
6953 while (++j < n) {
6954 var source = subgroups[i + "-" + j], target = subgroups[j + "-" + i];
6955 if (source.value || target.value) {
6956 chords.push(source.value < target.value ? {
6957 source: target,
6958 target: source
6959 } : {
6960 source: source,
6961 target: target
6962 });
6963 }
6964 }
6965 }
6966 if (sortChords) resort();
6967 }
6968 function resort() {
6969 chords.sort(function(a, b) {
6970 return sortChords((a.source.value + a.target.value) / 2, (b.source.value + b.target.value) / 2);
6971 });
6972 }
6973 chord.matrix = function(x) {
6974 if (!arguments.length) return matrix;
6975 n = (matrix = x) && matrix.length;
6976 chords = groups = null;
6977 return chord;
6978 };
6979 chord.padding = function(x) {
6980 if (!arguments.length) return padding;
6981 padding = x;
6982 chords = groups = null;
6983 return chord;
6984 };
6985 chord.sortGroups = function(x) {
6986 if (!arguments.length) return sortGroups;
6987 sortGroups = x;
6988 chords = groups = null;
6989 return chord;
6990 };
6991 chord.sortSubgroups = function(x) {
6992 if (!arguments.length) return sortSubgroups;
6993 sortSubgroups = x;
6994 chords = null;
6995 return chord;
6996 };
6997 chord.sortChords = function(x) {
6998 if (!arguments.length) return sortChords;
6999 sortChords = x;
7000 if (chords) resort();
7001 return chord;
7002 };
7003 chord.chords = function() {
7004 if (!chords) relayout();
7005 return chords;
7006 };
7007 chord.groups = function() {
7008 if (!groups) relayout();
7009 return groups;
7010 };
7011 return chord;
7012 };
7013 d3.layout.force = function() {
7014 var force = {}, event = d3.dispatch("start", "tick", "end"), timer, size = [ 1, 1 ], drag, alpha, friction = .9, linkDistance = d3_layout_forceLinkDistance, linkStrength = d3_layout_forceLinkStrength, charge = -30, chargeDistance2 = d3_layout_forceChargeDistance2, gravity = .1, theta2 = .64, nodes = [], links = [], distances, strengths, charges;
7015 function repulse(node) {
7016 return function(quad, x1, _, x2) {
7017 if (quad.point !== node) {
7018 var dx = quad.cx - node.x, dy = quad.cy - node.y, dw = x2 - x1, dn = dx * dx + dy * dy;
7019 if (dw * dw / theta2 < dn) {
7020 if (dn < chargeDistance2) {
7021 var k = quad.charge / dn;
7022 node.px -= dx * k;
7023 node.py -= dy * k;
7024 }
7025 return true;
7026 }
7027 if (quad.point && dn && dn < chargeDistance2) {
7028 var k = quad.pointCharge / dn;
7029 node.px -= dx * k;
7030 node.py -= dy * k;
7031 }
7032 }
7033 return !quad.charge;
7034 };
7035 }
7036 force.tick = function() {
7037 if ((alpha *= .99) < .005) {
7038 timer = null;
7039 event.end({
7040 type: "end",
7041 alpha: alpha = 0
7042 });
7043 return true;
7044 }
7045 var n = nodes.length, m = links.length, q, i, o, s, t, l, k, x, y;
7046 for (i = 0; i < m; ++i) {
7047 o = links[i];
7048 s = o.source;
7049 t = o.target;
7050 x = t.x - s.x;
7051 y = t.y - s.y;
7052 if (l = x * x + y * y) {
7053 l = alpha * strengths[i] * ((l = Math.sqrt(l)) - distances[i]) / l;
7054 x *= l;
7055 y *= l;
7056 t.x -= x * (k = s.weight + t.weight ? s.weight / (s.weight + t.weight) : .5);
7057 t.y -= y * k;
7058 s.x += x * (k = 1 - k);
7059 s.y += y * k;
7060 }
7061 }
7062 if (k = alpha * gravity) {
7063 x = size[0] / 2;
7064 y = size[1] / 2;
7065 i = -1;
7066 if (k) while (++i < n) {
7067 o = nodes[i];
7068 o.x += (x - o.x) * k;
7069 o.y += (y - o.y) * k;
7070 }
7071 }
7072 if (charge) {
7073 d3_layout_forceAccumulate(q = d3.geom.quadtree(nodes), alpha, charges);
7074 i = -1;
7075 while (++i < n) {
7076 if (!(o = nodes[i]).fixed) {
7077 q.visit(repulse(o));
7078 }
7079 }
7080 }
7081 i = -1;
7082 while (++i < n) {
7083 o = nodes[i];
7084 if (o.fixed) {
7085 o.x = o.px;
7086 o.y = o.py;
7087 } else {
7088 o.x -= (o.px - (o.px = o.x)) * friction;
7089 o.y -= (o.py - (o.py = o.y)) * friction;
7090 }
7091 }
7092 event.tick({
7093 type: "tick",
7094 alpha: alpha
7095 });
7096 };
7097 force.nodes = function(x) {
7098 if (!arguments.length) return nodes;
7099 nodes = x;
7100 return force;
7101 };
7102 force.links = function(x) {
7103 if (!arguments.length) return links;
7104 links = x;
7105 return force;
7106 };
7107 force.size = function(x) {
7108 if (!arguments.length) return size;
7109 size = x;
7110 return force;
7111 };
7112 force.linkDistance = function(x) {
7113 if (!arguments.length) return linkDistance;
7114 linkDistance = typeof x === "function" ? x : +x;
7115 return force;
7116 };
7117 force.distance = force.linkDistance;
7118 force.linkStrength = function(x) {
7119 if (!arguments.length) return linkStrength;
7120 linkStrength = typeof x === "function" ? x : +x;
7121 return force;
7122 };
7123 force.friction = function(x) {
7124 if (!arguments.length) return friction;
7125 friction = +x;
7126 return force;
7127 };
7128 force.charge = function(x) {
7129 if (!arguments.length) return charge;
7130 charge = typeof x === "function" ? x : +x;
7131 return force;
7132 };
7133 force.chargeDistance = function(x) {
7134 if (!arguments.length) return Math.sqrt(chargeDistance2);
7135 chargeDistance2 = x * x;
7136 return force;
7137 };
7138 force.gravity = function(x) {
7139 if (!arguments.length) return gravity;
7140 gravity = +x;
7141 return force;
7142 };
7143 force.theta = function(x) {
7144 if (!arguments.length) return Math.sqrt(theta2);
7145 theta2 = x * x;
7146 return force;
7147 };
7148 force.alpha = function(x) {
7149 if (!arguments.length) return alpha;
7150 x = +x;
7151 if (alpha) {
7152 if (x > 0) {
7153 alpha = x;
7154 } else {
7155 timer.c = null, timer.t = NaN, timer = null;
7156 event.end({
7157 type: "end",
7158 alpha: alpha = 0
7159 });
7160 }
7161 } else if (x > 0) {
7162 event.start({
7163 type: "start",
7164 alpha: alpha = x
7165 });
7166 timer = d3_timer(force.tick);
7167 }
7168 return force;
7169 };
7170 force.start = function() {
7171 var i, n = nodes.length, m = links.length, w = size[0], h = size[1], neighbors, o;
7172 for (i = 0; i < n; ++i) {
7173 (o = nodes[i]).index = i;
7174 o.weight = 0;
7175 }
7176 for (i = 0; i < m; ++i) {
7177 o = links[i];
7178 if (typeof o.source == "number") o.source = nodes[o.source];
7179 if (typeof o.target == "number") o.target = nodes[o.target];
7180 ++o.source.weight;
7181 ++o.target.weight;
7182 }
7183 for (i = 0; i < n; ++i) {
7184 o = nodes[i];
7185 if (isNaN(o.x)) o.x = position("x", w);
7186 if (isNaN(o.y)) o.y = position("y", h);
7187 if (isNaN(o.px)) o.px = o.x;
7188 if (isNaN(o.py)) o.py = o.y;
7189 }
7190 distances = [];
7191 if (typeof linkDistance === "function") for (i = 0; i < m; ++i) distances[i] = +linkDistance.call(this, links[i], i); else for (i = 0; i < m; ++i) distances[i] = linkDistance;
7192 strengths = [];
7193 if (typeof linkStrength === "function") for (i = 0; i < m; ++i) strengths[i] = +linkStrength.call(this, links[i], i); else for (i = 0; i < m; ++i) strengths[i] = linkStrength;
7194 charges = [];
7195 if (typeof charge === "function") for (i = 0; i < n; ++i) charges[i] = +charge.call(this, nodes[i], i); else for (i = 0; i < n; ++i) charges[i] = charge;
7196 function position(dimension, size) {
7197 if (!neighbors) {
7198 neighbors = new Array(n);
7199 for (j = 0; j < n; ++j) {
7200 neighbors[j] = [];
7201 }
7202 for (j = 0; j < m; ++j) {
7203 var o = links[j];
7204 neighbors[o.source.index].push(o.target);
7205 neighbors[o.target.index].push(o.source);
7206 }
7207 }
7208 var candidates = neighbors[i], j = -1, l = candidates.length, x;
7209 while (++j < l) if (!isNaN(x = candidates[j][dimension])) return x;
7210 return Math.random() * size;
7211 }
7212 return force.resume();
7213 };
7214 force.resume = function() {
7215 return force.alpha(.1);
7216 };
7217 force.stop = function() {
7218 return force.alpha(0);
7219 };
7220 force.drag = function() {
7221 if (!drag) drag = d3.behavior.drag().origin(d3_identity).on("dragstart.force", d3_layout_forceDragstart).on("drag.force", dragmove).on("dragend.force", d3_layout_forceDragend);
7222 if (!arguments.length) return drag;
7223 this.on("mouseover.force", d3_layout_forceMouseover).on("mouseout.force", d3_layout_forceMouseout).call(drag);
7224 };
7225 function dragmove(d) {
7226 d.px = d3.event.x, d.py = d3.event.y;
7227 force.resume();
7228 }
7229 return d3.rebind(force, event, "on");
7230 };
7231 function d3_layout_forceDragstart(d) {
7232 d.fixed |= 2;
7233 }
7234 function d3_layout_forceDragend(d) {
7235 d.fixed &= ~6;
7236 }
7237 function d3_layout_forceMouseover(d) {
7238 d.fixed |= 4;
7239 d.px = d.x, d.py = d.y;
7240 }
7241 function d3_layout_forceMouseout(d) {
7242 d.fixed &= ~4;
7243 }
7244 function d3_layout_forceAccumulate(quad, alpha, charges) {
7245 var cx = 0, cy = 0;
7246 quad.charge = 0;
7247 if (!quad.leaf) {
7248 var nodes = quad.nodes, n = nodes.length, i = -1, c;
7249 while (++i < n) {
7250 c = nodes[i];
7251 if (c == null) continue;
7252 d3_layout_forceAccumulate(c, alpha, charges);
7253 quad.charge += c.charge;
7254 cx += c.charge * c.cx;
7255 cy += c.charge * c.cy;
7256 }
7257 }
7258 if (quad.point) {
7259 if (!quad.leaf) {
7260 quad.point.x += Math.random() - .5;
7261 quad.point.y += Math.random() - .5;
7262 }
7263 var k = alpha * charges[quad.point.index];
7264 quad.charge += quad.pointCharge = k;
7265 cx += k * quad.point.x;
7266 cy += k * quad.point.y;
7267 }
7268 quad.cx = cx / quad.charge;
7269 quad.cy = cy / quad.charge;
7270 }
7271 var d3_layout_forceLinkDistance = 20, d3_layout_forceLinkStrength = 1, d3_layout_forceChargeDistance2 = Infinity;
7272 d3.layout.hierarchy = function() {
7273 var sort = d3_layout_hierarchySort, children = d3_layout_hierarchyChildren, value = d3_layout_hierarchyValue;
7274 function hierarchy(root) {
7275 var stack = [ root ], nodes = [], node;
7276 root.depth = 0;
7277 while ((node = stack.pop()) != null) {
7278 nodes.push(node);
7279 if ((childs = children.call(hierarchy, node, node.depth)) && (n = childs.length)) {
7280 var n, childs, child;
7281 while (--n >= 0) {
7282 stack.push(child = childs[n]);
7283 child.parent = node;
7284 child.depth = node.depth + 1;
7285 }
7286 if (value) node.value = 0;
7287 node.children = childs;
7288 } else {
7289 if (value) node.value = +value.call(hierarchy, node, node.depth) || 0;
7290 delete node.children;
7291 }
7292 }
7293 d3_layout_hierarchyVisitAfter(root, function(node) {
7294 var childs, parent;
7295 if (sort && (childs = node.children)) childs.sort(sort);
7296 if (value && (parent = node.parent)) parent.value += node.value;
7297 });
7298 return nodes;
7299 }
7300 hierarchy.sort = function(x) {
7301 if (!arguments.length) return sort;
7302 sort = x;
7303 return hierarchy;
7304 };
7305 hierarchy.children = function(x) {
7306 if (!arguments.length) return children;
7307 children = x;
7308 return hierarchy;
7309 };
7310 hierarchy.value = function(x) {
7311 if (!arguments.length) return value;
7312 value = x;
7313 return hierarchy;
7314 };
7315 hierarchy.revalue = function(root) {
7316 if (value) {
7317 d3_layout_hierarchyVisitBefore(root, function(node) {
7318 if (node.children) node.value = 0;
7319 });
7320 d3_layout_hierarchyVisitAfter(root, function(node) {
7321 var parent;
7322 if (!node.children) node.value = +value.call(hierarchy, node, node.depth) || 0;
7323 if (parent = node.parent) parent.value += node.value;
7324 });
7325 }
7326 return root;
7327 };
7328 return hierarchy;
7329 };
7330 function d3_layout_hierarchyRebind(object, hierarchy) {
7331 d3.rebind(object, hierarchy, "sort", "children", "value");
7332 object.nodes = object;
7333 object.links = d3_layout_hierarchyLinks;
7334 return object;
7335 }
7336 function d3_layout_hierarchyVisitBefore(node, callback) {
7337 var nodes = [ node ];
7338 while ((node = nodes.pop()) != null) {
7339 callback(node);
7340 if ((children = node.children) && (n = children.length)) {
7341 var n, children;
7342 while (--n >= 0) nodes.push(children[n]);
7343 }
7344 }
7345 }
7346 function d3_layout_hierarchyVisitAfter(node, callback) {
7347 var nodes = [ node ], nodes2 = [];
7348 while ((node = nodes.pop()) != null) {
7349 nodes2.push(node);
7350 if ((children = node.children) && (n = children.length)) {
7351 var i = -1, n, children;
7352 while (++i < n) nodes.push(children[i]);
7353 }
7354 }
7355 while ((node = nodes2.pop()) != null) {
7356 callback(node);
7357 }
7358 }
7359 function d3_layout_hierarchyChildren(d) {
7360 return d.children;
7361 }
7362 function d3_layout_hierarchyValue(d) {
7363 return d.value;
7364 }
7365 function d3_layout_hierarchySort(a, b) {
7366 return b.value - a.value;
7367 }
7368 function d3_layout_hierarchyLinks(nodes) {
7369 return d3.merge(nodes.map(function(parent) {
7370 return (parent.children || []).map(function(child) {
7371 return {
7372 source: parent,
7373 target: child
7374 };
7375 });
7376 }));
7377 }
7378 d3.layout.partition = function() {
7379 var hierarchy = d3.layout.hierarchy(), size = [ 1, 1 ];
7380 function position(node, x, dx, dy) {
7381 var children = node.children;
7382 node.x = x;
7383 node.y = node.depth * dy;
7384 node.dx = dx;
7385 node.dy = dy;
7386 if (children && (n = children.length)) {
7387 var i = -1, n, c, d;
7388 dx = node.value ? dx / node.value : 0;
7389 while (++i < n) {
7390 position(c = children[i], x, d = c.value * dx, dy);
7391 x += d;
7392 }
7393 }
7394 }
7395 function depth(node) {
7396 var children = node.children, d = 0;
7397 if (children && (n = children.length)) {
7398 var i = -1, n;
7399 while (++i < n) d = Math.max(d, depth(children[i]));
7400 }
7401 return 1 + d;
7402 }
7403 function partition(d, i) {
7404 var nodes = hierarchy.call(this, d, i);
7405 position(nodes[0], 0, size[0], size[1] / depth(nodes[0]));
7406 return nodes;
7407 }
7408 partition.size = function(x) {
7409 if (!arguments.length) return size;
7410 size = x;
7411 return partition;
7412 };
7413 return d3_layout_hierarchyRebind(partition, hierarchy);
7414 };
7415 d3.layout.pie = function() {
7416 var value = Number, sort = d3_layout_pieSortByValue, startAngle = 0, endAngle = τ, padAngle = 0;
7417 function pie(data) {
7418 var n = data.length, values = data.map(function(d, i) {
7419 return +value.call(pie, d, i);
7420 }), a = +(typeof startAngle === "function" ? startAngle.apply(this, arguments) : startAngle), da = (typeof endAngle === "function" ? endAngle.apply(this, arguments) : endAngle) - a, p = Math.min(Math.abs(da) / n, +(typeof padAngle === "function" ? padAngle.apply(this, arguments) : padAngle)), pa = p * (da < 0 ? -1 : 1), sum = d3.sum(values), k = sum ? (da - n * pa) / sum : 0, index = d3.range(n), arcs = [], v;
7421 if (sort != null) index.sort(sort === d3_layout_pieSortByValue ? function(i, j) {
7422 return values[j] - values[i];
7423 } : function(i, j) {
7424 return sort(data[i], data[j]);
7425 });
7426 index.forEach(function(i) {
7427 arcs[i] = {
7428 data: data[i],
7429 value: v = values[i],
7430 startAngle: a,
7431 endAngle: a += v * k + pa,
7432 padAngle: p
7433 };
7434 });
7435 return arcs;
7436 }
7437 pie.value = function(_) {
7438 if (!arguments.length) return value;
7439 value = _;
7440 return pie;
7441 };
7442 pie.sort = function(_) {
7443 if (!arguments.length) return sort;
7444 sort = _;
7445 return pie;
7446 };
7447 pie.startAngle = function(_) {
7448 if (!arguments.length) return startAngle;
7449 startAngle = _;
7450 return pie;
7451 };
7452 pie.endAngle = function(_) {
7453 if (!arguments.length) return endAngle;
7454 endAngle = _;
7455 return pie;
7456 };
7457 pie.padAngle = function(_) {
7458 if (!arguments.length) return padAngle;
7459 padAngle = _;
7460 return pie;
7461 };
7462 return pie;
7463 };
7464 var d3_layout_pieSortByValue = {};
7465 d3.layout.stack = function() {
7466 var values = d3_identity, order = d3_layout_stackOrderDefault, offset = d3_layout_stackOffsetZero, out = d3_layout_stackOut, x = d3_layout_stackX, y = d3_layout_stackY;
7467 function stack(data, index) {
7468 if (!(n = data.length)) return data;
7469 var series = data.map(function(d, i) {
7470 return values.call(stack, d, i);
7471 });
7472 var points = series.map(function(d) {
7473 return d.map(function(v, i) {
7474 return [ x.call(stack, v, i), y.call(stack, v, i) ];
7475 });
7476 });
7477 var orders = order.call(stack, points, index);
7478 series = d3.permute(series, orders);
7479 points = d3.permute(points, orders);
7480 var offsets = offset.call(stack, points, index);
7481 var m = series[0].length, n, i, j, o;
7482 for (j = 0; j < m; ++j) {
7483 out.call(stack, series[0][j], o = offsets[j], points[0][j][1]);
7484 for (i = 1; i < n; ++i) {
7485 out.call(stack, series[i][j], o += points[i - 1][j][1], points[i][j][1]);
7486 }
7487 }
7488 return data;
7489 }
7490 stack.values = function(x) {
7491 if (!arguments.length) return values;
7492 values = x;
7493 return stack;
7494 };
7495 stack.order = function(x) {
7496 if (!arguments.length) return order;
7497 order = typeof x === "function" ? x : d3_layout_stackOrders.get(x) || d3_layout_stackOrderDefault;
7498 return stack;
7499 };
7500 stack.offset = function(x) {
7501 if (!arguments.length) return offset;
7502 offset = typeof x === "function" ? x : d3_layout_stackOffsets.get(x) || d3_layout_stackOffsetZero;
7503 return stack;
7504 };
7505 stack.x = function(z) {
7506 if (!arguments.length) return x;
7507 x = z;
7508 return stack;
7509 };
7510 stack.y = function(z) {
7511 if (!arguments.length) return y;
7512 y = z;
7513 return stack;
7514 };
7515 stack.out = function(z) {
7516 if (!arguments.length) return out;
7517 out = z;
7518 return stack;
7519 };
7520 return stack;
7521 };
7522 function d3_layout_stackX(d) {
7523 return d.x;
7524 }
7525 function d3_layout_stackY(d) {
7526 return d.y;
7527 }
7528 function d3_layout_stackOut(d, y0, y) {
7529 d.y0 = y0;
7530 d.y = y;
7531 }
7532 var d3_layout_stackOrders = d3.map({
7533 "inside-out": function(data) {
7534 var n = data.length, i, j, max = data.map(d3_layout_stackMaxIndex), sums = data.map(d3_layout_stackReduceSum), index = d3.range(n).sort(function(a, b) {
7535 return max[a] - max[b];
7536 }), top = 0, bottom = 0, tops = [], bottoms = [];
7537 for (i = 0; i < n; ++i) {
7538 j = index[i];
7539 if (top < bottom) {
7540 top += sums[j];
7541 tops.push(j);
7542 } else {
7543 bottom += sums[j];
7544 bottoms.push(j);
7545 }
7546 }
7547 return bottoms.reverse().concat(tops);
7548 },
7549 reverse: function(data) {
7550 return d3.range(data.length).reverse();
7551 },
7552 "default": d3_layout_stackOrderDefault
7553 });
7554 var d3_layout_stackOffsets = d3.map({
7555 silhouette: function(data) {
7556 var n = data.length, m = data[0].length, sums = [], max = 0, i, j, o, y0 = [];
7557 for (j = 0; j < m; ++j) {
7558 for (i = 0, o = 0; i < n; i++) o += data[i][j][1];
7559 if (o > max) max = o;
7560 sums.push(o);
7561 }
7562 for (j = 0; j < m; ++j) {
7563 y0[j] = (max - sums[j]) / 2;
7564 }
7565 return y0;
7566 },
7567 wiggle: function(data) {
7568 var n = data.length, x = data[0], m = x.length, i, j, k, s1, s2, s3, dx, o, o0, y0 = [];
7569 y0[0] = o = o0 = 0;
7570 for (j = 1; j < m; ++j) {
7571 for (i = 0, s1 = 0; i < n; ++i) s1 += data[i][j][1];
7572 for (i = 0, s2 = 0, dx = x[j][0] - x[j - 1][0]; i < n; ++i) {
7573 for (k = 0, s3 = (data[i][j][1] - data[i][j - 1][1]) / (2 * dx); k < i; ++k) {
7574 s3 += (data[k][j][1] - data[k][j - 1][1]) / dx;
7575 }
7576 s2 += s3 * data[i][j][1];
7577 }
7578 y0[j] = o -= s1 ? s2 / s1 * dx : 0;
7579 if (o < o0) o0 = o;
7580 }
7581 for (j = 0; j < m; ++j) y0[j] -= o0;
7582 return y0;
7583 },
7584 expand: function(data) {
7585 var n = data.length, m = data[0].length, k = 1 / n, i, j, o, y0 = [];
7586 for (j = 0; j < m; ++j) {
7587 for (i = 0, o = 0; i < n; i++) o += data[i][j][1];
7588 if (o) for (i = 0; i < n; i++) data[i][j][1] /= o; else for (i = 0; i < n; i++) data[i][j][1] = k;
7589 }
7590 for (j = 0; j < m; ++j) y0[j] = 0;
7591 return y0;
7592 },
7593 zero: d3_layout_stackOffsetZero
7594 });
7595 function d3_layout_stackOrderDefault(data) {
7596 return d3.range(data.length);
7597 }
7598 function d3_layout_stackOffsetZero(data) {
7599 var j = -1, m = data[0].length, y0 = [];
7600 while (++j < m) y0[j] = 0;
7601 return y0;
7602 }
7603 function d3_layout_stackMaxIndex(array) {
7604 var i = 1, j = 0, v = array[0][1], k, n = array.length;
7605 for (;i < n; ++i) {
7606 if ((k = array[i][1]) > v) {
7607 j = i;
7608 v = k;
7609 }
7610 }
7611 return j;
7612 }
7613 function d3_layout_stackReduceSum(d) {
7614 return d.reduce(d3_layout_stackSum, 0);
7615 }
7616 function d3_layout_stackSum(p, d) {
7617 return p + d[1];
7618 }
7619 d3.layout.histogram = function() {
7620 var frequency = true, valuer = Number, ranger = d3_layout_histogramRange, binner = d3_layout_histogramBinSturges;
7621 function histogram(data, i) {
7622 var bins = [], values = data.map(valuer, this), range = ranger.call(this, values, i), thresholds = binner.call(this, range, values, i), bin, i = -1, n = values.length, m = thresholds.length - 1, k = frequency ? 1 : 1 / n, x;
7623 while (++i < m) {
7624 bin = bins[i] = [];
7625 bin.dx = thresholds[i + 1] - (bin.x = thresholds[i]);
7626 bin.y = 0;
7627 }
7628 if (m > 0) {
7629 i = -1;
7630 while (++i < n) {
7631 x = values[i];
7632 if (x >= range[0] && x <= range[1]) {
7633 bin = bins[d3.bisect(thresholds, x, 1, m) - 1];
7634 bin.y += k;
7635 bin.push(data[i]);
7636 }
7637 }
7638 }
7639 return bins;
7640 }
7641 histogram.value = function(x) {
7642 if (!arguments.length) return valuer;
7643 valuer = x;
7644 return histogram;
7645 };
7646 histogram.range = function(x) {
7647 if (!arguments.length) return ranger;
7648 ranger = d3_functor(x);
7649 return histogram;
7650 };
7651 histogram.bins = function(x) {
7652 if (!arguments.length) return binner;
7653 binner = typeof x === "number" ? function(range) {
7654 return d3_layout_histogramBinFixed(range, x);
7655 } : d3_functor(x);
7656 return histogram;
7657 };
7658 histogram.frequency = function(x) {
7659 if (!arguments.length) return frequency;
7660 frequency = !!x;
7661 return histogram;
7662 };
7663 return histogram;
7664 };
7665 function d3_layout_histogramBinSturges(range, values) {
7666 return d3_layout_histogramBinFixed(range, Math.ceil(Math.log(values.length) / Math.LN2 + 1));
7667 }
7668 function d3_layout_histogramBinFixed(range, n) {
7669 var x = -1, b = +range[0], m = (range[1] - b) / n, f = [];
7670 while (++x <= n) f[x] = m * x + b;
7671 return f;
7672 }
7673 function d3_layout_histogramRange(values) {
7674 return [ d3.min(values), d3.max(values) ];
7675 }
7676 d3.layout.pack = function() {
7677 var hierarchy = d3.layout.hierarchy().sort(d3_layout_packSort), padding = 0, size = [ 1, 1 ], radius;
7678 function pack(d, i) {
7679 var nodes = hierarchy.call(this, d, i), root = nodes[0], w = size[0], h = size[1], r = radius == null ? Math.sqrt : typeof radius === "function" ? radius : function() {
7680 return radius;
7681 };
7682 root.x = root.y = 0;
7683 d3_layout_hierarchyVisitAfter(root, function(d) {
7684 d.r = +r(d.value);
7685 });
7686 d3_layout_hierarchyVisitAfter(root, d3_layout_packSiblings);
7687 if (padding) {
7688 var dr = padding * (radius ? 1 : Math.max(2 * root.r / w, 2 * root.r / h)) / 2;
7689 d3_layout_hierarchyVisitAfter(root, function(d) {
7690 d.r += dr;
7691 });
7692 d3_layout_hierarchyVisitAfter(root, d3_layout_packSiblings);
7693 d3_layout_hierarchyVisitAfter(root, function(d) {
7694 d.r -= dr;
7695 });
7696 }
7697 d3_layout_packTransform(root, w / 2, h / 2, radius ? 1 : 1 / Math.max(2 * root.r / w, 2 * root.r / h));
7698 return nodes;
7699 }
7700 pack.size = function(_) {
7701 if (!arguments.length) return size;
7702 size = _;
7703 return pack;
7704 };
7705 pack.radius = function(_) {
7706 if (!arguments.length) return radius;
7707 radius = _ == null || typeof _ === "function" ? _ : +_;
7708 return pack;
7709 };
7710 pack.padding = function(_) {
7711 if (!arguments.length) return padding;
7712 padding = +_;
7713 return pack;
7714 };
7715 return d3_layout_hierarchyRebind(pack, hierarchy);
7716 };
7717 function d3_layout_packSort(a, b) {
7718 return a.value - b.value;
7719 }
7720 function d3_layout_packInsert(a, b) {
7721 var c = a._pack_next;
7722 a._pack_next = b;
7723 b._pack_prev = a;
7724 b._pack_next = c;
7725 c._pack_prev = b;
7726 }
7727 function d3_layout_packSplice(a, b) {
7728 a._pack_next = b;
7729 b._pack_prev = a;
7730 }
7731 function d3_layout_packIntersects(a, b) {
7732 var dx = b.x - a.x, dy = b.y - a.y, dr = a.r + b.r;
7733 return .999 * dr * dr > dx * dx + dy * dy;
7734 }
7735 function d3_layout_packSiblings(node) {
7736 if (!(nodes = node.children) || !(n = nodes.length)) return;
7737 var nodes, xMin = Infinity, xMax = -Infinity, yMin = Infinity, yMax = -Infinity, a, b, c, i, j, k, n;
7738 function bound(node) {
7739 xMin = Math.min(node.x - node.r, xMin);
7740 xMax = Math.max(node.x + node.r, xMax);
7741 yMin = Math.min(node.y - node.r, yMin);
7742 yMax = Math.max(node.y + node.r, yMax);
7743 }
7744 nodes.forEach(d3_layout_packLink);
7745 a = nodes[0];
7746 a.x = -a.r;
7747 a.y = 0;
7748 bound(a);
7749 if (n > 1) {
7750 b = nodes[1];
7751 b.x = b.r;
7752 b.y = 0;
7753 bound(b);
7754 if (n > 2) {
7755 c = nodes[2];
7756 d3_layout_packPlace(a, b, c);
7757 bound(c);
7758 d3_layout_packInsert(a, c);
7759 a._pack_prev = c;
7760 d3_layout_packInsert(c, b);
7761 b = a._pack_next;
7762 for (i = 3; i < n; i++) {
7763 d3_layout_packPlace(a, b, c = nodes[i]);
7764 var isect = 0, s1 = 1, s2 = 1;
7765 for (j = b._pack_next; j !== b; j = j._pack_next, s1++) {
7766 if (d3_layout_packIntersects(j, c)) {
7767 isect = 1;
7768 break;
7769 }
7770 }
7771 if (isect == 1) {
7772 for (k = a._pack_prev; k !== j._pack_prev; k = k._pack_prev, s2++) {
7773 if (d3_layout_packIntersects(k, c)) {
7774 break;
7775 }
7776 }
7777 }
7778 if (isect) {
7779 if (s1 < s2 || s1 == s2 && b.r < a.r) d3_layout_packSplice(a, b = j); else d3_layout_packSplice(a = k, b);
7780 i--;
7781 } else {
7782 d3_layout_packInsert(a, c);
7783 b = c;
7784 bound(c);
7785 }
7786 }
7787 }
7788 }
7789 var cx = (xMin + xMax) / 2, cy = (yMin + yMax) / 2, cr = 0;
7790 for (i = 0; i < n; i++) {
7791 c = nodes[i];
7792 c.x -= cx;
7793 c.y -= cy;
7794 cr = Math.max(cr, c.r + Math.sqrt(c.x * c.x + c.y * c.y));
7795 }
7796 node.r = cr;
7797 nodes.forEach(d3_layout_packUnlink);
7798 }
7799 function d3_layout_packLink(node) {
7800 node._pack_next = node._pack_prev = node;
7801 }
7802 function d3_layout_packUnlink(node) {
7803 delete node._pack_next;
7804 delete node._pack_prev;
7805 }
7806 function d3_layout_packTransform(node, x, y, k) {
7807 var children = node.children;
7808 node.x = x += k * node.x;
7809 node.y = y += k * node.y;
7810 node.r *= k;
7811 if (children) {
7812 var i = -1, n = children.length;
7813 while (++i < n) d3_layout_packTransform(children[i], x, y, k);
7814 }
7815 }
7816 function d3_layout_packPlace(a, b, c) {
7817 var db = a.r + c.r, dx = b.x - a.x, dy = b.y - a.y;
7818 if (db && (dx || dy)) {
7819 var da = b.r + c.r, dc = dx * dx + dy * dy;
7820 da *= da;
7821 db *= db;
7822 var x = .5 + (db - da) / (2 * dc), y = Math.sqrt(Math.max(0, 2 * da * (db + dc) - (db -= dc) * db - da * da)) / (2 * dc);
7823 c.x = a.x + x * dx + y * dy;
7824 c.y = a.y + x * dy - y * dx;
7825 } else {
7826 c.x = a.x + db;
7827 c.y = a.y;
7828 }
7829 }
7830 d3.layout.tree = function() {
7831 var hierarchy = d3.layout.hierarchy().sort(null).value(null), separation = d3_layout_treeSeparation, size = [ 1, 1 ], nodeSize = null;
7832 function tree(d, i) {
7833 var nodes = hierarchy.call(this, d, i), root0 = nodes[0], root1 = wrapTree(root0);
7834 d3_layout_hierarchyVisitAfter(root1, firstWalk), root1.parent.m = -root1.z;
7835 d3_layout_hierarchyVisitBefore(root1, secondWalk);
7836 if (nodeSize) d3_layout_hierarchyVisitBefore(root0, sizeNode); else {
7837 var left = root0, right = root0, bottom = root0;
7838 d3_layout_hierarchyVisitBefore(root0, function(node) {
7839 if (node.x < left.x) left = node;
7840 if (node.x > right.x) right = node;
7841 if (node.depth > bottom.depth) bottom = node;
7842 });
7843 var tx = separation(left, right) / 2 - left.x, kx = size[0] / (right.x + separation(right, left) / 2 + tx), ky = size[1] / (bottom.depth || 1);
7844 d3_layout_hierarchyVisitBefore(root0, function(node) {
7845 node.x = (node.x + tx) * kx;
7846 node.y = node.depth * ky;
7847 });
7848 }
7849 return nodes;
7850 }
7851 function wrapTree(root0) {
7852 var root1 = {
7853 A: null,
7854 children: [ root0 ]
7855 }, queue = [ root1 ], node1;
7856 while ((node1 = queue.pop()) != null) {
7857 for (var children = node1.children, child, i = 0, n = children.length; i < n; ++i) {
7858 queue.push((children[i] = child = {
7859 _: children[i],
7860 parent: node1,
7861 children: (child = children[i].children) && child.slice() || [],
7862 A: null,
7863 a: null,
7864 z: 0,
7865 m: 0,
7866 c: 0,
7867 s: 0,
7868 t: null,
7869 i: i
7870 }).a = child);
7871 }
7872 }
7873 return root1.children[0];
7874 }
7875 function firstWalk(v) {
7876 var children = v.children, siblings = v.parent.children, w = v.i ? siblings[v.i - 1] : null;
7877 if (children.length) {
7878 d3_layout_treeShift(v);
7879 var midpoint = (children[0].z + children[children.length - 1].z) / 2;
7880 if (w) {
7881 v.z = w.z + separation(v._, w._);
7882 v.m = v.z - midpoint;
7883 } else {
7884 v.z = midpoint;
7885 }
7886 } else if (w) {
7887 v.z = w.z + separation(v._, w._);
7888 }
7889 v.parent.A = apportion(v, w, v.parent.A || siblings[0]);
7890 }
7891 function secondWalk(v) {
7892 v._.x = v.z + v.parent.m;
7893 v.m += v.parent.m;
7894 }
7895 function apportion(v, w, ancestor) {
7896 if (w) {
7897 var vip = v, vop = v, vim = w, vom = vip.parent.children[0], sip = vip.m, sop = vop.m, sim = vim.m, som = vom.m, shift;
7898 while (vim = d3_layout_treeRight(vim), vip = d3_layout_treeLeft(vip), vim && vip) {
7899 vom = d3_layout_treeLeft(vom);
7900 vop = d3_layout_treeRight(vop);
7901 vop.a = v;
7902 shift = vim.z + sim - vip.z - sip + separation(vim._, vip._);
7903 if (shift > 0) {
7904 d3_layout_treeMove(d3_layout_treeAncestor(vim, v, ancestor), v, shift);
7905 sip += shift;
7906 sop += shift;
7907 }
7908 sim += vim.m;
7909 sip += vip.m;
7910 som += vom.m;
7911 sop += vop.m;
7912 }
7913 if (vim && !d3_layout_treeRight(vop)) {
7914 vop.t = vim;
7915 vop.m += sim - sop;
7916 }
7917 if (vip && !d3_layout_treeLeft(vom)) {
7918 vom.t = vip;
7919 vom.m += sip - som;
7920 ancestor = v;
7921 }
7922 }
7923 return ancestor;
7924 }
7925 function sizeNode(node) {
7926 node.x *= size[0];
7927 node.y = node.depth * size[1];
7928 }
7929 tree.separation = function(x) {
7930 if (!arguments.length) return separation;
7931 separation = x;
7932 return tree;
7933 };
7934 tree.size = function(x) {
7935 if (!arguments.length) return nodeSize ? null : size;
7936 nodeSize = (size = x) == null ? sizeNode : null;
7937 return tree;
7938 };
7939 tree.nodeSize = function(x) {
7940 if (!arguments.length) return nodeSize ? size : null;
7941 nodeSize = (size = x) == null ? null : sizeNode;
7942 return tree;
7943 };
7944 return d3_layout_hierarchyRebind(tree, hierarchy);
7945 };
7946 function d3_layout_treeSeparation(a, b) {
7947 return a.parent == b.parent ? 1 : 2;
7948 }
7949 function d3_layout_treeLeft(v) {
7950 var children = v.children;
7951 return children.length ? children[0] : v.t;
7952 }
7953 function d3_layout_treeRight(v) {
7954 var children = v.children, n;
7955 return (n = children.length) ? children[n - 1] : v.t;
7956 }
7957 function d3_layout_treeMove(wm, wp, shift) {
7958 var change = shift / (wp.i - wm.i);
7959 wp.c -= change;
7960 wp.s += shift;
7961 wm.c += change;
7962 wp.z += shift;
7963 wp.m += shift;
7964 }
7965 function d3_layout_treeShift(v) {
7966 var shift = 0, change = 0, children = v.children, i = children.length, w;
7967 while (--i >= 0) {
7968 w = children[i];
7969 w.z += shift;
7970 w.m += shift;
7971 shift += w.s + (change += w.c);
7972 }
7973 }
7974 function d3_layout_treeAncestor(vim, v, ancestor) {
7975 return vim.a.parent === v.parent ? vim.a : ancestor;
7976 }
7977 d3.layout.cluster = function() {
7978 var hierarchy = d3.layout.hierarchy().sort(null).value(null), separation = d3_layout_treeSeparation, size = [ 1, 1 ], nodeSize = false;
7979 function cluster(d, i) {
7980 var nodes = hierarchy.call(this, d, i), root = nodes[0], previousNode, x = 0;
7981 d3_layout_hierarchyVisitAfter(root, function(node) {
7982 var children = node.children;
7983 if (children && children.length) {
7984 node.x = d3_layout_clusterX(children);
7985 node.y = d3_layout_clusterY(children);
7986 } else {
7987 node.x = previousNode ? x += separation(node, previousNode) : 0;
7988 node.y = 0;
7989 previousNode = node;
7990 }
7991 });
7992 var left = d3_layout_clusterLeft(root), right = d3_layout_clusterRight(root), x0 = left.x - separation(left, right) / 2, x1 = right.x + separation(right, left) / 2;
7993 d3_layout_hierarchyVisitAfter(root, nodeSize ? function(node) {
7994 node.x = (node.x - root.x) * size[0];
7995 node.y = (root.y - node.y) * size[1];
7996 } : function(node) {
7997 node.x = (node.x - x0) / (x1 - x0) * size[0];
7998 node.y = (1 - (root.y ? node.y / root.y : 1)) * size[1];
7999 });
8000 return nodes;
8001 }
8002 cluster.separation = function(x) {
8003 if (!arguments.length) return separation;
8004 separation = x;
8005 return cluster;
8006 };
8007 cluster.size = function(x) {
8008 if (!arguments.length) return nodeSize ? null : size;
8009 nodeSize = (size = x) == null;
8010 return cluster;
8011 };
8012 cluster.nodeSize = function(x) {
8013 if (!arguments.length) return nodeSize ? size : null;
8014 nodeSize = (size = x) != null;
8015 return cluster;
8016 };
8017 return d3_layout_hierarchyRebind(cluster, hierarchy);
8018 };
8019 function d3_layout_clusterY(children) {
8020 return 1 + d3.max(children, function(child) {
8021 return child.y;
8022 });
8023 }
8024 function d3_layout_clusterX(children) {
8025 return children.reduce(function(x, child) {
8026 return x + child.x;
8027 }, 0) / children.length;
8028 }
8029 function d3_layout_clusterLeft(node) {
8030 var children = node.children;
8031 return children && children.length ? d3_layout_clusterLeft(children[0]) : node;
8032 }
8033 function d3_layout_clusterRight(node) {
8034 var children = node.children, n;
8035 return children && (n = children.length) ? d3_layout_clusterRight(children[n - 1]) : node;
8036 }
8037 d3.layout.treemap = function() {
8038 var hierarchy = d3.layout.hierarchy(), round = Math.round, size = [ 1, 1 ], padding = null, pad = d3_layout_treemapPadNull, sticky = false, stickies, mode = "squarify", ratio = .5 * (1 + Math.sqrt(5));
8039 function scale(children, k) {
8040 var i = -1, n = children.length, child, area;
8041 while (++i < n) {
8042 area = (child = children[i]).value * (k < 0 ? 0 : k);
8043 child.area = isNaN(area) || area <= 0 ? 0 : area;
8044 }
8045 }
8046 function squarify(node) {
8047 var children = node.children;
8048 if (children && children.length) {
8049 var rect = pad(node), row = [], remaining = children.slice(), child, best = Infinity, score, u = mode === "slice" ? rect.dx : mode === "dice" ? rect.dy : mode === "slice-dice" ? node.depth & 1 ? rect.dy : rect.dx : Math.min(rect.dx, rect.dy), n;
8050 scale(remaining, rect.dx * rect.dy / node.value);
8051 row.area = 0;
8052 while ((n = remaining.length) > 0) {
8053 row.push(child = remaining[n - 1]);
8054 row.area += child.area;
8055 if (mode !== "squarify" || (score = worst(row, u)) <= best) {
8056 remaining.pop();
8057 best = score;
8058 } else {
8059 row.area -= row.pop().area;
8060 position(row, u, rect, false);
8061 u = Math.min(rect.dx, rect.dy);
8062 row.length = row.area = 0;
8063 best = Infinity;
8064 }
8065 }
8066 if (row.length) {
8067 position(row, u, rect, true);
8068 row.length = row.area = 0;
8069 }
8070 children.forEach(squarify);
8071 }
8072 }
8073 function stickify(node) {
8074 var children = node.children;
8075 if (children && children.length) {
8076 var rect = pad(node), remaining = children.slice(), child, row = [];
8077 scale(remaining, rect.dx * rect.dy / node.value);
8078 row.area = 0;
8079 while (child = remaining.pop()) {
8080 row.push(child);
8081 row.area += child.area;
8082 if (child.z != null) {
8083 position(row, child.z ? rect.dx : rect.dy, rect, !remaining.length);
8084 row.length = row.area = 0;
8085 }
8086 }
8087 children.forEach(stickify);
8088 }
8089 }
8090 function worst(row, u) {
8091 var s = row.area, r, rmax = 0, rmin = Infinity, i = -1, n = row.length;
8092 while (++i < n) {
8093 if (!(r = row[i].area)) continue;
8094 if (r < rmin) rmin = r;
8095 if (r > rmax) rmax = r;
8096 }
8097 s *= s;
8098 u *= u;
8099 return s ? Math.max(u * rmax * ratio / s, s / (u * rmin * ratio)) : Infinity;
8100 }
8101 function position(row, u, rect, flush) {
8102 var i = -1, n = row.length, x = rect.x, y = rect.y, v = u ? round(row.area / u) : 0, o;
8103 if (u == rect.dx) {
8104 if (flush || v > rect.dy) v = rect.dy;
8105 while (++i < n) {
8106 o = row[i];
8107 o.x = x;
8108 o.y = y;
8109 o.dy = v;
8110 x += o.dx = Math.min(rect.x + rect.dx - x, v ? round(o.area / v) : 0);
8111 }
8112 o.z = true;
8113 o.dx += rect.x + rect.dx - x;
8114 rect.y += v;
8115 rect.dy -= v;
8116 } else {
8117 if (flush || v > rect.dx) v = rect.dx;
8118 while (++i < n) {
8119 o = row[i];
8120 o.x = x;
8121 o.y = y;
8122 o.dx = v;
8123 y += o.dy = Math.min(rect.y + rect.dy - y, v ? round(o.area / v) : 0);
8124 }
8125 o.z = false;
8126 o.dy += rect.y + rect.dy - y;
8127 rect.x += v;
8128 rect.dx -= v;
8129 }
8130 }
8131 function treemap(d) {
8132 var nodes = stickies || hierarchy(d), root = nodes[0];
8133 root.x = root.y = 0;
8134 if (root.value) root.dx = size[0], root.dy = size[1]; else root.dx = root.dy = 0;
8135 if (stickies) hierarchy.revalue(root);
8136 scale([ root ], root.dx * root.dy / root.value);
8137 (stickies ? stickify : squarify)(root);
8138 if (sticky) stickies = nodes;
8139 return nodes;
8140 }
8141 treemap.size = function(x) {
8142 if (!arguments.length) return size;
8143 size = x;
8144 return treemap;
8145 };
8146 treemap.padding = function(x) {
8147 if (!arguments.length) return padding;
8148 function padFunction(node) {
8149 var p = x.call(treemap, node, node.depth);
8150 return p == null ? d3_layout_treemapPadNull(node) : d3_layout_treemapPad(node, typeof p === "number" ? [ p, p, p, p ] : p);
8151 }
8152 function padConstant(node) {
8153 return d3_layout_treemapPad(node, x);
8154 }
8155 var type;
8156 pad = (padding = x) == null ? d3_layout_treemapPadNull : (type = typeof x) === "function" ? padFunction : type === "number" ? (x = [ x, x, x, x ],
8157 padConstant) : padConstant;
8158 return treemap;
8159 };
8160 treemap.round = function(x) {
8161 if (!arguments.length) return round != Number;
8162 round = x ? Math.round : Number;
8163 return treemap;
8164 };
8165 treemap.sticky = function(x) {
8166 if (!arguments.length) return sticky;
8167 sticky = x;
8168 stickies = null;
8169 return treemap;
8170 };
8171 treemap.ratio = function(x) {
8172 if (!arguments.length) return ratio;
8173 ratio = x;
8174 return treemap;
8175 };
8176 treemap.mode = function(x) {
8177 if (!arguments.length) return mode;
8178 mode = x + "";
8179 return treemap;
8180 };
8181 return d3_layout_hierarchyRebind(treemap, hierarchy);
8182 };
8183 function d3_layout_treemapPadNull(node) {
8184 return {
8185 x: node.x,
8186 y: node.y,
8187 dx: node.dx,
8188 dy: node.dy
8189 };
8190 }
8191 function d3_layout_treemapPad(node, padding) {
8192 var x = node.x + padding[3], y = node.y + padding[0], dx = node.dx - padding[1] - padding[3], dy = node.dy - padding[0] - padding[2];
8193 if (dx < 0) {
8194 x += dx / 2;
8195 dx = 0;
8196 }
8197 if (dy < 0) {
8198 y += dy / 2;
8199 dy = 0;
8200 }
8201 return {
8202 x: x,
8203 y: y,
8204 dx: dx,
8205 dy: dy
8206 };
8207 }
8208 d3.random = {
8209 normal: function(µ, σ) {
8210 var n = arguments.length;
8211 if (n < 2) σ = 1;
8212 if (n < 1) µ = 0;
8213 return function() {
8214 var x, y, r;
8215 do {
8216 x = Math.random() * 2 - 1;
8217 y = Math.random() * 2 - 1;
8218 r = x * x + y * y;
8219 } while (!r || r > 1);
8220 return µ + σ * x * Math.sqrt(-2 * Math.log(r) / r);
8221 };
8222 },
8223 logNormal: function() {
8224 var random = d3.random.normal.apply(d3, arguments);
8225 return function() {
8226 return Math.exp(random());
8227 };
8228 },
8229 bates: function(m) {
8230 var random = d3.random.irwinHall(m);
8231 return function() {
8232 return random() / m;
8233 };
8234 },
8235 irwinHall: function(m) {
8236 return function() {
8237 for (var s = 0, j = 0; j < m; j++) s += Math.random();
8238 return s;
8239 };
8240 }
8241 };
8242 d3.scale = {};
8243 function d3_scaleExtent(domain) {
8244 var start = domain[0], stop = domain[domain.length - 1];
8245 return start < stop ? [ start, stop ] : [ stop, start ];
8246 }
8247 function d3_scaleRange(scale) {
8248 return scale.rangeExtent ? scale.rangeExtent() : d3_scaleExtent(scale.range());
8249 }
8250 function d3_scale_bilinear(domain, range, uninterpolate, interpolate) {
8251 var u = uninterpolate(domain[0], domain[1]), i = interpolate(range[0], range[1]);
8252 return function(x) {
8253 return i(u(x));
8254 };
8255 }
8256 function d3_scale_nice(domain, nice) {
8257 var i0 = 0, i1 = domain.length - 1, x0 = domain[i0], x1 = domain[i1], dx;
8258 if (x1 < x0) {
8259 dx = i0, i0 = i1, i1 = dx;
8260 dx = x0, x0 = x1, x1 = dx;
8261 }
8262 domain[i0] = nice.floor(x0);
8263 domain[i1] = nice.ceil(x1);
8264 return domain;
8265 }
8266 function d3_scale_niceStep(step) {
8267 return step ? {
8268 floor: function(x) {
8269 return Math.floor(x / step) * step;
8270 },
8271 ceil: function(x) {
8272 return Math.ceil(x / step) * step;
8273 }
8274 } : d3_scale_niceIdentity;
8275 }
8276 var d3_scale_niceIdentity = {
8277 floor: d3_identity,
8278 ceil: d3_identity
8279 };
8280 function d3_scale_polylinear(domain, range, uninterpolate, interpolate) {
8281 var u = [], i = [], j = 0, k = Math.min(domain.length, range.length) - 1;
8282 if (domain[k] < domain[0]) {
8283 domain = domain.slice().reverse();
8284 range = range.slice().reverse();
8285 }
8286 while (++j <= k) {
8287 u.push(uninterpolate(domain[j - 1], domain[j]));
8288 i.push(interpolate(range[j - 1], range[j]));
8289 }
8290 return function(x) {
8291 var j = d3.bisect(domain, x, 1, k) - 1;
8292 return i[j](u[j](x));
8293 };
8294 }
8295 d3.scale.linear = function() {
8296 return d3_scale_linear([ 0, 1 ], [ 0, 1 ], d3_interpolate, false);
8297 };
8298 function d3_scale_linear(domain, range, interpolate, clamp) {
8299 var output, input;
8300 function rescale() {
8301 var linear = Math.min(domain.length, range.length) > 2 ? d3_scale_polylinear : d3_scale_bilinear, uninterpolate = clamp ? d3_uninterpolateClamp : d3_uninterpolateNumber;
8302 output = linear(domain, range, uninterpolate, interpolate);
8303 input = linear(range, domain, uninterpolate, d3_interpolate);
8304 return scale;
8305 }
8306 function scale(x) {
8307 return output(x);
8308 }
8309 scale.invert = function(y) {
8310 return input(y);
8311 };
8312 scale.domain = function(x) {
8313 if (!arguments.length) return domain;
8314 domain = x.map(Number);
8315 return rescale();
8316 };
8317 scale.range = function(x) {
8318 if (!arguments.length) return range;
8319 range = x;
8320 return rescale();
8321 };
8322 scale.rangeRound = function(x) {
8323 return scale.range(x).interpolate(d3_interpolateRound);
8324 };
8325 scale.clamp = function(x) {
8326 if (!arguments.length) return clamp;
8327 clamp = x;
8328 return rescale();
8329 };
8330 scale.interpolate = function(x) {
8331 if (!arguments.length) return interpolate;
8332 interpolate = x;
8333 return rescale();
8334 };
8335 scale.ticks = function(m) {
8336 return d3_scale_linearTicks(domain, m);
8337 };
8338 scale.tickFormat = function(m, format) {
8339 return d3_scale_linearTickFormat(domain, m, format);
8340 };
8341 scale.nice = function(m) {
8342 d3_scale_linearNice(domain, m);
8343 return rescale();
8344 };
8345 scale.copy = function() {
8346 return d3_scale_linear(domain, range, interpolate, clamp);
8347 };
8348 return rescale();
8349 }
8350 function d3_scale_linearRebind(scale, linear) {
8351 return d3.rebind(scale, linear, "range", "rangeRound", "interpolate", "clamp");
8352 }
8353 function d3_scale_linearNice(domain, m) {
8354 d3_scale_nice(domain, d3_scale_niceStep(d3_scale_linearTickRange(domain, m)[2]));
8355 d3_scale_nice(domain, d3_scale_niceStep(d3_scale_linearTickRange(domain, m)[2]));
8356 return domain;
8357 }
8358 function d3_scale_linearTickRange(domain, m) {
8359 if (m == null) m = 10;
8360 var extent = d3_scaleExtent(domain), span = extent[1] - extent[0], step = Math.pow(10, Math.floor(Math.log(span / m) / Math.LN10)), err = m / span * step;
8361 if (err <= .15) step *= 10; else if (err <= .35) step *= 5; else if (err <= .75) step *= 2;
8362 extent[0] = Math.ceil(extent[0] / step) * step;
8363 extent[1] = Math.floor(extent[1] / step) * step + step * .5;
8364 extent[2] = step;
8365 return extent;
8366 }
8367 function d3_scale_linearTicks(domain, m) {
8368 return d3.range.apply(d3, d3_scale_linearTickRange(domain, m));
8369 }
8370 function d3_scale_linearTickFormat(domain, m, format) {
8371 var range = d3_scale_linearTickRange(domain, m);
8372 if (format) {
8373 var match = d3_format_re.exec(format);
8374 match.shift();
8375 if (match[8] === "s") {
8376 var prefix = d3.formatPrefix(Math.max(abs(range[0]), abs(range[1])));
8377 if (!match[7]) match[7] = "." + d3_scale_linearPrecision(prefix.scale(range[2]));
8378 match[8] = "f";
8379 format = d3.format(match.join(""));
8380 return function(d) {
8381 return format(prefix.scale(d)) + prefix.symbol;
8382 };
8383 }
8384 if (!match[7]) match[7] = "." + d3_scale_linearFormatPrecision(match[8], range);
8385 format = match.join("");
8386 } else {
8387 format = ",." + d3_scale_linearPrecision(range[2]) + "f";
8388 }
8389 return d3.format(format);
8390 }
8391 var d3_scale_linearFormatSignificant = {
8392 s: 1,
8393 g: 1,
8394 p: 1,
8395 r: 1,
8396 e: 1
8397 };
8398 function d3_scale_linearPrecision(value) {
8399 return -Math.floor(Math.log(value) / Math.LN10 + .01);
8400 }
8401 function d3_scale_linearFormatPrecision(type, range) {
8402 var p = d3_scale_linearPrecision(range[2]);
8403 return type in d3_scale_linearFormatSignificant ? Math.abs(p - d3_scale_linearPrecision(Math.max(abs(range[0]), abs(range[1])))) + +(type !== "e") : p - (type === "%") * 2;
8404 }
8405 d3.scale.log = function() {
8406 return d3_scale_log(d3.scale.linear().domain([ 0, 1 ]), 10, true, [ 1, 10 ]);
8407 };
8408 function d3_scale_log(linear, base, positive, domain) {
8409 function log(x) {
8410 return (positive ? Math.log(x < 0 ? 0 : x) : -Math.log(x > 0 ? 0 : -x)) / Math.log(base);
8411 }
8412 function pow(x) {
8413 return positive ? Math.pow(base, x) : -Math.pow(base, -x);
8414 }
8415 function scale(x) {
8416 return linear(log(x));
8417 }
8418 scale.invert = function(x) {
8419 return pow(linear.invert(x));
8420 };
8421 scale.domain = function(x) {
8422 if (!arguments.length) return domain;
8423 positive = x[0] >= 0;
8424 linear.domain((domain = x.map(Number)).map(log));
8425 return scale;
8426 };
8427 scale.base = function(_) {
8428 if (!arguments.length) return base;
8429 base = +_;
8430 linear.domain(domain.map(log));
8431 return scale;
8432 };
8433 scale.nice = function() {
8434 var niced = d3_scale_nice(domain.map(log), positive ? Math : d3_scale_logNiceNegative);
8435 linear.domain(niced);
8436 domain = niced.map(pow);
8437 return scale;
8438 };
8439 scale.ticks = function() {
8440 var extent = d3_scaleExtent(domain), ticks = [], u = extent[0], v = extent[1], i = Math.floor(log(u)), j = Math.ceil(log(v)), n = base % 1 ? 2 : base;
8441 if (isFinite(j - i)) {
8442 if (positive) {
8443 for (;i < j; i++) for (var k = 1; k < n; k++) ticks.push(pow(i) * k);
8444 ticks.push(pow(i));
8445 } else {
8446 ticks.push(pow(i));
8447 for (;i++ < j; ) for (var k = n - 1; k > 0; k--) ticks.push(pow(i) * k);
8448 }
8449 for (i = 0; ticks[i] < u; i++) {}
8450 for (j = ticks.length; ticks[j - 1] > v; j--) {}
8451 ticks = ticks.slice(i, j);
8452 }
8453 return ticks;
8454 };
8455 scale.tickFormat = function(n, format) {
8456 if (!arguments.length) return d3_scale_logFormat;
8457 if (arguments.length < 2) format = d3_scale_logFormat; else if (typeof format !== "function") format = d3.format(format);
8458 var k = Math.max(1, base * n / scale.ticks().length);
8459 return function(d) {
8460 var i = d / pow(Math.round(log(d)));
8461 if (i * base < base - .5) i *= base;
8462 return i <= k ? format(d) : "";
8463 };
8464 };
8465 scale.copy = function() {
8466 return d3_scale_log(linear.copy(), base, positive, domain);
8467 };
8468 return d3_scale_linearRebind(scale, linear);
8469 }
8470 var d3_scale_logFormat = d3.format(".0e"), d3_scale_logNiceNegative = {
8471 floor: function(x) {
8472 return -Math.ceil(-x);
8473 },
8474 ceil: function(x) {
8475 return -Math.floor(-x);
8476 }
8477 };
8478 d3.scale.pow = function() {
8479 return d3_scale_pow(d3.scale.linear(), 1, [ 0, 1 ]);
8480 };
8481 function d3_scale_pow(linear, exponent, domain) {
8482 var powp = d3_scale_powPow(exponent), powb = d3_scale_powPow(1 / exponent);
8483 function scale(x) {
8484 return linear(powp(x));
8485 }
8486 scale.invert = function(x) {
8487 return powb(linear.invert(x));
8488 };
8489 scale.domain = function(x) {
8490 if (!arguments.length) return domain;
8491 linear.domain((domain = x.map(Number)).map(powp));
8492 return scale;
8493 };
8494 scale.ticks = function(m) {
8495 return d3_scale_linearTicks(domain, m);
8496 };
8497 scale.tickFormat = function(m, format) {
8498 return d3_scale_linearTickFormat(domain, m, format);
8499 };
8500 scale.nice = function(m) {
8501 return scale.domain(d3_scale_linearNice(domain, m));
8502 };
8503 scale.exponent = function(x) {
8504 if (!arguments.length) return exponent;
8505 powp = d3_scale_powPow(exponent = x);
8506 powb = d3_scale_powPow(1 / exponent);
8507 linear.domain(domain.map(powp));
8508 return scale;
8509 };
8510 scale.copy = function() {
8511 return d3_scale_pow(linear.copy(), exponent, domain);
8512 };
8513 return d3_scale_linearRebind(scale, linear);
8514 }
8515 function d3_scale_powPow(e) {
8516 return function(x) {
8517 return x < 0 ? -Math.pow(-x, e) : Math.pow(x, e);
8518 };
8519 }
8520 d3.scale.sqrt = function() {
8521 return d3.scale.pow().exponent(.5);
8522 };
8523 d3.scale.ordinal = function() {
8524 return d3_scale_ordinal([], {
8525 t: "range",
8526 a: [ [] ]
8527 });
8528 };
8529 function d3_scale_ordinal(domain, ranger) {
8530 var index, range, rangeBand;
8531 function scale(x) {
8532 return range[((index.get(x) || (ranger.t === "range" ? index.set(x, domain.push(x)) : NaN)) - 1) % range.length];
8533 }
8534 function steps(start, step) {
8535 return d3.range(domain.length).map(function(i) {
8536 return start + step * i;
8537 });
8538 }
8539 scale.domain = function(x) {
8540 if (!arguments.length) return domain;
8541 domain = [];
8542 index = new d3_Map();
8543 var i = -1, n = x.length, xi;
8544 while (++i < n) if (!index.has(xi = x[i])) index.set(xi, domain.push(xi));
8545 return scale[ranger.t].apply(scale, ranger.a);
8546 };
8547 scale.range = function(x) {
8548 if (!arguments.length) return range;
8549 range = x;
8550 rangeBand = 0;
8551 ranger = {
8552 t: "range",
8553 a: arguments
8554 };
8555 return scale;
8556 };
8557 scale.rangePoints = function(x, padding) {
8558 if (arguments.length < 2) padding = 0;
8559 var start = x[0], stop = x[1], step = domain.length < 2 ? (start = (start + stop) / 2,
8560 0) : (stop - start) / (domain.length - 1 + padding);
8561 range = steps(start + step * padding / 2, step);
8562 rangeBand = 0;
8563 ranger = {
8564 t: "rangePoints",
8565 a: arguments
8566 };
8567 return scale;
8568 };
8569 scale.rangeRoundPoints = function(x, padding) {
8570 if (arguments.length < 2) padding = 0;
8571 var start = x[0], stop = x[1], step = domain.length < 2 ? (start = stop = Math.round((start + stop) / 2),
8572 0) : (stop - start) / (domain.length - 1 + padding) | 0;
8573 range = steps(start + Math.round(step * padding / 2 + (stop - start - (domain.length - 1 + padding) * step) / 2), step);
8574 rangeBand = 0;
8575 ranger = {
8576 t: "rangeRoundPoints",
8577 a: arguments
8578 };
8579 return scale;
8580 };
8581 scale.rangeBands = function(x, padding, outerPadding) {
8582 if (arguments.length < 2) padding = 0;
8583 if (arguments.length < 3) outerPadding = padding;
8584 var reverse = x[1] < x[0], start = x[reverse - 0], stop = x[1 - reverse], step = (stop - start) / (domain.length - padding + 2 * outerPadding);
8585 range = steps(start + step * outerPadding, step);
8586 if (reverse) range.reverse();
8587 rangeBand = step * (1 - padding);
8588 ranger = {
8589 t: "rangeBands",
8590 a: arguments
8591 };
8592 return scale;
8593 };
8594 scale.rangeRoundBands = function(x, padding, outerPadding) {
8595 if (arguments.length < 2) padding = 0;
8596 if (arguments.length < 3) outerPadding = padding;
8597 var reverse = x[1] < x[0], start = x[reverse - 0], stop = x[1 - reverse], step = Math.floor((stop - start) / (domain.length - padding + 2 * outerPadding));
8598 range = steps(start + Math.round((stop - start - (domain.length - padding) * step) / 2), step);
8599 if (reverse) range.reverse();
8600 rangeBand = Math.round(step * (1 - padding));
8601 ranger = {
8602 t: "rangeRoundBands",
8603 a: arguments
8604 };
8605 return scale;
8606 };
8607 scale.rangeBand = function() {
8608 return rangeBand;
8609 };
8610 scale.rangeExtent = function() {
8611 return d3_scaleExtent(ranger.a[0]);
8612 };
8613 scale.copy = function() {
8614 return d3_scale_ordinal(domain, ranger);
8615 };
8616 return scale.domain(domain);
8617 }
8618 d3.scale.category10 = function() {
8619 return d3.scale.ordinal().range(d3_category10);
8620 };
8621 d3.scale.category20 = function() {
8622 return d3.scale.ordinal().range(d3_category20);
8623 };
8624 d3.scale.category20b = function() {
8625 return d3.scale.ordinal().range(d3_category20b);
8626 };
8627 d3.scale.category20c = function() {
8628 return d3.scale.ordinal().range(d3_category20c);
8629 };
8630 var d3_category10 = [ 2062260, 16744206, 2924588, 14034728, 9725885, 9197131, 14907330, 8355711, 12369186, 1556175 ].map(d3_rgbString);
8631 var d3_category20 = [ 2062260, 11454440, 16744206, 16759672, 2924588, 10018698, 14034728, 16750742, 9725885, 12955861, 9197131, 12885140, 14907330, 16234194, 8355711, 13092807, 12369186, 14408589, 1556175, 10410725 ].map(d3_rgbString);
8632 var d3_category20b = [ 3750777, 5395619, 7040719, 10264286, 6519097, 9216594, 11915115, 13556636, 9202993, 12426809, 15186514, 15190932, 8666169, 11356490, 14049643, 15177372, 8077683, 10834324, 13528509, 14589654 ].map(d3_rgbString);
8633 var d3_category20c = [ 3244733, 7057110, 10406625, 13032431, 15095053, 16616764, 16625259, 16634018, 3253076, 7652470, 10607003, 13101504, 7695281, 10394312, 12369372, 14342891, 6513507, 9868950, 12434877, 14277081 ].map(d3_rgbString);
8634 d3.scale.quantile = function() {
8635 return d3_scale_quantile([], []);
8636 };
8637 function d3_scale_quantile(domain, range) {
8638 var thresholds;
8639 function rescale() {
8640 var k = 0, q = range.length;
8641 thresholds = [];
8642 while (++k < q) thresholds[k - 1] = d3.quantile(domain, k / q);
8643 return scale;
8644 }
8645 function scale(x) {
8646 if (!isNaN(x = +x)) return range[d3.bisect(thresholds, x)];
8647 }
8648 scale.domain = function(x) {
8649 if (!arguments.length) return domain;
8650 domain = x.map(d3_number).filter(d3_numeric).sort(d3_ascending);
8651 return rescale();
8652 };
8653 scale.range = function(x) {
8654 if (!arguments.length) return range;
8655 range = x;
8656 return rescale();
8657 };
8658 scale.quantiles = function() {
8659 return thresholds;
8660 };
8661 scale.invertExtent = function(y) {
8662 y = range.indexOf(y);
8663 return y < 0 ? [ NaN, NaN ] : [ y > 0 ? thresholds[y - 1] : domain[0], y < thresholds.length ? thresholds[y] : domain[domain.length - 1] ];
8664 };
8665 scale.copy = function() {
8666 return d3_scale_quantile(domain, range);
8667 };
8668 return rescale();
8669 }
8670 d3.scale.quantize = function() {
8671 return d3_scale_quantize(0, 1, [ 0, 1 ]);
8672 };
8673 function d3_scale_quantize(x0, x1, range) {
8674 var kx, i;
8675 function scale(x) {
8676 return range[Math.max(0, Math.min(i, Math.floor(kx * (x - x0))))];
8677 }
8678 function rescale() {
8679 kx = range.length / (x1 - x0);
8680 i = range.length - 1;
8681 return scale;
8682 }
8683 scale.domain = function(x) {
8684 if (!arguments.length) return [ x0, x1 ];
8685 x0 = +x[0];
8686 x1 = +x[x.length - 1];
8687 return rescale();
8688 };
8689 scale.range = function(x) {
8690 if (!arguments.length) return range;
8691 range = x;
8692 return rescale();
8693 };
8694 scale.invertExtent = function(y) {
8695 y = range.indexOf(y);
8696 y = y < 0 ? NaN : y / kx + x0;
8697 return [ y, y + 1 / kx ];
8698 };
8699 scale.copy = function() {
8700 return d3_scale_quantize(x0, x1, range);
8701 };
8702 return rescale();
8703 }
8704 d3.scale.threshold = function() {
8705 return d3_scale_threshold([ .5 ], [ 0, 1 ]);
8706 };
8707 function d3_scale_threshold(domain, range) {
8708 function scale(x) {
8709 if (x <= x) return range[d3.bisect(domain, x)];
8710 }
8711 scale.domain = function(_) {
8712 if (!arguments.length) return domain;
8713 domain = _;
8714 return scale;
8715 };
8716 scale.range = function(_) {
8717 if (!arguments.length) return range;
8718 range = _;
8719 return scale;
8720 };
8721 scale.invertExtent = function(y) {
8722 y = range.indexOf(y);
8723 return [ domain[y - 1], domain[y] ];
8724 };
8725 scale.copy = function() {
8726 return d3_scale_threshold(domain, range);
8727 };
8728 return scale;
8729 }
8730 d3.scale.identity = function() {
8731 return d3_scale_identity([ 0, 1 ]);
8732 };
8733 function d3_scale_identity(domain) {
8734 function identity(x) {
8735 return +x;
8736 }
8737 identity.invert = identity;
8738 identity.domain = identity.range = function(x) {
8739 if (!arguments.length) return domain;
8740 domain = x.map(identity);
8741 return identity;
8742 };
8743 identity.ticks = function(m) {
8744 return d3_scale_linearTicks(domain, m);
8745 };
8746 identity.tickFormat = function(m, format) {
8747 return d3_scale_linearTickFormat(domain, m, format);
8748 };
8749 identity.copy = function() {
8750 return d3_scale_identity(domain);
8751 };
8752 return identity;
8753 }
8754 d3.svg = {};
8755 function d3_zero() {
8756 return 0;
8757 }
8758 d3.svg.arc = function() {
8759 var innerRadius = d3_svg_arcInnerRadius, outerRadius = d3_svg_arcOuterRadius, cornerRadius = d3_zero, padRadius = d3_svg_arcAuto, startAngle = d3_svg_arcStartAngle, endAngle = d3_svg_arcEndAngle, padAngle = d3_svg_arcPadAngle;
8760 function arc() {
8761 var r0 = Math.max(0, +innerRadius.apply(this, arguments)), r1 = Math.max(0, +outerRadius.apply(this, arguments)), a0 = startAngle.apply(this, arguments) - halfπ, a1 = endAngle.apply(this, arguments) - halfπ, da = Math.abs(a1 - a0), cw = a0 > a1 ? 0 : 1;
8762 if (r1 < r0) rc = r1, r1 = r0, r0 = rc;
8763 if (da >= τε) return circleSegment(r1, cw) + (r0 ? circleSegment(r0, 1 - cw) : "") + "Z";
8764 var rc, cr, rp, ap, p0 = 0, p1 = 0, x0, y0, x1, y1, x2, y2, x3, y3, path = [];
8765 if (ap = (+padAngle.apply(this, arguments) || 0) / 2) {
8766 rp = padRadius === d3_svg_arcAuto ? Math.sqrt(r0 * r0 + r1 * r1) : +padRadius.apply(this, arguments);
8767 if (!cw) p1 *= -1;
8768 if (r1) p1 = d3_asin(rp / r1 * Math.sin(ap));
8769 if (r0) p0 = d3_asin(rp / r0 * Math.sin(ap));
8770 }
8771 if (r1) {
8772 x0 = r1 * Math.cos(a0 + p1);
8773 y0 = r1 * Math.sin(a0 + p1);
8774 x1 = r1 * Math.cos(a1 - p1);
8775 y1 = r1 * Math.sin(a1 - p1);
8776 var l1 = Math.abs(a1 - a0 - 2 * p1) <= π ? 0 : 1;
8777 if (p1 && d3_svg_arcSweep(x0, y0, x1, y1) === cw ^ l1) {
8778 var h1 = (a0 + a1) / 2;
8779 x0 = r1 * Math.cos(h1);
8780 y0 = r1 * Math.sin(h1);
8781 x1 = y1 = null;
8782 }
8783 } else {
8784 x0 = y0 = 0;
8785 }
8786 if (r0) {
8787 x2 = r0 * Math.cos(a1 - p0);
8788 y2 = r0 * Math.sin(a1 - p0);
8789 x3 = r0 * Math.cos(a0 + p0);
8790 y3 = r0 * Math.sin(a0 + p0);
8791 var l0 = Math.abs(a0 - a1 + 2 * p0) <= π ? 0 : 1;
8792 if (p0 && d3_svg_arcSweep(x2, y2, x3, y3) === 1 - cw ^ l0) {
8793 var h0 = (a0 + a1) / 2;
8794 x2 = r0 * Math.cos(h0);
8795 y2 = r0 * Math.sin(h0);
8796 x3 = y3 = null;
8797 }
8798 } else {
8799 x2 = y2 = 0;
8800 }
8801 if (da > ε && (rc = Math.min(Math.abs(r1 - r0) / 2, +cornerRadius.apply(this, arguments))) > .001) {
8802 cr = r0 < r1 ^ cw ? 0 : 1;
8803 var rc1 = rc, rc0 = rc;
8804 if (da < π) {
8805 var oc = x3 == null ? [ x2, y2 ] : x1 == null ? [ x0, y0 ] : d3_geom_polygonIntersect([ x0, y0 ], [ x3, y3 ], [ x1, y1 ], [ x2, y2 ]), ax = x0 - oc[0], ay = y0 - oc[1], bx = x1 - oc[0], by = y1 - oc[1], kc = 1 / Math.sin(Math.acos((ax * bx + ay * by) / (Math.sqrt(ax * ax + ay * ay) * Math.sqrt(bx * bx + by * by))) / 2), lc = Math.sqrt(oc[0] * oc[0] + oc[1] * oc[1]);
8806 rc0 = Math.min(rc, (r0 - lc) / (kc - 1));
8807 rc1 = Math.min(rc, (r1 - lc) / (kc + 1));
8808 }
8809 if (x1 != null) {
8810 var t30 = d3_svg_arcCornerTangents(x3 == null ? [ x2, y2 ] : [ x3, y3 ], [ x0, y0 ], r1, rc1, cw), t12 = d3_svg_arcCornerTangents([ x1, y1 ], [ x2, y2 ], r1, rc1, cw);
8811 if (rc === rc1) {
8812 path.push("M", t30[0], "A", rc1, ",", rc1, " 0 0,", cr, " ", t30[1], "A", r1, ",", r1, " 0 ", 1 - cw ^ d3_svg_arcSweep(t30[1][0], t30[1][1], t12[1][0], t12[1][1]), ",", cw, " ", t12[1], "A", rc1, ",", rc1, " 0 0,", cr, " ", t12[0]);
8813 } else {
8814 path.push("M", t30[0], "A", rc1, ",", rc1, " 0 1,", cr, " ", t12[0]);
8815 }
8816 } else {
8817 path.push("M", x0, ",", y0);
8818 }
8819 if (x3 != null) {
8820 var t03 = d3_svg_arcCornerTangents([ x0, y0 ], [ x3, y3 ], r0, -rc0, cw), t21 = d3_svg_arcCornerTangents([ x2, y2 ], x1 == null ? [ x0, y0 ] : [ x1, y1 ], r0, -rc0, cw);
8821 if (rc === rc0) {
8822 path.push("L", t21[0], "A", rc0, ",", rc0, " 0 0,", cr, " ", t21[1], "A", r0, ",", r0, " 0 ", cw ^ d3_svg_arcSweep(t21[1][0], t21[1][1], t03[1][0], t03[1][1]), ",", 1 - cw, " ", t03[1], "A", rc0, ",", rc0, " 0 0,", cr, " ", t03[0]);
8823 } else {
8824 path.push("L", t21[0], "A", rc0, ",", rc0, " 0 0,", cr, " ", t03[0]);
8825 }
8826 } else {
8827 path.push("L", x2, ",", y2);
8828 }
8829 } else {
8830 path.push("M", x0, ",", y0);
8831 if (x1 != null) path.push("A", r1, ",", r1, " 0 ", l1, ",", cw, " ", x1, ",", y1);
8832 path.push("L", x2, ",", y2);
8833 if (x3 != null) path.push("A", r0, ",", r0, " 0 ", l0, ",", 1 - cw, " ", x3, ",", y3);
8834 }
8835 path.push("Z");
8836 return path.join("");
8837 }
8838 function circleSegment(r1, cw) {
8839 return "M0," + r1 + "A" + r1 + "," + r1 + " 0 1," + cw + " 0," + -r1 + "A" + r1 + "," + r1 + " 0 1," + cw + " 0," + r1;
8840 }
8841 arc.innerRadius = function(v) {
8842 if (!arguments.length) return innerRadius;
8843 innerRadius = d3_functor(v);
8844 return arc;
8845 };
8846 arc.outerRadius = function(v) {
8847 if (!arguments.length) return outerRadius;
8848 outerRadius = d3_functor(v);
8849 return arc;
8850 };
8851 arc.cornerRadius = function(v) {
8852 if (!arguments.length) return cornerRadius;
8853 cornerRadius = d3_functor(v);
8854 return arc;
8855 };
8856 arc.padRadius = function(v) {
8857 if (!arguments.length) return padRadius;
8858 padRadius = v == d3_svg_arcAuto ? d3_svg_arcAuto : d3_functor(v);
8859 return arc;
8860 };
8861 arc.startAngle = function(v) {
8862 if (!arguments.length) return startAngle;
8863 startAngle = d3_functor(v);
8864 return arc;
8865 };
8866 arc.endAngle = function(v) {
8867 if (!arguments.length) return endAngle;
8868 endAngle = d3_functor(v);
8869 return arc;
8870 };
8871 arc.padAngle = function(v) {
8872 if (!arguments.length) return padAngle;
8873 padAngle = d3_functor(v);
8874 return arc;
8875 };
8876 arc.centroid = function() {
8877 var r = (+innerRadius.apply(this, arguments) + +outerRadius.apply(this, arguments)) / 2, a = (+startAngle.apply(this, arguments) + +endAngle.apply(this, arguments)) / 2 - halfπ;
8878 return [ Math.cos(a) * r, Math.sin(a) * r ];
8879 };
8880 return arc;
8881 };
8882 var d3_svg_arcAuto = "auto";
8883 function d3_svg_arcInnerRadius(d) {
8884 return d.innerRadius;
8885 }
8886 function d3_svg_arcOuterRadius(d) {
8887 return d.outerRadius;
8888 }
8889 function d3_svg_arcStartAngle(d) {
8890 return d.startAngle;
8891 }
8892 function d3_svg_arcEndAngle(d) {
8893 return d.endAngle;
8894 }
8895 function d3_svg_arcPadAngle(d) {
8896 return d && d.padAngle;
8897 }
8898 function d3_svg_arcSweep(x0, y0, x1, y1) {
8899 return (x0 - x1) * y0 - (y0 - y1) * x0 > 0 ? 0 : 1;
8900 }
8901 function d3_svg_arcCornerTangents(p0, p1, r1, rc, cw) {
8902 var x01 = p0[0] - p1[0], y01 = p0[1] - p1[1], lo = (cw ? rc : -rc) / Math.sqrt(x01 * x01 + y01 * y01), ox = lo * y01, oy = -lo * x01, x1 = p0[0] + ox, y1 = p0[1] + oy, x2 = p1[0] + ox, y2 = p1[1] + oy, x3 = (x1 + x2) / 2, y3 = (y1 + y2) / 2, dx = x2 - x1, dy = y2 - y1, d2 = dx * dx + dy * dy, r = r1 - rc, D = x1 * y2 - x2 * y1, d = (dy < 0 ? -1 : 1) * Math.sqrt(Math.max(0, r * r * d2 - D * D)), cx0 = (D * dy - dx * d) / d2, cy0 = (-D * dx - dy * d) / d2, cx1 = (D * dy + dx * d) / d2, cy1 = (-D * dx + dy * d) / d2, dx0 = cx0 - x3, dy0 = cy0 - y3, dx1 = cx1 - x3, dy1 = cy1 - y3;
8903 if (dx0 * dx0 + dy0 * dy0 > dx1 * dx1 + dy1 * dy1) cx0 = cx1, cy0 = cy1;
8904 return [ [ cx0 - ox, cy0 - oy ], [ cx0 * r1 / r, cy0 * r1 / r ] ];
8905 }
8906 function d3_svg_line(projection) {
8907 var x = d3_geom_pointX, y = d3_geom_pointY, defined = d3_true, interpolate = d3_svg_lineLinear, interpolateKey = interpolate.key, tension = .7;
8908 function line(data) {
8909 var segments = [], points = [], i = -1, n = data.length, d, fx = d3_functor(x), fy = d3_functor(y);
8910 function segment() {
8911 segments.push("M", interpolate(projection(points), tension));
8912 }
8913 while (++i < n) {
8914 if (defined.call(this, d = data[i], i)) {
8915 points.push([ +fx.call(this, d, i), +fy.call(this, d, i) ]);
8916 } else if (points.length) {
8917 segment();
8918 points = [];
8919 }
8920 }
8921 if (points.length) segment();
8922 return segments.length ? segments.join("") : null;
8923 }
8924 line.x = function(_) {
8925 if (!arguments.length) return x;
8926 x = _;
8927 return line;
8928 };
8929 line.y = function(_) {
8930 if (!arguments.length) return y;
8931 y = _;
8932 return line;
8933 };
8934 line.defined = function(_) {
8935 if (!arguments.length) return defined;
8936 defined = _;
8937 return line;
8938 };
8939 line.interpolate = function(_) {
8940 if (!arguments.length) return interpolateKey;
8941 if (typeof _ === "function") interpolateKey = interpolate = _; else interpolateKey = (interpolate = d3_svg_lineInterpolators.get(_) || d3_svg_lineLinear).key;
8942 return line;
8943 };
8944 line.tension = function(_) {
8945 if (!arguments.length) return tension;
8946 tension = _;
8947 return line;
8948 };
8949 return line;
8950 }
8951 d3.svg.line = function() {
8952 return d3_svg_line(d3_identity);
8953 };
8954 var d3_svg_lineInterpolators = d3.map({
8955 linear: d3_svg_lineLinear,
8956 "linear-closed": d3_svg_lineLinearClosed,
8957 step: d3_svg_lineStep,
8958 "step-before": d3_svg_lineStepBefore,
8959 "step-after": d3_svg_lineStepAfter,
8960 basis: d3_svg_lineBasis,
8961 "basis-open": d3_svg_lineBasisOpen,
8962 "basis-closed": d3_svg_lineBasisClosed,
8963 bundle: d3_svg_lineBundle,
8964 cardinal: d3_svg_lineCardinal,
8965 "cardinal-open": d3_svg_lineCardinalOpen,
8966 "cardinal-closed": d3_svg_lineCardinalClosed,
8967 monotone: d3_svg_lineMonotone
8968 });
8969 d3_svg_lineInterpolators.forEach(function(key, value) {
8970 value.key = key;
8971 value.closed = /-closed$/.test(key);
8972 });
8973 function d3_svg_lineLinear(points) {
8974 return points.length > 1 ? points.join("L") : points + "Z";
8975 }
8976 function d3_svg_lineLinearClosed(points) {
8977 return points.join("L") + "Z";
8978 }
8979 function d3_svg_lineStep(points) {
8980 var i = 0, n = points.length, p = points[0], path = [ p[0], ",", p[1] ];
8981 while (++i < n) path.push("H", (p[0] + (p = points[i])[0]) / 2, "V", p[1]);
8982 if (n > 1) path.push("H", p[0]);
8983 return path.join("");
8984 }
8985 function d3_svg_lineStepBefore(points) {
8986 var i = 0, n = points.length, p = points[0], path = [ p[0], ",", p[1] ];
8987 while (++i < n) path.push("V", (p = points[i])[1], "H", p[0]);
8988 return path.join("");
8989 }
8990 function d3_svg_lineStepAfter(points) {
8991 var i = 0, n = points.length, p = points[0], path = [ p[0], ",", p[1] ];
8992 while (++i < n) path.push("H", (p = points[i])[0], "V", p[1]);
8993 return path.join("");
8994 }
8995 function d3_svg_lineCardinalOpen(points, tension) {
8996 return points.length < 4 ? d3_svg_lineLinear(points) : points[1] + d3_svg_lineHermite(points.slice(1, -1), d3_svg_lineCardinalTangents(points, tension));
8997 }
8998 function d3_svg_lineCardinalClosed(points, tension) {
8999 return points.length < 3 ? d3_svg_lineLinearClosed(points) : points[0] + d3_svg_lineHermite((points.push(points[0]),
9000 points), d3_svg_lineCardinalTangents([ points[points.length - 2] ].concat(points, [ points[1] ]), tension));
9001 }
9002 function d3_svg_lineCardinal(points, tension) {
9003 return points.length < 3 ? d3_svg_lineLinear(points) : points[0] + d3_svg_lineHermite(points, d3_svg_lineCardinalTangents(points, tension));
9004 }
9005 function d3_svg_lineHermite(points, tangents) {
9006 if (tangents.length < 1 || points.length != tangents.length && points.length != tangents.length + 2) {
9007 return d3_svg_lineLinear(points);
9008 }
9009 var quad = points.length != tangents.length, path = "", p0 = points[0], p = points[1], t0 = tangents[0], t = t0, pi = 1;
9010 if (quad) {
9011 path += "Q" + (p[0] - t0[0] * 2 / 3) + "," + (p[1] - t0[1] * 2 / 3) + "," + p[0] + "," + p[1];
9012 p0 = points[1];
9013 pi = 2;
9014 }
9015 if (tangents.length > 1) {
9016 t = tangents[1];
9017 p = points[pi];
9018 pi++;
9019 path += "C" + (p0[0] + t0[0]) + "," + (p0[1] + t0[1]) + "," + (p[0] - t[0]) + "," + (p[1] - t[1]) + "," + p[0] + "," + p[1];
9020 for (var i = 2; i < tangents.length; i++, pi++) {
9021 p = points[pi];
9022 t = tangents[i];
9023 path += "S" + (p[0] - t[0]) + "," + (p[1] - t[1]) + "," + p[0] + "," + p[1];
9024 }
9025 }
9026 if (quad) {
9027 var lp = points[pi];
9028 path += "Q" + (p[0] + t[0] * 2 / 3) + "," + (p[1] + t[1] * 2 / 3) + "," + lp[0] + "," + lp[1];
9029 }
9030 return path;
9031 }
9032 function d3_svg_lineCardinalTangents(points, tension) {
9033 var tangents = [], a = (1 - tension) / 2, p0, p1 = points[0], p2 = points[1], i = 1, n = points.length;
9034 while (++i < n) {
9035 p0 = p1;
9036 p1 = p2;
9037 p2 = points[i];
9038 tangents.push([ a * (p2[0] - p0[0]), a * (p2[1] - p0[1]) ]);
9039 }
9040 return tangents;
9041 }
9042 function d3_svg_lineBasis(points) {
9043 if (points.length < 3) return d3_svg_lineLinear(points);
9044 var i = 1, n = points.length, pi = points[0], x0 = pi[0], y0 = pi[1], px = [ x0, x0, x0, (pi = points[1])[0] ], py = [ y0, y0, y0, pi[1] ], path = [ x0, ",", y0, "L", d3_svg_lineDot4(d3_svg_lineBasisBezier3, px), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, py) ];
9045 points.push(points[n - 1]);
9046 while (++i <= n) {
9047 pi = points[i];
9048 px.shift();
9049 px.push(pi[0]);
9050 py.shift();
9051 py.push(pi[1]);
9052 d3_svg_lineBasisBezier(path, px, py);
9053 }
9054 points.pop();
9055 path.push("L", pi);
9056 return path.join("");
9057 }
9058 function d3_svg_lineBasisOpen(points) {
9059 if (points.length < 4) return d3_svg_lineLinear(points);
9060 var path = [], i = -1, n = points.length, pi, px = [ 0 ], py = [ 0 ];
9061 while (++i < 3) {
9062 pi = points[i];
9063 px.push(pi[0]);
9064 py.push(pi[1]);
9065 }
9066 path.push(d3_svg_lineDot4(d3_svg_lineBasisBezier3, px) + "," + d3_svg_lineDot4(d3_svg_lineBasisBezier3, py));
9067 --i;
9068 while (++i < n) {
9069 pi = points[i];
9070 px.shift();
9071 px.push(pi[0]);
9072 py.shift();
9073 py.push(pi[1]);
9074 d3_svg_lineBasisBezier(path, px, py);
9075 }
9076 return path.join("");
9077 }
9078 function d3_svg_lineBasisClosed(points) {
9079 var path, i = -1, n = points.length, m = n + 4, pi, px = [], py = [];
9080 while (++i < 4) {
9081 pi = points[i % n];
9082 px.push(pi[0]);
9083 py.push(pi[1]);
9084 }
9085 path = [ d3_svg_lineDot4(d3_svg_lineBasisBezier3, px), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, py) ];
9086 --i;
9087 while (++i < m) {
9088 pi = points[i % n];
9089 px.shift();
9090 px.push(pi[0]);
9091 py.shift();
9092 py.push(pi[1]);
9093 d3_svg_lineBasisBezier(path, px, py);
9094 }
9095 return path.join("");
9096 }
9097 function d3_svg_lineBundle(points, tension) {
9098 var n = points.length - 1;
9099 if (n) {
9100 var x0 = points[0][0], y0 = points[0][1], dx = points[n][0] - x0, dy = points[n][1] - y0, i = -1, p, t;
9101 while (++i <= n) {
9102 p = points[i];
9103 t = i / n;
9104 p[0] = tension * p[0] + (1 - tension) * (x0 + t * dx);
9105 p[1] = tension * p[1] + (1 - tension) * (y0 + t * dy);
9106 }
9107 }
9108 return d3_svg_lineBasis(points);
9109 }
9110 function d3_svg_lineDot4(a, b) {
9111 return a[0] * b[0] + a[1] * b[1] + a[2] * b[2] + a[3] * b[3];
9112 }
9113 var d3_svg_lineBasisBezier1 = [ 0, 2 / 3, 1 / 3, 0 ], d3_svg_lineBasisBezier2 = [ 0, 1 / 3, 2 / 3, 0 ], d3_svg_lineBasisBezier3 = [ 0, 1 / 6, 2 / 3, 1 / 6 ];
9114 function d3_svg_lineBasisBezier(path, x, y) {
9115 path.push("C", d3_svg_lineDot4(d3_svg_lineBasisBezier1, x), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier1, y), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier2, x), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier2, y), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, x), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, y));
9116 }
9117 function d3_svg_lineSlope(p0, p1) {
9118 return (p1[1] - p0[1]) / (p1[0] - p0[0]);
9119 }
9120 function d3_svg_lineFiniteDifferences(points) {
9121 var i = 0, j = points.length - 1, m = [], p0 = points[0], p1 = points[1], d = m[0] = d3_svg_lineSlope(p0, p1);
9122 while (++i < j) {
9123 m[i] = (d + (d = d3_svg_lineSlope(p0 = p1, p1 = points[i + 1]))) / 2;
9124 }
9125 m[i] = d;
9126 return m;
9127 }
9128 function d3_svg_lineMonotoneTangents(points) {
9129 var tangents = [], d, a, b, s, m = d3_svg_lineFiniteDifferences(points), i = -1, j = points.length - 1;
9130 while (++i < j) {
9131 d = d3_svg_lineSlope(points[i], points[i + 1]);
9132 if (abs(d) < ε) {
9133 m[i] = m[i + 1] = 0;
9134 } else {
9135 a = m[i] / d;
9136 b = m[i + 1] / d;
9137 s = a * a + b * b;
9138 if (s > 9) {
9139 s = d * 3 / Math.sqrt(s);
9140 m[i] = s * a;
9141 m[i + 1] = s * b;
9142 }
9143 }
9144 }
9145 i = -1;
9146 while (++i <= j) {
9147 s = (points[Math.min(j, i + 1)][0] - points[Math.max(0, i - 1)][0]) / (6 * (1 + m[i] * m[i]));
9148 tangents.push([ s || 0, m[i] * s || 0 ]);
9149 }
9150 return tangents;
9151 }
9152 function d3_svg_lineMonotone(points) {
9153 return points.length < 3 ? d3_svg_lineLinear(points) : points[0] + d3_svg_lineHermite(points, d3_svg_lineMonotoneTangents(points));
9154 }
9155 d3.svg.line.radial = function() {
9156 var line = d3_svg_line(d3_svg_lineRadial);
9157 line.radius = line.x, delete line.x;
9158 line.angle = line.y, delete line.y;
9159 return line;
9160 };
9161 function d3_svg_lineRadial(points) {
9162 var point, i = -1, n = points.length, r, a;
9163 while (++i < n) {
9164 point = points[i];
9165 r = point[0];
9166 a = point[1] - halfπ;
9167 point[0] = r * Math.cos(a);
9168 point[1] = r * Math.sin(a);
9169 }
9170 return points;
9171 }
9172 function d3_svg_area(projection) {
9173 var x0 = d3_geom_pointX, x1 = d3_geom_pointX, y0 = 0, y1 = d3_geom_pointY, defined = d3_true, interpolate = d3_svg_lineLinear, interpolateKey = interpolate.key, interpolateReverse = interpolate, L = "L", tension = .7;
9174 function area(data) {
9175 var segments = [], points0 = [], points1 = [], i = -1, n = data.length, d, fx0 = d3_functor(x0), fy0 = d3_functor(y0), fx1 = x0 === x1 ? function() {
9176 return x;
9177 } : d3_functor(x1), fy1 = y0 === y1 ? function() {
9178 return y;
9179 } : d3_functor(y1), x, y;
9180 function segment() {
9181 segments.push("M", interpolate(projection(points1), tension), L, interpolateReverse(projection(points0.reverse()), tension), "Z");
9182 }
9183 while (++i < n) {
9184 if (defined.call(this, d = data[i], i)) {
9185 points0.push([ x = +fx0.call(this, d, i), y = +fy0.call(this, d, i) ]);
9186 points1.push([ +fx1.call(this, d, i), +fy1.call(this, d, i) ]);
9187 } else if (points0.length) {
9188 segment();
9189 points0 = [];
9190 points1 = [];
9191 }
9192 }
9193 if (points0.length) segment();
9194 return segments.length ? segments.join("") : null;
9195 }
9196 area.x = function(_) {
9197 if (!arguments.length) return x1;
9198 x0 = x1 = _;
9199 return area;
9200 };
9201 area.x0 = function(_) {
9202 if (!arguments.length) return x0;
9203 x0 = _;
9204 return area;
9205 };
9206 area.x1 = function(_) {
9207 if (!arguments.length) return x1;
9208 x1 = _;
9209 return area;
9210 };
9211 area.y = function(_) {
9212 if (!arguments.length) return y1;
9213 y0 = y1 = _;
9214 return area;
9215 };
9216 area.y0 = function(_) {
9217 if (!arguments.length) return y0;
9218 y0 = _;
9219 return area;
9220 };
9221 area.y1 = function(_) {
9222 if (!arguments.length) return y1;
9223 y1 = _;
9224 return area;
9225 };
9226 area.defined = function(_) {
9227 if (!arguments.length) return defined;
9228 defined = _;
9229 return area;
9230 };
9231 area.interpolate = function(_) {
9232 if (!arguments.length) return interpolateKey;
9233 if (typeof _ === "function") interpolateKey = interpolate = _; else interpolateKey = (interpolate = d3_svg_lineInterpolators.get(_) || d3_svg_lineLinear).key;
9234 interpolateReverse = interpolate.reverse || interpolate;
9235 L = interpolate.closed ? "M" : "L";
9236 return area;
9237 };
9238 area.tension = function(_) {
9239 if (!arguments.length) return tension;
9240 tension = _;
9241 return area;
9242 };
9243 return area;
9244 }
9245 d3_svg_lineStepBefore.reverse = d3_svg_lineStepAfter;
9246 d3_svg_lineStepAfter.reverse = d3_svg_lineStepBefore;
9247 d3.svg.area = function() {
9248 return d3_svg_area(d3_identity);
9249 };
9250 d3.svg.area.radial = function() {
9251 var area = d3_svg_area(d3_svg_lineRadial);
9252 area.radius = area.x, delete area.x;
9253 area.innerRadius = area.x0, delete area.x0;
9254 area.outerRadius = area.x1, delete area.x1;
9255 area.angle = area.y, delete area.y;
9256 area.startAngle = area.y0, delete area.y0;
9257 area.endAngle = area.y1, delete area.y1;
9258 return area;
9259 };
9260 d3.svg.chord = function() {
9261 var source = d3_source, target = d3_target, radius = d3_svg_chordRadius, startAngle = d3_svg_arcStartAngle, endAngle = d3_svg_arcEndAngle;
9262 function chord(d, i) {
9263 var s = subgroup(this, source, d, i), t = subgroup(this, target, d, i);
9264 return "M" + s.p0 + arc(s.r, s.p1, s.a1 - s.a0) + (equals(s, t) ? curve(s.r, s.p1, s.r, s.p0) : curve(s.r, s.p1, t.r, t.p0) + arc(t.r, t.p1, t.a1 - t.a0) + curve(t.r, t.p1, s.r, s.p0)) + "Z";
9265 }
9266 function subgroup(self, f, d, i) {
9267 var subgroup = f.call(self, d, i), r = radius.call(self, subgroup, i), a0 = startAngle.call(self, subgroup, i) - halfπ, a1 = endAngle.call(self, subgroup, i) - halfπ;
9268 return {
9269 r: r,
9270 a0: a0,
9271 a1: a1,
9272 p0: [ r * Math.cos(a0), r * Math.sin(a0) ],
9273 p1: [ r * Math.cos(a1), r * Math.sin(a1) ]
9274 };
9275 }
9276 function equals(a, b) {
9277 return a.a0 == b.a0 && a.a1 == b.a1;
9278 }
9279 function arc(r, p, a) {
9280 return "A" + r + "," + r + " 0 " + +(a > π) + ",1 " + p;
9281 }
9282 function curve(r0, p0, r1, p1) {
9283 return "Q 0,0 " + p1;
9284 }
9285 chord.radius = function(v) {
9286 if (!arguments.length) return radius;
9287 radius = d3_functor(v);
9288 return chord;
9289 };
9290 chord.source = function(v) {
9291 if (!arguments.length) return source;
9292 source = d3_functor(v);
9293 return chord;
9294 };
9295 chord.target = function(v) {
9296 if (!arguments.length) return target;
9297 target = d3_functor(v);
9298 return chord;
9299 };
9300 chord.startAngle = function(v) {
9301 if (!arguments.length) return startAngle;
9302 startAngle = d3_functor(v);
9303 return chord;
9304 };
9305 chord.endAngle = function(v) {
9306 if (!arguments.length) return endAngle;
9307 endAngle = d3_functor(v);
9308 return chord;
9309 };
9310 return chord;
9311 };
9312 function d3_svg_chordRadius(d) {
9313 return d.radius;
9314 }
9315 d3.svg.diagonal = function() {
9316 var source = d3_source, target = d3_target, projection = d3_svg_diagonalProjection;
9317 function diagonal(d, i) {
9318 var p0 = source.call(this, d, i), p3 = target.call(this, d, i), m = (p0.y + p3.y) / 2, p = [ p0, {
9319 x: p0.x,
9320 y: m
9321 }, {
9322 x: p3.x,
9323 y: m
9324 }, p3 ];
9325 p = p.map(projection);
9326 return "M" + p[0] + "C" + p[1] + " " + p[2] + " " + p[3];
9327 }
9328 diagonal.source = function(x) {
9329 if (!arguments.length) return source;
9330 source = d3_functor(x);
9331 return diagonal;
9332 };
9333 diagonal.target = function(x) {
9334 if (!arguments.length) return target;
9335 target = d3_functor(x);
9336 return diagonal;
9337 };
9338 diagonal.projection = function(x) {
9339 if (!arguments.length) return projection;
9340 projection = x;
9341 return diagonal;
9342 };
9343 return diagonal;
9344 };
9345 function d3_svg_diagonalProjection(d) {
9346 return [ d.x, d.y ];
9347 }
9348 d3.svg.diagonal.radial = function() {
9349 var diagonal = d3.svg.diagonal(), projection = d3_svg_diagonalProjection, projection_ = diagonal.projection;
9350 diagonal.projection = function(x) {
9351 return arguments.length ? projection_(d3_svg_diagonalRadialProjection(projection = x)) : projection;
9352 };
9353 return diagonal;
9354 };
9355 function d3_svg_diagonalRadialProjection(projection) {
9356 return function() {
9357 var d = projection.apply(this, arguments), r = d[0], a = d[1] - halfπ;
9358 return [ r * Math.cos(a), r * Math.sin(a) ];
9359 };
9360 }
9361 d3.svg.symbol = function() {
9362 var type = d3_svg_symbolType, size = d3_svg_symbolSize;
9363 function symbol(d, i) {
9364 return (d3_svg_symbols.get(type.call(this, d, i)) || d3_svg_symbolCircle)(size.call(this, d, i));
9365 }
9366 symbol.type = function(x) {
9367 if (!arguments.length) return type;
9368 type = d3_functor(x);
9369 return symbol;
9370 };
9371 symbol.size = function(x) {
9372 if (!arguments.length) return size;
9373 size = d3_functor(x);
9374 return symbol;
9375 };
9376 return symbol;
9377 };
9378 function d3_svg_symbolSize() {
9379 return 64;
9380 }
9381 function d3_svg_symbolType() {
9382 return "circle";
9383 }
9384 function d3_svg_symbolCircle(size) {
9385 var r = Math.sqrt(size / π);
9386 return "M0," + r + "A" + r + "," + r + " 0 1,1 0," + -r + "A" + r + "," + r + " 0 1,1 0," + r + "Z";
9387 }
9388 var d3_svg_symbols = d3.map({
9389 circle: d3_svg_symbolCircle,
9390 cross: function(size) {
9391 var r = Math.sqrt(size / 5) / 2;
9392 return "M" + -3 * r + "," + -r + "H" + -r + "V" + -3 * r + "H" + r + "V" + -r + "H" + 3 * r + "V" + r + "H" + r + "V" + 3 * r + "H" + -r + "V" + r + "H" + -3 * r + "Z";
9393 },
9394 diamond: function(size) {
9395 var ry = Math.sqrt(size / (2 * d3_svg_symbolTan30)), rx = ry * d3_svg_symbolTan30;
9396 return "M0," + -ry + "L" + rx + ",0" + " 0," + ry + " " + -rx + ",0" + "Z";
9397 },
9398 square: function(size) {
9399 var r = Math.sqrt(size) / 2;
9400 return "M" + -r + "," + -r + "L" + r + "," + -r + " " + r + "," + r + " " + -r + "," + r + "Z";
9401 },
9402 "triangle-down": function(size) {
9403 var rx = Math.sqrt(size / d3_svg_symbolSqrt3), ry = rx * d3_svg_symbolSqrt3 / 2;
9404 return "M0," + ry + "L" + rx + "," + -ry + " " + -rx + "," + -ry + "Z";
9405 },
9406 "triangle-up": function(size) {
9407 var rx = Math.sqrt(size / d3_svg_symbolSqrt3), ry = rx * d3_svg_symbolSqrt3 / 2;
9408 return "M0," + -ry + "L" + rx + "," + ry + " " + -rx + "," + ry + "Z";
9409 }
9410 });
9411 d3.svg.symbolTypes = d3_svg_symbols.keys();
9412 var d3_svg_symbolSqrt3 = Math.sqrt(3), d3_svg_symbolTan30 = Math.tan(30 * d3_radians);
9413 d3_selectionPrototype.transition = function(name) {
9414 var id = d3_transitionInheritId || ++d3_transitionId, ns = d3_transitionNamespace(name), subgroups = [], subgroup, node, transition = d3_transitionInherit || {
9415 time: Date.now(),
9416 ease: d3_ease_cubicInOut,
9417 delay: 0,
9418 duration: 250
9419 };
9420 for (var j = -1, m = this.length; ++j < m; ) {
9421 subgroups.push(subgroup = []);
9422 for (var group = this[j], i = -1, n = group.length; ++i < n; ) {
9423 if (node = group[i]) d3_transitionNode(node, i, ns, id, transition);
9424 subgroup.push(node);
9425 }
9426 }
9427 return d3_transition(subgroups, ns, id);
9428 };
9429 d3_selectionPrototype.interrupt = function(name) {
9430 return this.each(name == null ? d3_selection_interrupt : d3_selection_interruptNS(d3_transitionNamespace(name)));
9431 };
9432 var d3_selection_interrupt = d3_selection_interruptNS(d3_transitionNamespace());
9433 function d3_selection_interruptNS(ns) {
9434 return function() {
9435 var lock, activeId, active;
9436 if ((lock = this[ns]) && (active = lock[activeId = lock.active])) {
9437 active.timer.c = null;
9438 active.timer.t = NaN;
9439 if (--lock.count) delete lock[activeId]; else delete this[ns];
9440 lock.active += .5;
9441 active.event && active.event.interrupt.call(this, this.__data__, active.index);
9442 }
9443 };
9444 }
9445 function d3_transition(groups, ns, id) {
9446 d3_subclass(groups, d3_transitionPrototype);
9447 groups.namespace = ns;
9448 groups.id = id;
9449 return groups;
9450 }
9451 var d3_transitionPrototype = [], d3_transitionId = 0, d3_transitionInheritId, d3_transitionInherit;
9452 d3_transitionPrototype.call = d3_selectionPrototype.call;
9453 d3_transitionPrototype.empty = d3_selectionPrototype.empty;
9454 d3_transitionPrototype.node = d3_selectionPrototype.node;
9455 d3_transitionPrototype.size = d3_selectionPrototype.size;
9456 d3.transition = function(selection, name) {
9457 return selection && selection.transition ? d3_transitionInheritId ? selection.transition(name) : selection : d3.selection().transition(selection);
9458 };
9459 d3.transition.prototype = d3_transitionPrototype;
9460 d3_transitionPrototype.select = function(selector) {
9461 var id = this.id, ns = this.namespace, subgroups = [], subgroup, subnode, node;
9462 selector = d3_selection_selector(selector);
9463 for (var j = -1, m = this.length; ++j < m; ) {
9464 subgroups.push(subgroup = []);
9465 for (var group = this[j], i = -1, n = group.length; ++i < n; ) {
9466 if ((node = group[i]) && (subnode = selector.call(node, node.__data__, i, j))) {
9467 if ("__data__" in node) subnode.__data__ = node.__data__;
9468 d3_transitionNode(subnode, i, ns, id, node[ns][id]);
9469 subgroup.push(subnode);
9470 } else {
9471 subgroup.push(null);
9472 }
9473 }
9474 }
9475 return d3_transition(subgroups, ns, id);
9476 };
9477 d3_transitionPrototype.selectAll = function(selector) {
9478 var id = this.id, ns = this.namespace, subgroups = [], subgroup, subnodes, node, subnode, transition;
9479 selector = d3_selection_selectorAll(selector);
9480 for (var j = -1, m = this.length; ++j < m; ) {
9481 for (var group = this[j], i = -1, n = group.length; ++i < n; ) {
9482 if (node = group[i]) {
9483 transition = node[ns][id];
9484 subnodes = selector.call(node, node.__data__, i, j);
9485 subgroups.push(subgroup = []);
9486 for (var k = -1, o = subnodes.length; ++k < o; ) {
9487 if (subnode = subnodes[k]) d3_transitionNode(subnode, k, ns, id, transition);
9488 subgroup.push(subnode);
9489 }
9490 }
9491 }
9492 }
9493 return d3_transition(subgroups, ns, id);
9494 };
9495 d3_transitionPrototype.filter = function(filter) {
9496 var subgroups = [], subgroup, group, node;
9497 if (typeof filter !== "function") filter = d3_selection_filter(filter);
9498 for (var j = 0, m = this.length; j < m; j++) {
9499 subgroups.push(subgroup = []);
9500 for (var group = this[j], i = 0, n = group.length; i < n; i++) {
9501 if ((node = group[i]) && filter.call(node, node.__data__, i, j)) {
9502 subgroup.push(node);
9503 }
9504 }
9505 }
9506 return d3_transition(subgroups, this.namespace, this.id);
9507 };
9508 d3_transitionPrototype.tween = function(name, tween) {
9509 var id = this.id, ns = this.namespace;
9510 if (arguments.length < 2) return this.node()[ns][id].tween.get(name);
9511 return d3_selection_each(this, tween == null ? function(node) {
9512 node[ns][id].tween.remove(name);
9513 } : function(node) {
9514 node[ns][id].tween.set(name, tween);
9515 });
9516 };
9517 function d3_transition_tween(groups, name, value, tween) {
9518 var id = groups.id, ns = groups.namespace;
9519 return d3_selection_each(groups, typeof value === "function" ? function(node, i, j) {
9520 node[ns][id].tween.set(name, tween(value.call(node, node.__data__, i, j)));
9521 } : (value = tween(value), function(node) {
9522 node[ns][id].tween.set(name, value);
9523 }));
9524 }
9525 d3_transitionPrototype.attr = function(nameNS, value) {
9526 if (arguments.length < 2) {
9527 for (value in nameNS) this.attr(value, nameNS[value]);
9528 return this;
9529 }
9530 var interpolate = nameNS == "transform" ? d3_interpolateTransform : d3_interpolate, name = d3.ns.qualify(nameNS);
9531 function attrNull() {
9532 this.removeAttribute(name);
9533 }
9534 function attrNullNS() {
9535 this.removeAttributeNS(name.space, name.local);
9536 }
9537 function attrTween(b) {
9538 return b == null ? attrNull : (b += "", function() {
9539 var a = this.getAttribute(name), i;
9540 return a !== b && (i = interpolate(a, b), function(t) {
9541 this.setAttribute(name, i(t));
9542 });
9543 });
9544 }
9545 function attrTweenNS(b) {
9546 return b == null ? attrNullNS : (b += "", function() {
9547 var a = this.getAttributeNS(name.space, name.local), i;
9548 return a !== b && (i = interpolate(a, b), function(t) {
9549 this.setAttributeNS(name.space, name.local, i(t));
9550 });
9551 });
9552 }
9553 return d3_transition_tween(this, "attr." + nameNS, value, name.local ? attrTweenNS : attrTween);
9554 };
9555 d3_transitionPrototype.attrTween = function(nameNS, tween) {
9556 var name = d3.ns.qualify(nameNS);
9557 function attrTween(d, i) {
9558 var f = tween.call(this, d, i, this.getAttribute(name));
9559 return f && function(t) {
9560 this.setAttribute(name, f(t));
9561 };
9562 }
9563 function attrTweenNS(d, i) {
9564 var f = tween.call(this, d, i, this.getAttributeNS(name.space, name.local));
9565 return f && function(t) {
9566 this.setAttributeNS(name.space, name.local, f(t));
9567 };
9568 }
9569 return this.tween("attr." + nameNS, name.local ? attrTweenNS : attrTween);
9570 };
9571 d3_transitionPrototype.style = function(name, value, priority) {
9572 var n = arguments.length;
9573 if (n < 3) {
9574 if (typeof name !== "string") {
9575 if (n < 2) value = "";
9576 for (priority in name) this.style(priority, name[priority], value);
9577 return this;
9578 }
9579 priority = "";
9580 }
9581 function styleNull() {
9582 this.style.removeProperty(name);
9583 }
9584 function styleString(b) {
9585 return b == null ? styleNull : (b += "", function() {
9586 var a = d3_window(this).getComputedStyle(this, null).getPropertyValue(name), i;
9587 return a !== b && (i = d3_interpolate(a, b), function(t) {
9588 this.style.setProperty(name, i(t), priority);
9589 });
9590 });
9591 }
9592 return d3_transition_tween(this, "style." + name, value, styleString);
9593 };
9594 d3_transitionPrototype.styleTween = function(name, tween, priority) {
9595 if (arguments.length < 3) priority = "";
9596 function styleTween(d, i) {
9597 var f = tween.call(this, d, i, d3_window(this).getComputedStyle(this, null).getPropertyValue(name));
9598 return f && function(t) {
9599 this.style.setProperty(name, f(t), priority);
9600 };
9601 }
9602 return this.tween("style." + name, styleTween);
9603 };
9604 d3_transitionPrototype.text = function(value) {
9605 return d3_transition_tween(this, "text", value, d3_transition_text);
9606 };
9607 function d3_transition_text(b) {
9608 if (b == null) b = "";
9609 return function() {
9610 this.textContent = b;
9611 };
9612 }
9613 d3_transitionPrototype.remove = function() {
9614 var ns = this.namespace;
9615 return this.each("end.transition", function() {
9616 var p;
9617 if (this[ns].count < 2 && (p = this.parentNode)) p.removeChild(this);
9618 });
9619 };
9620 d3_transitionPrototype.ease = function(value) {
9621 var id = this.id, ns = this.namespace;
9622 if (arguments.length < 1) return this.node()[ns][id].ease;
9623 if (typeof value !== "function") value = d3.ease.apply(d3, arguments);
9624 return d3_selection_each(this, function(node) {
9625 node[ns][id].ease = value;
9626 });
9627 };
9628 d3_transitionPrototype.delay = function(value) {
9629 var id = this.id, ns = this.namespace;
9630 if (arguments.length < 1) return this.node()[ns][id].delay;
9631 return d3_selection_each(this, typeof value === "function" ? function(node, i, j) {
9632 node[ns][id].delay = +value.call(node, node.__data__, i, j);
9633 } : (value = +value, function(node) {
9634 node[ns][id].delay = value;
9635 }));
9636 };
9637 d3_transitionPrototype.duration = function(value) {
9638 var id = this.id, ns = this.namespace;
9639 if (arguments.length < 1) return this.node()[ns][id].duration;
9640 return d3_selection_each(this, typeof value === "function" ? function(node, i, j) {
9641 node[ns][id].duration = Math.max(1, value.call(node, node.__data__, i, j));
9642 } : (value = Math.max(1, value), function(node) {
9643 node[ns][id].duration = value;
9644 }));
9645 };
9646 d3_transitionPrototype.each = function(type, listener) {
9647 var id = this.id, ns = this.namespace;
9648 if (arguments.length < 2) {
9649 var inherit = d3_transitionInherit, inheritId = d3_transitionInheritId;
9650 try {
9651 d3_transitionInheritId = id;
9652 d3_selection_each(this, function(node, i, j) {
9653 d3_transitionInherit = node[ns][id];
9654 type.call(node, node.__data__, i, j);
9655 });
9656 } finally {
9657 d3_transitionInherit = inherit;
9658 d3_transitionInheritId = inheritId;
9659 }
9660 } else {
9661 d3_selection_each(this, function(node) {
9662 var transition = node[ns][id];
9663 (transition.event || (transition.event = d3.dispatch("start", "end", "interrupt"))).on(type, listener);
9664 });
9665 }
9666 return this;
9667 };
9668 d3_transitionPrototype.transition = function() {
9669 var id0 = this.id, id1 = ++d3_transitionId, ns = this.namespace, subgroups = [], subgroup, group, node, transition;
9670 for (var j = 0, m = this.length; j < m; j++) {
9671 subgroups.push(subgroup = []);
9672 for (var group = this[j], i = 0, n = group.length; i < n; i++) {
9673 if (node = group[i]) {
9674 transition = node[ns][id0];
9675 d3_transitionNode(node, i, ns, id1, {
9676 time: transition.time,
9677 ease: transition.ease,
9678 delay: transition.delay + transition.duration,
9679 duration: transition.duration
9680 });
9681 }
9682 subgroup.push(node);
9683 }
9684 }
9685 return d3_transition(subgroups, ns, id1);
9686 };
9687 function d3_transitionNamespace(name) {
9688 return name == null ? "__transition__" : "__transition_" + name + "__";
9689 }
9690 function d3_transitionNode(node, i, ns, id, inherit) {
9691 var lock = node[ns] || (node[ns] = {
9692 active: 0,
9693 count: 0
9694 }), transition = lock[id], time, timer, duration, ease, tweens;
9695 function schedule(elapsed) {
9696 var delay = transition.delay;
9697 timer.t = delay + time;
9698 if (delay <= elapsed) return start(elapsed - delay);
9699 timer.c = start;
9700 }
9701 function start(elapsed) {
9702 var activeId = lock.active, active = lock[activeId];
9703 if (active) {
9704 active.timer.c = null;
9705 active.timer.t = NaN;
9706 --lock.count;
9707 delete lock[activeId];
9708 active.event && active.event.interrupt.call(node, node.__data__, active.index);
9709 }
9710 for (var cancelId in lock) {
9711 if (+cancelId < id) {
9712 var cancel = lock[cancelId];
9713 cancel.timer.c = null;
9714 cancel.timer.t = NaN;
9715 --lock.count;
9716 delete lock[cancelId];
9717 }
9718 }
9719 timer.c = tick;
9720 d3_timer(function() {
9721 if (timer.c && tick(elapsed || 1)) {
9722 timer.c = null;
9723 timer.t = NaN;
9724 }
9725 return 1;
9726 }, 0, time);
9727 lock.active = id;
9728 transition.event && transition.event.start.call(node, node.__data__, i);
9729 tweens = [];
9730 transition.tween.forEach(function(key, value) {
9731 if (value = value.call(node, node.__data__, i)) {
9732 tweens.push(value);
9733 }
9734 });
9735 ease = transition.ease;
9736 duration = transition.duration;
9737 }
9738 function tick(elapsed) {
9739 var t = elapsed / duration, e = ease(t), n = tweens.length;
9740 while (n > 0) {
9741 tweens[--n].call(node, e);
9742 }
9743 if (t >= 1) {
9744 transition.event && transition.event.end.call(node, node.__data__, i);
9745 if (--lock.count) delete lock[id]; else delete node[ns];
9746 return 1;
9747 }
9748 }
9749 if (!transition) {
9750 time = inherit.time;
9751 timer = d3_timer(schedule, 0, time);
9752 transition = lock[id] = {
9753 tween: new d3_Map(),
9754 time: time,
9755 timer: timer,
9756 delay: inherit.delay,
9757 duration: inherit.duration,
9758 ease: inherit.ease,
9759 index: i
9760 };
9761 inherit = null;
9762 ++lock.count;
9763 }
9764 }
9765 d3.svg.axis = function() {
9766 var scale = d3.scale.linear(), orient = d3_svg_axisDefaultOrient, innerTickSize = 6, outerTickSize = 6, tickPadding = 3, tickArguments_ = [ 10 ], tickValues = null, tickFormat_;
9767 function axis(g) {
9768 g.each(function() {
9769 var g = d3.select(this);
9770 var scale0 = this.__chart__ || scale, scale1 = this.__chart__ = scale.copy();
9771 var ticks = tickValues == null ? scale1.ticks ? scale1.ticks.apply(scale1, tickArguments_) : scale1.domain() : tickValues, tickFormat = tickFormat_ == null ? scale1.tickFormat ? scale1.tickFormat.apply(scale1, tickArguments_) : d3_identity : tickFormat_, tick = g.selectAll(".tick").data(ticks, scale1), tickEnter = tick.enter().insert("g", ".domain").attr("class", "tick").style("opacity", ε), tickExit = d3.transition(tick.exit()).style("opacity", ε).remove(), tickUpdate = d3.transition(tick.order()).style("opacity", 1), tickSpacing = Math.max(innerTickSize, 0) + tickPadding, tickTransform;
9772 var range = d3_scaleRange(scale1), path = g.selectAll(".domain").data([ 0 ]), pathUpdate = (path.enter().append("path").attr("class", "domain"),
9773 d3.transition(path));
9774 tickEnter.append("line");
9775 tickEnter.append("text");
9776 var lineEnter = tickEnter.select("line"), lineUpdate = tickUpdate.select("line"), text = tick.select("text").text(tickFormat), textEnter = tickEnter.select("text"), textUpdate = tickUpdate.select("text"), sign = orient === "top" || orient === "left" ? -1 : 1, x1, x2, y1, y2;
9777 if (orient === "bottom" || orient === "top") {
9778 tickTransform = d3_svg_axisX, x1 = "x", y1 = "y", x2 = "x2", y2 = "y2";
9779 text.attr("dy", sign < 0 ? "0em" : ".71em").style("text-anchor", "middle");
9780 pathUpdate.attr("d", "M" + range[0] + "," + sign * outerTickSize + "V0H" + range[1] + "V" + sign * outerTickSize);
9781 } else {
9782 tickTransform = d3_svg_axisY, x1 = "y", y1 = "x", x2 = "y2", y2 = "x2";
9783 text.attr("dy", ".32em").style("text-anchor", sign < 0 ? "end" : "start");
9784 pathUpdate.attr("d", "M" + sign * outerTickSize + "," + range[0] + "H0V" + range[1] + "H" + sign * outerTickSize);
9785 }
9786 lineEnter.attr(y2, sign * innerTickSize);
9787 textEnter.attr(y1, sign * tickSpacing);
9788 lineUpdate.attr(x2, 0).attr(y2, sign * innerTickSize);
9789 textUpdate.attr(x1, 0).attr(y1, sign * tickSpacing);
9790 if (scale1.rangeBand) {
9791 var x = scale1, dx = x.rangeBand() / 2;
9792 scale0 = scale1 = function(d) {
9793 return x(d) + dx;
9794 };
9795 } else if (scale0.rangeBand) {
9796 scale0 = scale1;
9797 } else {
9798 tickExit.call(tickTransform, scale1, scale0);
9799 }
9800 tickEnter.call(tickTransform, scale0, scale1);
9801 tickUpdate.call(tickTransform, scale1, scale1);
9802 });
9803 }
9804 axis.scale = function(x) {
9805 if (!arguments.length) return scale;
9806 scale = x;
9807 return axis;
9808 };
9809 axis.orient = function(x) {
9810 if (!arguments.length) return orient;
9811 orient = x in d3_svg_axisOrients ? x + "" : d3_svg_axisDefaultOrient;
9812 return axis;
9813 };
9814 axis.ticks = function() {
9815 if (!arguments.length) return tickArguments_;
9816 tickArguments_ = d3_array(arguments);
9817 return axis;
9818 };
9819 axis.tickValues = function(x) {
9820 if (!arguments.length) return tickValues;
9821 tickValues = x;
9822 return axis;
9823 };
9824 axis.tickFormat = function(x) {
9825 if (!arguments.length) return tickFormat_;
9826 tickFormat_ = x;
9827 return axis;
9828 };
9829 axis.tickSize = function(x) {
9830 var n = arguments.length;
9831 if (!n) return innerTickSize;
9832 innerTickSize = +x;
9833 outerTickSize = +arguments[n - 1];
9834 return axis;
9835 };
9836 axis.innerTickSize = function(x) {
9837 if (!arguments.length) return innerTickSize;
9838 innerTickSize = +x;
9839 return axis;
9840 };
9841 axis.outerTickSize = function(x) {
9842 if (!arguments.length) return outerTickSize;
9843 outerTickSize = +x;
9844 return axis;
9845 };
9846 axis.tickPadding = function(x) {
9847 if (!arguments.length) return tickPadding;
9848 tickPadding = +x;
9849 return axis;
9850 };
9851 axis.tickSubdivide = function() {
9852 return arguments.length && axis;
9853 };
9854 return axis;
9855 };
9856 var d3_svg_axisDefaultOrient = "bottom", d3_svg_axisOrients = {
9857 top: 1,
9858 right: 1,
9859 bottom: 1,
9860 left: 1
9861 };
9862 function d3_svg_axisX(selection, x0, x1) {
9863 selection.attr("transform", function(d) {
9864 var v0 = x0(d);
9865 return "translate(" + (isFinite(v0) ? v0 : x1(d)) + ",0)";
9866 });
9867 }
9868 function d3_svg_axisY(selection, y0, y1) {
9869 selection.attr("transform", function(d) {
9870 var v0 = y0(d);
9871 return "translate(0," + (isFinite(v0) ? v0 : y1(d)) + ")";
9872 });
9873 }
9874 d3.svg.brush = function() {
9875 var event = d3_eventDispatch(brush, "brushstart", "brush", "brushend"), x = null, y = null, xExtent = [ 0, 0 ], yExtent = [ 0, 0 ], xExtentDomain, yExtentDomain, xClamp = true, yClamp = true, resizes = d3_svg_brushResizes[0];
9876 function brush(g) {
9877 g.each(function() {
9878 var g = d3.select(this).style("pointer-events", "all").style("-webkit-tap-highlight-color", "rgba(0,0,0,0)").on("mousedown.brush", brushstart).on("touchstart.brush", brushstart);
9879 var background = g.selectAll(".background").data([ 0 ]);
9880 background.enter().append("rect").attr("class", "background").style("visibility", "hidden").style("cursor", "crosshair");
9881 g.selectAll(".extent").data([ 0 ]).enter().append("rect").attr("class", "extent").style("cursor", "move");
9882 var resize = g.selectAll(".resize").data(resizes, d3_identity);
9883 resize.exit().remove();
9884 resize.enter().append("g").attr("class", function(d) {
9885 return "resize " + d;
9886 }).style("cursor", function(d) {
9887 return d3_svg_brushCursor[d];
9888 }).append("rect").attr("x", function(d) {
9889 return /[ew]$/.test(d) ? -3 : null;
9890 }).attr("y", function(d) {
9891 return /^[ns]/.test(d) ? -3 : null;
9892 }).attr("width", 6).attr("height", 6).style("visibility", "hidden");
9893 resize.style("display", brush.empty() ? "none" : null);
9894 var gUpdate = d3.transition(g), backgroundUpdate = d3.transition(background), range;
9895 if (x) {
9896 range = d3_scaleRange(x);
9897 backgroundUpdate.attr("x", range[0]).attr("width", range[1] - range[0]);
9898 redrawX(gUpdate);
9899 }
9900 if (y) {
9901 range = d3_scaleRange(y);
9902 backgroundUpdate.attr("y", range[0]).attr("height", range[1] - range[0]);
9903 redrawY(gUpdate);
9904 }
9905 redraw(gUpdate);
9906 });
9907 }
9908 brush.event = function(g) {
9909 g.each(function() {
9910 var event_ = event.of(this, arguments), extent1 = {
9911 x: xExtent,
9912 y: yExtent,
9913 i: xExtentDomain,
9914 j: yExtentDomain
9915 }, extent0 = this.__chart__ || extent1;
9916 this.__chart__ = extent1;
9917 if (d3_transitionInheritId) {
9918 d3.select(this).transition().each("start.brush", function() {
9919 xExtentDomain = extent0.i;
9920 yExtentDomain = extent0.j;
9921 xExtent = extent0.x;
9922 yExtent = extent0.y;
9923 event_({
9924 type: "brushstart"
9925 });
9926 }).tween("brush:brush", function() {
9927 var xi = d3_interpolateArray(xExtent, extent1.x), yi = d3_interpolateArray(yExtent, extent1.y);
9928 xExtentDomain = yExtentDomain = null;
9929 return function(t) {
9930 xExtent = extent1.x = xi(t);
9931 yExtent = extent1.y = yi(t);
9932 event_({
9933 type: "brush",
9934 mode: "resize"
9935 });
9936 };
9937 }).each("end.brush", function() {
9938 xExtentDomain = extent1.i;
9939 yExtentDomain = extent1.j;
9940 event_({
9941 type: "brush",
9942 mode: "resize"
9943 });
9944 event_({
9945 type: "brushend"
9946 });
9947 });
9948 } else {
9949 event_({
9950 type: "brushstart"
9951 });
9952 event_({
9953 type: "brush",
9954 mode: "resize"
9955 });
9956 event_({
9957 type: "brushend"
9958 });
9959 }
9960 });
9961 };
9962 function redraw(g) {
9963 g.selectAll(".resize").attr("transform", function(d) {
9964 return "translate(" + xExtent[+/e$/.test(d)] + "," + yExtent[+/^s/.test(d)] + ")";
9965 });
9966 }
9967 function redrawX(g) {
9968 g.select(".extent").attr("x", xExtent[0]);
9969 g.selectAll(".extent,.n>rect,.s>rect").attr("width", xExtent[1] - xExtent[0]);
9970 }
9971 function redrawY(g) {
9972 g.select(".extent").attr("y", yExtent[0]);
9973 g.selectAll(".extent,.e>rect,.w>rect").attr("height", yExtent[1] - yExtent[0]);
9974 }
9975 function brushstart() {
9976 var target = this, eventTarget = d3.select(d3.event.target), event_ = event.of(target, arguments), g = d3.select(target), resizing = eventTarget.datum(), resizingX = !/^(n|s)$/.test(resizing) && x, resizingY = !/^(e|w)$/.test(resizing) && y, dragging = eventTarget.classed("extent"), dragRestore = d3_event_dragSuppress(target), center, origin = d3.mouse(target), offset;
9977 var w = d3.select(d3_window(target)).on("keydown.brush", keydown).on("keyup.brush", keyup);
9978 if (d3.event.changedTouches) {
9979 w.on("touchmove.brush", brushmove).on("touchend.brush", brushend);
9980 } else {
9981 w.on("mousemove.brush", brushmove).on("mouseup.brush", brushend);
9982 }
9983 g.interrupt().selectAll("*").interrupt();
9984 if (dragging) {
9985 origin[0] = xExtent[0] - origin[0];
9986 origin[1] = yExtent[0] - origin[1];
9987 } else if (resizing) {
9988 var ex = +/w$/.test(resizing), ey = +/^n/.test(resizing);
9989 offset = [ xExtent[1 - ex] - origin[0], yExtent[1 - ey] - origin[1] ];
9990 origin[0] = xExtent[ex];
9991 origin[1] = yExtent[ey];
9992 } else if (d3.event.altKey) center = origin.slice();
9993 g.style("pointer-events", "none").selectAll(".resize").style("display", null);
9994 d3.select("body").style("cursor", eventTarget.style("cursor"));
9995 event_({
9996 type: "brushstart"
9997 });
9998 brushmove();
9999 function keydown() {
10000 if (d3.event.keyCode == 32) {
10001 if (!dragging) {
10002 center = null;
10003 origin[0] -= xExtent[1];
10004 origin[1] -= yExtent[1];
10005 dragging = 2;
10006 }
10007 d3_eventPreventDefault();
10008 }
10009 }
10010 function keyup() {
10011 if (d3.event.keyCode == 32 && dragging == 2) {
10012 origin[0] += xExtent[1];
10013 origin[1] += yExtent[1];
10014 dragging = 0;
10015 d3_eventPreventDefault();
10016 }
10017 }
10018 function brushmove() {
10019 var point = d3.mouse(target), moved = false;
10020 if (offset) {
10021 point[0] += offset[0];
10022 point[1] += offset[1];
10023 }
10024 if (!dragging) {
10025 if (d3.event.altKey) {
10026 if (!center) center = [ (xExtent[0] + xExtent[1]) / 2, (yExtent[0] + yExtent[1]) / 2 ];
10027 origin[0] = xExtent[+(point[0] < center[0])];
10028 origin[1] = yExtent[+(point[1] < center[1])];
10029 } else center = null;
10030 }
10031 if (resizingX && move1(point, x, 0)) {
10032 redrawX(g);
10033 moved = true;
10034 }
10035 if (resizingY && move1(point, y, 1)) {
10036 redrawY(g);
10037 moved = true;
10038 }
10039 if (moved) {
10040 redraw(g);
10041 event_({
10042 type: "brush",
10043 mode: dragging ? "move" : "resize"
10044 });
10045 }
10046 }
10047 function move1(point, scale, i) {
10048 var range = d3_scaleRange(scale), r0 = range[0], r1 = range[1], position = origin[i], extent = i ? yExtent : xExtent, size = extent[1] - extent[0], min, max;
10049 if (dragging) {
10050 r0 -= position;
10051 r1 -= size + position;
10052 }
10053 min = (i ? yClamp : xClamp) ? Math.max(r0, Math.min(r1, point[i])) : point[i];
10054 if (dragging) {
10055 max = (min += position) + size;
10056 } else {
10057 if (center) position = Math.max(r0, Math.min(r1, 2 * center[i] - min));
10058 if (position < min) {
10059 max = min;
10060 min = position;
10061 } else {
10062 max = position;
10063 }
10064 }
10065 if (extent[0] != min || extent[1] != max) {
10066 if (i) yExtentDomain = null; else xExtentDomain = null;
10067 extent[0] = min;
10068 extent[1] = max;
10069 return true;
10070 }
10071 }
10072 function brushend() {
10073 brushmove();
10074 g.style("pointer-events", "all").selectAll(".resize").style("display", brush.empty() ? "none" : null);
10075 d3.select("body").style("cursor", null);
10076 w.on("mousemove.brush", null).on("mouseup.brush", null).on("touchmove.brush", null).on("touchend.brush", null).on("keydown.brush", null).on("keyup.brush", null);
10077 dragRestore();
10078 event_({
10079 type: "brushend"
10080 });
10081 }
10082 }
10083 brush.x = function(z) {
10084 if (!arguments.length) return x;
10085 x = z;
10086 resizes = d3_svg_brushResizes[!x << 1 | !y];
10087 return brush;
10088 };
10089 brush.y = function(z) {
10090 if (!arguments.length) return y;
10091 y = z;
10092 resizes = d3_svg_brushResizes[!x << 1 | !y];
10093 return brush;
10094 };
10095 brush.clamp = function(z) {
10096 if (!arguments.length) return x && y ? [ xClamp, yClamp ] : x ? xClamp : y ? yClamp : null;
10097 if (x && y) xClamp = !!z[0], yClamp = !!z[1]; else if (x) xClamp = !!z; else if (y) yClamp = !!z;
10098 return brush;
10099 };
10100 brush.extent = function(z) {
10101 var x0, x1, y0, y1, t;
10102 if (!arguments.length) {
10103 if (x) {
10104 if (xExtentDomain) {
10105 x0 = xExtentDomain[0], x1 = xExtentDomain[1];
10106 } else {
10107 x0 = xExtent[0], x1 = xExtent[1];
10108 if (x.invert) x0 = x.invert(x0), x1 = x.invert(x1);
10109 if (x1 < x0) t = x0, x0 = x1, x1 = t;
10110 }
10111 }
10112 if (y) {
10113 if (yExtentDomain) {
10114 y0 = yExtentDomain[0], y1 = yExtentDomain[1];
10115 } else {
10116 y0 = yExtent[0], y1 = yExtent[1];
10117 if (y.invert) y0 = y.invert(y0), y1 = y.invert(y1);
10118 if (y1 < y0) t = y0, y0 = y1, y1 = t;
10119 }
10120 }
10121 return x && y ? [ [ x0, y0 ], [ x1, y1 ] ] : x ? [ x0, x1 ] : y && [ y0, y1 ];
10122 }
10123 if (x) {
10124 x0 = z[0], x1 = z[1];
10125 if (y) x0 = x0[0], x1 = x1[0];
10126 xExtentDomain = [ x0, x1 ];
10127 if (x.invert) x0 = x(x0), x1 = x(x1);
10128 if (x1 < x0) t = x0, x0 = x1, x1 = t;
10129 if (x0 != xExtent[0] || x1 != xExtent[1]) xExtent = [ x0, x1 ];
10130 }
10131 if (y) {
10132 y0 = z[0], y1 = z[1];
10133 if (x) y0 = y0[1], y1 = y1[1];
10134 yExtentDomain = [ y0, y1 ];
10135 if (y.invert) y0 = y(y0), y1 = y(y1);
10136 if (y1 < y0) t = y0, y0 = y1, y1 = t;
10137 if (y0 != yExtent[0] || y1 != yExtent[1]) yExtent = [ y0, y1 ];
10138 }
10139 return brush;
10140 };
10141 brush.clear = function() {
10142 if (!brush.empty()) {
10143 xExtent = [ 0, 0 ], yExtent = [ 0, 0 ];
10144 xExtentDomain = yExtentDomain = null;
10145 }
10146 return brush;
10147 };
10148 brush.empty = function() {
10149 return !!x && xExtent[0] == xExtent[1] || !!y && yExtent[0] == yExtent[1];
10150 };
10151 return d3.rebind(brush, event, "on");
10152 };
10153 var d3_svg_brushCursor = {
10154 n: "ns-resize",
10155 e: "ew-resize",
10156 s: "ns-resize",
10157 w: "ew-resize",
10158 nw: "nwse-resize",
10159 ne: "nesw-resize",
10160 se: "nwse-resize",
10161 sw: "nesw-resize"
10162 };
10163 var d3_svg_brushResizes = [ [ "n", "e", "s", "w", "nw", "ne", "se", "sw" ], [ "e", "w" ], [ "n", "s" ], [] ];
10164 var d3_time_format = d3_time.format = d3_locale_enUS.timeFormat;
10165 var d3_time_formatUtc = d3_time_format.utc;
10166 var d3_time_formatIso = d3_time_formatUtc("%Y-%m-%dT%H:%M:%S.%LZ");
10167 d3_time_format.iso = Date.prototype.toISOString && +new Date("2000-01-01T00:00:00.000Z") ? d3_time_formatIsoNative : d3_time_formatIso;
10168 function d3_time_formatIsoNative(date) {
10169 return date.toISOString();
10170 }
10171 d3_time_formatIsoNative.parse = function(string) {
10172 var date = new Date(string);
10173 return isNaN(date) ? null : date;
10174 };
10175 d3_time_formatIsoNative.toString = d3_time_formatIso.toString;
10176 d3_time.second = d3_time_interval(function(date) {
10177 return new d3_date(Math.floor(date / 1e3) * 1e3);
10178 }, function(date, offset) {
10179 date.setTime(date.getTime() + Math.floor(offset) * 1e3);
10180 }, function(date) {
10181 return date.getSeconds();
10182 });
10183 d3_time.seconds = d3_time.second.range;
10184 d3_time.seconds.utc = d3_time.second.utc.range;
10185 d3_time.minute = d3_time_interval(function(date) {
10186 return new d3_date(Math.floor(date / 6e4) * 6e4);
10187 }, function(date, offset) {
10188 date.setTime(date.getTime() + Math.floor(offset) * 6e4);
10189 }, function(date) {
10190 return date.getMinutes();
10191 });
10192 d3_time.minutes = d3_time.minute.range;
10193 d3_time.minutes.utc = d3_time.minute.utc.range;
10194 d3_time.hour = d3_time_interval(function(date) {
10195 var timezone = date.getTimezoneOffset() / 60;
10196 return new d3_date((Math.floor(date / 36e5 - timezone) + timezone) * 36e5);
10197 }, function(date, offset) {
10198 date.setTime(date.getTime() + Math.floor(offset) * 36e5);
10199 }, function(date) {
10200 return date.getHours();
10201 });
10202 d3_time.hours = d3_time.hour.range;
10203 d3_time.hours.utc = d3_time.hour.utc.range;
10204 d3_time.month = d3_time_interval(function(date) {
10205 date = d3_time.day(date);
10206 date.setDate(1);
10207 return date;
10208 }, function(date, offset) {
10209 date.setMonth(date.getMonth() + offset);
10210 }, function(date) {
10211 return date.getMonth();
10212 });
10213 d3_time.months = d3_time.month.range;
10214 d3_time.months.utc = d3_time.month.utc.range;
10215 function d3_time_scale(linear, methods, format) {
10216 function scale(x) {
10217 return linear(x);
10218 }
10219 scale.invert = function(x) {
10220 return d3_time_scaleDate(linear.invert(x));
10221 };
10222 scale.domain = function(x) {
10223 if (!arguments.length) return linear.domain().map(d3_time_scaleDate);
10224 linear.domain(x);
10225 return scale;
10226 };
10227 function tickMethod(extent, count) {
10228 var span = extent[1] - extent[0], target = span / count, i = d3.bisect(d3_time_scaleSteps, target);
10229 return i == d3_time_scaleSteps.length ? [ methods.year, d3_scale_linearTickRange(extent.map(function(d) {
10230 return d / 31536e6;
10231 }), count)[2] ] : !i ? [ d3_time_scaleMilliseconds, d3_scale_linearTickRange(extent, count)[2] ] : methods[target / d3_time_scaleSteps[i - 1] < d3_time_scaleSteps[i] / target ? i - 1 : i];
10232 }
10233 scale.nice = function(interval, skip) {
10234 var domain = scale.domain(), extent = d3_scaleExtent(domain), method = interval == null ? tickMethod(extent, 10) : typeof interval === "number" && tickMethod(extent, interval);
10235 if (method) interval = method[0], skip = method[1];
10236 function skipped(date) {
10237 return !isNaN(date) && !interval.range(date, d3_time_scaleDate(+date + 1), skip).length;
10238 }
10239 return scale.domain(d3_scale_nice(domain, skip > 1 ? {
10240 floor: function(date) {
10241 while (skipped(date = interval.floor(date))) date = d3_time_scaleDate(date - 1);
10242 return date;
10243 },
10244 ceil: function(date) {
10245 while (skipped(date = interval.ceil(date))) date = d3_time_scaleDate(+date + 1);
10246 return date;
10247 }
10248 } : interval));
10249 };
10250 scale.ticks = function(interval, skip) {
10251 var extent = d3_scaleExtent(scale.domain()), method = interval == null ? tickMethod(extent, 10) : typeof interval === "number" ? tickMethod(extent, interval) : !interval.range && [ {
10252 range: interval
10253 }, skip ];
10254 if (method) interval = method[0], skip = method[1];
10255 return interval.range(extent[0], d3_time_scaleDate(+extent[1] + 1), skip < 1 ? 1 : skip);
10256 };
10257 scale.tickFormat = function() {
10258 return format;
10259 };
10260 scale.copy = function() {
10261 return d3_time_scale(linear.copy(), methods, format);
10262 };
10263 return d3_scale_linearRebind(scale, linear);
10264 }
10265 function d3_time_scaleDate(t) {
10266 return new Date(t);
10267 }
10268 var d3_time_scaleSteps = [ 1e3, 5e3, 15e3, 3e4, 6e4, 3e5, 9e5, 18e5, 36e5, 108e5, 216e5, 432e5, 864e5, 1728e5, 6048e5, 2592e6, 7776e6, 31536e6 ];
10269 var d3_time_scaleLocalMethods = [ [ d3_time.second, 1 ], [ d3_time.second, 5 ], [ d3_time.second, 15 ], [ d3_time.second, 30 ], [ d3_time.minute, 1 ], [ d3_time.minute, 5 ], [ d3_time.minute, 15 ], [ d3_time.minute, 30 ], [ d3_time.hour, 1 ], [ d3_time.hour, 3 ], [ d3_time.hour, 6 ], [ d3_time.hour, 12 ], [ d3_time.day, 1 ], [ d3_time.day, 2 ], [ d3_time.week, 1 ], [ d3_time.month, 1 ], [ d3_time.month, 3 ], [ d3_time.year, 1 ] ];
10270 var d3_time_scaleLocalFormat = d3_time_format.multi([ [ ".%L", function(d) {
10271 return d.getMilliseconds();
10272 } ], [ ":%S", function(d) {
10273 return d.getSeconds();
10274 } ], [ "%I:%M", function(d) {
10275 return d.getMinutes();
10276 } ], [ "%I %p", function(d) {
10277 return d.getHours();
10278 } ], [ "%a %d", function(d) {
10279 return d.getDay() && d.getDate() != 1;
10280 } ], [ "%b %d", function(d) {
10281 return d.getDate() != 1;
10282 } ], [ "%B", function(d) {
10283 return d.getMonth();
10284 } ], [ "%Y", d3_true ] ]);
10285 var d3_time_scaleMilliseconds = {
10286 range: function(start, stop, step) {
10287 return d3.range(Math.ceil(start / step) * step, +stop, step).map(d3_time_scaleDate);
10288 },
10289 floor: d3_identity,
10290 ceil: d3_identity
10291 };
10292 d3_time_scaleLocalMethods.year = d3_time.year;
10293 d3_time.scale = function() {
10294 return d3_time_scale(d3.scale.linear(), d3_time_scaleLocalMethods, d3_time_scaleLocalFormat);
10295 };
10296 var d3_time_scaleUtcMethods = d3_time_scaleLocalMethods.map(function(m) {
10297 return [ m[0].utc, m[1] ];
10298 });
10299 var d3_time_scaleUtcFormat = d3_time_formatUtc.multi([ [ ".%L", function(d) {
10300 return d.getUTCMilliseconds();
10301 } ], [ ":%S", function(d) {
10302 return d.getUTCSeconds();
10303 } ], [ "%I:%M", function(d) {
10304 return d.getUTCMinutes();
10305 } ], [ "%I %p", function(d) {
10306 return d.getUTCHours();
10307 } ], [ "%a %d", function(d) {
10308 return d.getUTCDay() && d.getUTCDate() != 1;
10309 } ], [ "%b %d", function(d) {
10310 return d.getUTCDate() != 1;
10311 } ], [ "%B", function(d) {
10312 return d.getUTCMonth();
10313 } ], [ "%Y", d3_true ] ]);
10314 d3_time_scaleUtcMethods.year = d3_time.year.utc;
10315 d3_time.scale.utc = function() {
10316 return d3_time_scale(d3.scale.linear(), d3_time_scaleUtcMethods, d3_time_scaleUtcFormat);
10317 };
10318 d3.text = d3_xhrType(function(request) {
10319 return request.responseText;
10320 });
10321 d3.json = function(url, callback) {
10322 return d3_xhr(url, "application/json", d3_json, callback);
10323 };
10324 function d3_json(request) {
10325 return JSON.parse(request.responseText);
10326 }
10327 d3.html = function(url, callback) {
10328 return d3_xhr(url, "text/html", d3_html, callback);
10329 };
10330 function d3_html(request) {
10331 var range = d3_document.createRange();
10332 range.selectNode(d3_document.body);
10333 return range.createContextualFragment(request.responseText);
10334 }
10335 d3.xml = d3_xhrType(function(request) {
10336 return request.responseXML;
10337 });
10338 if (typeof define === "function" && define.amd) this.d3 = d3, define(d3); else if (typeof module === "object" && module.exports) module.exports = d3; else this.d3 = d3;
10339}();
10340},{}],17:[function(_dereq_,module,exports){
10341(function (process,global){
10342/*!
10343 * @overview es6-promise - a tiny implementation of Promises/A+.
10344 * @copyright Copyright (c) 2014 Yehuda Katz, Tom Dale, Stefan Penner and contributors (Conversion to ES6 API by Jake Archibald)
10345 * @license Licensed under MIT license
10346 * See https://raw.githubusercontent.com/stefanpenner/es6-promise/master/LICENSE
10347 * @version v4.2.8+1e68dce6
10348 */
10349
10350(function (global, factory) {
10351 typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
10352 typeof define === 'function' && define.amd ? define(factory) :
10353 (global.ES6Promise = factory());
10354}(this, (function () { 'use strict';
10355
10356function objectOrFunction(x) {
10357 var type = typeof x;
10358 return x !== null && (type === 'object' || type === 'function');
10359}
10360
10361function isFunction(x) {
10362 return typeof x === 'function';
10363}
10364
10365
10366
10367var _isArray = void 0;
10368if (Array.isArray) {
10369 _isArray = Array.isArray;
10370} else {
10371 _isArray = function (x) {
10372 return Object.prototype.toString.call(x) === '[object Array]';
10373 };
10374}
10375
10376var isArray = _isArray;
10377
10378var len = 0;
10379var vertxNext = void 0;
10380var customSchedulerFn = void 0;
10381
10382var asap = function asap(callback, arg) {
10383 queue[len] = callback;
10384 queue[len + 1] = arg;
10385 len += 2;
10386 if (len === 2) {
10387 // If len is 2, that means that we need to schedule an async flush.
10388 // If additional callbacks are queued before the queue is flushed, they
10389 // will be processed by this flush that we are scheduling.
10390 if (customSchedulerFn) {
10391 customSchedulerFn(flush);
10392 } else {
10393 scheduleFlush();
10394 }
10395 }
10396};
10397
10398function setScheduler(scheduleFn) {
10399 customSchedulerFn = scheduleFn;
10400}
10401
10402function setAsap(asapFn) {
10403 asap = asapFn;
10404}
10405
10406var browserWindow = typeof window !== 'undefined' ? window : undefined;
10407var browserGlobal = browserWindow || {};
10408var BrowserMutationObserver = browserGlobal.MutationObserver || browserGlobal.WebKitMutationObserver;
10409var isNode = typeof self === 'undefined' && typeof process !== 'undefined' && {}.toString.call(process) === '[object process]';
10410
10411// test for web worker but not in IE10
10412var isWorker = typeof Uint8ClampedArray !== 'undefined' && typeof importScripts !== 'undefined' && typeof MessageChannel !== 'undefined';
10413
10414// node
10415function useNextTick() {
10416 // node version 0.10.x displays a deprecation warning when nextTick is used recursively
10417 // see https://github.com/cujojs/when/issues/410 for details
10418 return function () {
10419 return process.nextTick(flush);
10420 };
10421}
10422
10423// vertx
10424function useVertxTimer() {
10425 if (typeof vertxNext !== 'undefined') {
10426 return function () {
10427 vertxNext(flush);
10428 };
10429 }
10430
10431 return useSetTimeout();
10432}
10433
10434function useMutationObserver() {
10435 var iterations = 0;
10436 var observer = new BrowserMutationObserver(flush);
10437 var node = document.createTextNode('');
10438 observer.observe(node, { characterData: true });
10439
10440 return function () {
10441 node.data = iterations = ++iterations % 2;
10442 };
10443}
10444
10445// web worker
10446function useMessageChannel() {
10447 var channel = new MessageChannel();
10448 channel.port1.onmessage = flush;
10449 return function () {
10450 return channel.port2.postMessage(0);
10451 };
10452}
10453
10454function useSetTimeout() {
10455 // Store setTimeout reference so es6-promise will be unaffected by
10456 // other code modifying setTimeout (like sinon.useFakeTimers())
10457 var globalSetTimeout = setTimeout;
10458 return function () {
10459 return globalSetTimeout(flush, 1);
10460 };
10461}
10462
10463var queue = new Array(1000);
10464function flush() {
10465 for (var i = 0; i < len; i += 2) {
10466 var callback = queue[i];
10467 var arg = queue[i + 1];
10468
10469 callback(arg);
10470
10471 queue[i] = undefined;
10472 queue[i + 1] = undefined;
10473 }
10474
10475 len = 0;
10476}
10477
10478function attemptVertx() {
10479 try {
10480 var vertx = Function('return this')().require('vertx');
10481 vertxNext = vertx.runOnLoop || vertx.runOnContext;
10482 return useVertxTimer();
10483 } catch (e) {
10484 return useSetTimeout();
10485 }
10486}
10487
10488var scheduleFlush = void 0;
10489// Decide what async method to use to triggering processing of queued callbacks:
10490if (isNode) {
10491 scheduleFlush = useNextTick();
10492} else if (BrowserMutationObserver) {
10493 scheduleFlush = useMutationObserver();
10494} else if (isWorker) {
10495 scheduleFlush = useMessageChannel();
10496} else if (browserWindow === undefined && typeof _dereq_ === 'function') {
10497 scheduleFlush = attemptVertx();
10498} else {
10499 scheduleFlush = useSetTimeout();
10500}
10501
10502function then(onFulfillment, onRejection) {
10503 var parent = this;
10504
10505 var child = new this.constructor(noop);
10506
10507 if (child[PROMISE_ID] === undefined) {
10508 makePromise(child);
10509 }
10510
10511 var _state = parent._state;
10512
10513
10514 if (_state) {
10515 var callback = arguments[_state - 1];
10516 asap(function () {
10517 return invokeCallback(_state, child, callback, parent._result);
10518 });
10519 } else {
10520 subscribe(parent, child, onFulfillment, onRejection);
10521 }
10522
10523 return child;
10524}
10525
10526/**
10527 `Promise.resolve` returns a promise that will become resolved with the
10528 passed `value`. It is shorthand for the following:
10529
10530 ```javascript
10531 let promise = new Promise(function(resolve, reject){
10532 resolve(1);
10533 });
10534
10535 promise.then(function(value){
10536 // value === 1
10537 });
10538 ```
10539
10540 Instead of writing the above, your code now simply becomes the following:
10541
10542 ```javascript
10543 let promise = Promise.resolve(1);
10544
10545 promise.then(function(value){
10546 // value === 1
10547 });
10548 ```
10549
10550 @method resolve
10551 @static
10552 @param {Any} value value that the returned promise will be resolved with
10553 Useful for tooling.
10554 @return {Promise} a promise that will become fulfilled with the given
10555 `value`
10556*/
10557function resolve$1(object) {
10558 /*jshint validthis:true */
10559 var Constructor = this;
10560
10561 if (object && typeof object === 'object' && object.constructor === Constructor) {
10562 return object;
10563 }
10564
10565 var promise = new Constructor(noop);
10566 resolve(promise, object);
10567 return promise;
10568}
10569
10570var PROMISE_ID = Math.random().toString(36).substring(2);
10571
10572function noop() {}
10573
10574var PENDING = void 0;
10575var FULFILLED = 1;
10576var REJECTED = 2;
10577
10578function selfFulfillment() {
10579 return new TypeError("You cannot resolve a promise with itself");
10580}
10581
10582function cannotReturnOwn() {
10583 return new TypeError('A promises callback cannot return that same promise.');
10584}
10585
10586function tryThen(then$$1, value, fulfillmentHandler, rejectionHandler) {
10587 try {
10588 then$$1.call(value, fulfillmentHandler, rejectionHandler);
10589 } catch (e) {
10590 return e;
10591 }
10592}
10593
10594function handleForeignThenable(promise, thenable, then$$1) {
10595 asap(function (promise) {
10596 var sealed = false;
10597 var error = tryThen(then$$1, thenable, function (value) {
10598 if (sealed) {
10599 return;
10600 }
10601 sealed = true;
10602 if (thenable !== value) {
10603 resolve(promise, value);
10604 } else {
10605 fulfill(promise, value);
10606 }
10607 }, function (reason) {
10608 if (sealed) {
10609 return;
10610 }
10611 sealed = true;
10612
10613 reject(promise, reason);
10614 }, 'Settle: ' + (promise._label || ' unknown promise'));
10615
10616 if (!sealed && error) {
10617 sealed = true;
10618 reject(promise, error);
10619 }
10620 }, promise);
10621}
10622
10623function handleOwnThenable(promise, thenable) {
10624 if (thenable._state === FULFILLED) {
10625 fulfill(promise, thenable._result);
10626 } else if (thenable._state === REJECTED) {
10627 reject(promise, thenable._result);
10628 } else {
10629 subscribe(thenable, undefined, function (value) {
10630 return resolve(promise, value);
10631 }, function (reason) {
10632 return reject(promise, reason);
10633 });
10634 }
10635}
10636
10637function handleMaybeThenable(promise, maybeThenable, then$$1) {
10638 if (maybeThenable.constructor === promise.constructor && then$$1 === then && maybeThenable.constructor.resolve === resolve$1) {
10639 handleOwnThenable(promise, maybeThenable);
10640 } else {
10641 if (then$$1 === undefined) {
10642 fulfill(promise, maybeThenable);
10643 } else if (isFunction(then$$1)) {
10644 handleForeignThenable(promise, maybeThenable, then$$1);
10645 } else {
10646 fulfill(promise, maybeThenable);
10647 }
10648 }
10649}
10650
10651function resolve(promise, value) {
10652 if (promise === value) {
10653 reject(promise, selfFulfillment());
10654 } else if (objectOrFunction(value)) {
10655 var then$$1 = void 0;
10656 try {
10657 then$$1 = value.then;
10658 } catch (error) {
10659 reject(promise, error);
10660 return;
10661 }
10662 handleMaybeThenable(promise, value, then$$1);
10663 } else {
10664 fulfill(promise, value);
10665 }
10666}
10667
10668function publishRejection(promise) {
10669 if (promise._onerror) {
10670 promise._onerror(promise._result);
10671 }
10672
10673 publish(promise);
10674}
10675
10676function fulfill(promise, value) {
10677 if (promise._state !== PENDING) {
10678 return;
10679 }
10680
10681 promise._result = value;
10682 promise._state = FULFILLED;
10683
10684 if (promise._subscribers.length !== 0) {
10685 asap(publish, promise);
10686 }
10687}
10688
10689function reject(promise, reason) {
10690 if (promise._state !== PENDING) {
10691 return;
10692 }
10693 promise._state = REJECTED;
10694 promise._result = reason;
10695
10696 asap(publishRejection, promise);
10697}
10698
10699function subscribe(parent, child, onFulfillment, onRejection) {
10700 var _subscribers = parent._subscribers;
10701 var length = _subscribers.length;
10702
10703
10704 parent._onerror = null;
10705
10706 _subscribers[length] = child;
10707 _subscribers[length + FULFILLED] = onFulfillment;
10708 _subscribers[length + REJECTED] = onRejection;
10709
10710 if (length === 0 && parent._state) {
10711 asap(publish, parent);
10712 }
10713}
10714
10715function publish(promise) {
10716 var subscribers = promise._subscribers;
10717 var settled = promise._state;
10718
10719 if (subscribers.length === 0) {
10720 return;
10721 }
10722
10723 var child = void 0,
10724 callback = void 0,
10725 detail = promise._result;
10726
10727 for (var i = 0; i < subscribers.length; i += 3) {
10728 child = subscribers[i];
10729 callback = subscribers[i + settled];
10730
10731 if (child) {
10732 invokeCallback(settled, child, callback, detail);
10733 } else {
10734 callback(detail);
10735 }
10736 }
10737
10738 promise._subscribers.length = 0;
10739}
10740
10741function invokeCallback(settled, promise, callback, detail) {
10742 var hasCallback = isFunction(callback),
10743 value = void 0,
10744 error = void 0,
10745 succeeded = true;
10746
10747 if (hasCallback) {
10748 try {
10749 value = callback(detail);
10750 } catch (e) {
10751 succeeded = false;
10752 error = e;
10753 }
10754
10755 if (promise === value) {
10756 reject(promise, cannotReturnOwn());
10757 return;
10758 }
10759 } else {
10760 value = detail;
10761 }
10762
10763 if (promise._state !== PENDING) {
10764 // noop
10765 } else if (hasCallback && succeeded) {
10766 resolve(promise, value);
10767 } else if (succeeded === false) {
10768 reject(promise, error);
10769 } else if (settled === FULFILLED) {
10770 fulfill(promise, value);
10771 } else if (settled === REJECTED) {
10772 reject(promise, value);
10773 }
10774}
10775
10776function initializePromise(promise, resolver) {
10777 try {
10778 resolver(function resolvePromise(value) {
10779 resolve(promise, value);
10780 }, function rejectPromise(reason) {
10781 reject(promise, reason);
10782 });
10783 } catch (e) {
10784 reject(promise, e);
10785 }
10786}
10787
10788var id = 0;
10789function nextId() {
10790 return id++;
10791}
10792
10793function makePromise(promise) {
10794 promise[PROMISE_ID] = id++;
10795 promise._state = undefined;
10796 promise._result = undefined;
10797 promise._subscribers = [];
10798}
10799
10800function validationError() {
10801 return new Error('Array Methods must be provided an Array');
10802}
10803
10804var Enumerator = function () {
10805 function Enumerator(Constructor, input) {
10806 this._instanceConstructor = Constructor;
10807 this.promise = new Constructor(noop);
10808
10809 if (!this.promise[PROMISE_ID]) {
10810 makePromise(this.promise);
10811 }
10812
10813 if (isArray(input)) {
10814 this.length = input.length;
10815 this._remaining = input.length;
10816
10817 this._result = new Array(this.length);
10818
10819 if (this.length === 0) {
10820 fulfill(this.promise, this._result);
10821 } else {
10822 this.length = this.length || 0;
10823 this._enumerate(input);
10824 if (this._remaining === 0) {
10825 fulfill(this.promise, this._result);
10826 }
10827 }
10828 } else {
10829 reject(this.promise, validationError());
10830 }
10831 }
10832
10833 Enumerator.prototype._enumerate = function _enumerate(input) {
10834 for (var i = 0; this._state === PENDING && i < input.length; i++) {
10835 this._eachEntry(input[i], i);
10836 }
10837 };
10838
10839 Enumerator.prototype._eachEntry = function _eachEntry(entry, i) {
10840 var c = this._instanceConstructor;
10841 var resolve$$1 = c.resolve;
10842
10843
10844 if (resolve$$1 === resolve$1) {
10845 var _then = void 0;
10846 var error = void 0;
10847 var didError = false;
10848 try {
10849 _then = entry.then;
10850 } catch (e) {
10851 didError = true;
10852 error = e;
10853 }
10854
10855 if (_then === then && entry._state !== PENDING) {
10856 this._settledAt(entry._state, i, entry._result);
10857 } else if (typeof _then !== 'function') {
10858 this._remaining--;
10859 this._result[i] = entry;
10860 } else if (c === Promise$1) {
10861 var promise = new c(noop);
10862 if (didError) {
10863 reject(promise, error);
10864 } else {
10865 handleMaybeThenable(promise, entry, _then);
10866 }
10867 this._willSettleAt(promise, i);
10868 } else {
10869 this._willSettleAt(new c(function (resolve$$1) {
10870 return resolve$$1(entry);
10871 }), i);
10872 }
10873 } else {
10874 this._willSettleAt(resolve$$1(entry), i);
10875 }
10876 };
10877
10878 Enumerator.prototype._settledAt = function _settledAt(state, i, value) {
10879 var promise = this.promise;
10880
10881
10882 if (promise._state === PENDING) {
10883 this._remaining--;
10884
10885 if (state === REJECTED) {
10886 reject(promise, value);
10887 } else {
10888 this._result[i] = value;
10889 }
10890 }
10891
10892 if (this._remaining === 0) {
10893 fulfill(promise, this._result);
10894 }
10895 };
10896
10897 Enumerator.prototype._willSettleAt = function _willSettleAt(promise, i) {
10898 var enumerator = this;
10899
10900 subscribe(promise, undefined, function (value) {
10901 return enumerator._settledAt(FULFILLED, i, value);
10902 }, function (reason) {
10903 return enumerator._settledAt(REJECTED, i, reason);
10904 });
10905 };
10906
10907 return Enumerator;
10908}();
10909
10910/**
10911 `Promise.all` accepts an array of promises, and returns a new promise which
10912 is fulfilled with an array of fulfillment values for the passed promises, or
10913 rejected with the reason of the first passed promise to be rejected. It casts all
10914 elements of the passed iterable to promises as it runs this algorithm.
10915
10916 Example:
10917
10918 ```javascript
10919 let promise1 = resolve(1);
10920 let promise2 = resolve(2);
10921 let promise3 = resolve(3);
10922 let promises = [ promise1, promise2, promise3 ];
10923
10924 Promise.all(promises).then(function(array){
10925 // The array here would be [ 1, 2, 3 ];
10926 });
10927 ```
10928
10929 If any of the `promises` given to `all` are rejected, the first promise
10930 that is rejected will be given as an argument to the returned promises's
10931 rejection handler. For example:
10932
10933 Example:
10934
10935 ```javascript
10936 let promise1 = resolve(1);
10937 let promise2 = reject(new Error("2"));
10938 let promise3 = reject(new Error("3"));
10939 let promises = [ promise1, promise2, promise3 ];
10940
10941 Promise.all(promises).then(function(array){
10942 // Code here never runs because there are rejected promises!
10943 }, function(error) {
10944 // error.message === "2"
10945 });
10946 ```
10947
10948 @method all
10949 @static
10950 @param {Array} entries array of promises
10951 @param {String} label optional string for labeling the promise.
10952 Useful for tooling.
10953 @return {Promise} promise that is fulfilled when all `promises` have been
10954 fulfilled, or rejected if any of them become rejected.
10955 @static
10956*/
10957function all(entries) {
10958 return new Enumerator(this, entries).promise;
10959}
10960
10961/**
10962 `Promise.race` returns a new promise which is settled in the same way as the
10963 first passed promise to settle.
10964
10965 Example:
10966
10967 ```javascript
10968 let promise1 = new Promise(function(resolve, reject){
10969 setTimeout(function(){
10970 resolve('promise 1');
10971 }, 200);
10972 });
10973
10974 let promise2 = new Promise(function(resolve, reject){
10975 setTimeout(function(){
10976 resolve('promise 2');
10977 }, 100);
10978 });
10979
10980 Promise.race([promise1, promise2]).then(function(result){
10981 // result === 'promise 2' because it was resolved before promise1
10982 // was resolved.
10983 });
10984 ```
10985
10986 `Promise.race` is deterministic in that only the state of the first
10987 settled promise matters. For example, even if other promises given to the
10988 `promises` array argument are resolved, but the first settled promise has
10989 become rejected before the other promises became fulfilled, the returned
10990 promise will become rejected:
10991
10992 ```javascript
10993 let promise1 = new Promise(function(resolve, reject){
10994 setTimeout(function(){
10995 resolve('promise 1');
10996 }, 200);
10997 });
10998
10999 let promise2 = new Promise(function(resolve, reject){
11000 setTimeout(function(){
11001 reject(new Error('promise 2'));
11002 }, 100);
11003 });
11004
11005 Promise.race([promise1, promise2]).then(function(result){
11006 // Code here never runs
11007 }, function(reason){
11008 // reason.message === 'promise 2' because promise 2 became rejected before
11009 // promise 1 became fulfilled
11010 });
11011 ```
11012
11013 An example real-world use case is implementing timeouts:
11014
11015 ```javascript
11016 Promise.race([ajax('foo.json'), timeout(5000)])
11017 ```
11018
11019 @method race
11020 @static
11021 @param {Array} promises array of promises to observe
11022 Useful for tooling.
11023 @return {Promise} a promise which settles in the same way as the first passed
11024 promise to settle.
11025*/
11026function race(entries) {
11027 /*jshint validthis:true */
11028 var Constructor = this;
11029
11030 if (!isArray(entries)) {
11031 return new Constructor(function (_, reject) {
11032 return reject(new TypeError('You must pass an array to race.'));
11033 });
11034 } else {
11035 return new Constructor(function (resolve, reject) {
11036 var length = entries.length;
11037 for (var i = 0; i < length; i++) {
11038 Constructor.resolve(entries[i]).then(resolve, reject);
11039 }
11040 });
11041 }
11042}
11043
11044/**
11045 `Promise.reject` returns a promise rejected with the passed `reason`.
11046 It is shorthand for the following:
11047
11048 ```javascript
11049 let promise = new Promise(function(resolve, reject){
11050 reject(new Error('WHOOPS'));
11051 });
11052
11053 promise.then(function(value){
11054 // Code here doesn't run because the promise is rejected!
11055 }, function(reason){
11056 // reason.message === 'WHOOPS'
11057 });
11058 ```
11059
11060 Instead of writing the above, your code now simply becomes the following:
11061
11062 ```javascript
11063 let promise = Promise.reject(new Error('WHOOPS'));
11064
11065 promise.then(function(value){
11066 // Code here doesn't run because the promise is rejected!
11067 }, function(reason){
11068 // reason.message === 'WHOOPS'
11069 });
11070 ```
11071
11072 @method reject
11073 @static
11074 @param {Any} reason value that the returned promise will be rejected with.
11075 Useful for tooling.
11076 @return {Promise} a promise rejected with the given `reason`.
11077*/
11078function reject$1(reason) {
11079 /*jshint validthis:true */
11080 var Constructor = this;
11081 var promise = new Constructor(noop);
11082 reject(promise, reason);
11083 return promise;
11084}
11085
11086function needsResolver() {
11087 throw new TypeError('You must pass a resolver function as the first argument to the promise constructor');
11088}
11089
11090function needsNew() {
11091 throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function.");
11092}
11093
11094/**
11095 Promise objects represent the eventual result of an asynchronous operation. The
11096 primary way of interacting with a promise is through its `then` method, which
11097 registers callbacks to receive either a promise's eventual value or the reason
11098 why the promise cannot be fulfilled.
11099
11100 Terminology
11101 -----------
11102
11103 - `promise` is an object or function with a `then` method whose behavior conforms to this specification.
11104 - `thenable` is an object or function that defines a `then` method.
11105 - `value` is any legal JavaScript value (including undefined, a thenable, or a promise).
11106 - `exception` is a value that is thrown using the throw statement.
11107 - `reason` is a value that indicates why a promise was rejected.
11108 - `settled` the final resting state of a promise, fulfilled or rejected.
11109
11110 A promise can be in one of three states: pending, fulfilled, or rejected.
11111
11112 Promises that are fulfilled have a fulfillment value and are in the fulfilled
11113 state. Promises that are rejected have a rejection reason and are in the
11114 rejected state. A fulfillment value is never a thenable.
11115
11116 Promises can also be said to *resolve* a value. If this value is also a
11117 promise, then the original promise's settled state will match the value's
11118 settled state. So a promise that *resolves* a promise that rejects will
11119 itself reject, and a promise that *resolves* a promise that fulfills will
11120 itself fulfill.
11121
11122
11123 Basic Usage:
11124 ------------
11125
11126 ```js
11127 let promise = new Promise(function(resolve, reject) {
11128 // on success
11129 resolve(value);
11130
11131 // on failure
11132 reject(reason);
11133 });
11134
11135 promise.then(function(value) {
11136 // on fulfillment
11137 }, function(reason) {
11138 // on rejection
11139 });
11140 ```
11141
11142 Advanced Usage:
11143 ---------------
11144
11145 Promises shine when abstracting away asynchronous interactions such as
11146 `XMLHttpRequest`s.
11147
11148 ```js
11149 function getJSON(url) {
11150 return new Promise(function(resolve, reject){
11151 let xhr = new XMLHttpRequest();
11152
11153 xhr.open('GET', url);
11154 xhr.onreadystatechange = handler;
11155 xhr.responseType = 'json';
11156 xhr.setRequestHeader('Accept', 'application/json');
11157 xhr.send();
11158
11159 function handler() {
11160 if (this.readyState === this.DONE) {
11161 if (this.status === 200) {
11162 resolve(this.response);
11163 } else {
11164 reject(new Error('getJSON: `' + url + '` failed with status: [' + this.status + ']'));
11165 }
11166 }
11167 };
11168 });
11169 }
11170
11171 getJSON('/posts.json').then(function(json) {
11172 // on fulfillment
11173 }, function(reason) {
11174 // on rejection
11175 });
11176 ```
11177
11178 Unlike callbacks, promises are great composable primitives.
11179
11180 ```js
11181 Promise.all([
11182 getJSON('/posts'),
11183 getJSON('/comments')
11184 ]).then(function(values){
11185 values[0] // => postsJSON
11186 values[1] // => commentsJSON
11187
11188 return values;
11189 });
11190 ```
11191
11192 @class Promise
11193 @param {Function} resolver
11194 Useful for tooling.
11195 @constructor
11196*/
11197
11198var Promise$1 = function () {
11199 function Promise(resolver) {
11200 this[PROMISE_ID] = nextId();
11201 this._result = this._state = undefined;
11202 this._subscribers = [];
11203
11204 if (noop !== resolver) {
11205 typeof resolver !== 'function' && needsResolver();
11206 this instanceof Promise ? initializePromise(this, resolver) : needsNew();
11207 }
11208 }
11209
11210 /**
11211 The primary way of interacting with a promise is through its `then` method,
11212 which registers callbacks to receive either a promise's eventual value or the
11213 reason why the promise cannot be fulfilled.
11214 ```js
11215 findUser().then(function(user){
11216 // user is available
11217 }, function(reason){
11218 // user is unavailable, and you are given the reason why
11219 });
11220 ```
11221 Chaining
11222 --------
11223 The return value of `then` is itself a promise. This second, 'downstream'
11224 promise is resolved with the return value of the first promise's fulfillment
11225 or rejection handler, or rejected if the handler throws an exception.
11226 ```js
11227 findUser().then(function (user) {
11228 return user.name;
11229 }, function (reason) {
11230 return 'default name';
11231 }).then(function (userName) {
11232 // If `findUser` fulfilled, `userName` will be the user's name, otherwise it
11233 // will be `'default name'`
11234 });
11235 findUser().then(function (user) {
11236 throw new Error('Found user, but still unhappy');
11237 }, function (reason) {
11238 throw new Error('`findUser` rejected and we're unhappy');
11239 }).then(function (value) {
11240 // never reached
11241 }, function (reason) {
11242 // if `findUser` fulfilled, `reason` will be 'Found user, but still unhappy'.
11243 // If `findUser` rejected, `reason` will be '`findUser` rejected and we're unhappy'.
11244 });
11245 ```
11246 If the downstream promise does not specify a rejection handler, rejection reasons will be propagated further downstream.
11247 ```js
11248 findUser().then(function (user) {
11249 throw new PedagogicalException('Upstream error');
11250 }).then(function (value) {
11251 // never reached
11252 }).then(function (value) {
11253 // never reached
11254 }, function (reason) {
11255 // The `PedgagocialException` is propagated all the way down to here
11256 });
11257 ```
11258 Assimilation
11259 ------------
11260 Sometimes the value you want to propagate to a downstream promise can only be
11261 retrieved asynchronously. This can be achieved by returning a promise in the
11262 fulfillment or rejection handler. The downstream promise will then be pending
11263 until the returned promise is settled. This is called *assimilation*.
11264 ```js
11265 findUser().then(function (user) {
11266 return findCommentsByAuthor(user);
11267 }).then(function (comments) {
11268 // The user's comments are now available
11269 });
11270 ```
11271 If the assimliated promise rejects, then the downstream promise will also reject.
11272 ```js
11273 findUser().then(function (user) {
11274 return findCommentsByAuthor(user);
11275 }).then(function (comments) {
11276 // If `findCommentsByAuthor` fulfills, we'll have the value here
11277 }, function (reason) {
11278 // If `findCommentsByAuthor` rejects, we'll have the reason here
11279 });
11280 ```
11281 Simple Example
11282 --------------
11283 Synchronous Example
11284 ```javascript
11285 let result;
11286 try {
11287 result = findResult();
11288 // success
11289 } catch(reason) {
11290 // failure
11291 }
11292 ```
11293 Errback Example
11294 ```js
11295 findResult(function(result, err){
11296 if (err) {
11297 // failure
11298 } else {
11299 // success
11300 }
11301 });
11302 ```
11303 Promise Example;
11304 ```javascript
11305 findResult().then(function(result){
11306 // success
11307 }, function(reason){
11308 // failure
11309 });
11310 ```
11311 Advanced Example
11312 --------------
11313 Synchronous Example
11314 ```javascript
11315 let author, books;
11316 try {
11317 author = findAuthor();
11318 books = findBooksByAuthor(author);
11319 // success
11320 } catch(reason) {
11321 // failure
11322 }
11323 ```
11324 Errback Example
11325 ```js
11326 function foundBooks(books) {
11327 }
11328 function failure(reason) {
11329 }
11330 findAuthor(function(author, err){
11331 if (err) {
11332 failure(err);
11333 // failure
11334 } else {
11335 try {
11336 findBoooksByAuthor(author, function(books, err) {
11337 if (err) {
11338 failure(err);
11339 } else {
11340 try {
11341 foundBooks(books);
11342 } catch(reason) {
11343 failure(reason);
11344 }
11345 }
11346 });
11347 } catch(error) {
11348 failure(err);
11349 }
11350 // success
11351 }
11352 });
11353 ```
11354 Promise Example;
11355 ```javascript
11356 findAuthor().
11357 then(findBooksByAuthor).
11358 then(function(books){
11359 // found books
11360 }).catch(function(reason){
11361 // something went wrong
11362 });
11363 ```
11364 @method then
11365 @param {Function} onFulfilled
11366 @param {Function} onRejected
11367 Useful for tooling.
11368 @return {Promise}
11369 */
11370
11371 /**
11372 `catch` is simply sugar for `then(undefined, onRejection)` which makes it the same
11373 as the catch block of a try/catch statement.
11374 ```js
11375 function findAuthor(){
11376 throw new Error('couldn't find that author');
11377 }
11378 // synchronous
11379 try {
11380 findAuthor();
11381 } catch(reason) {
11382 // something went wrong
11383 }
11384 // async with promises
11385 findAuthor().catch(function(reason){
11386 // something went wrong
11387 });
11388 ```
11389 @method catch
11390 @param {Function} onRejection
11391 Useful for tooling.
11392 @return {Promise}
11393 */
11394
11395
11396 Promise.prototype.catch = function _catch(onRejection) {
11397 return this.then(null, onRejection);
11398 };
11399
11400 /**
11401 `finally` will be invoked regardless of the promise's fate just as native
11402 try/catch/finally behaves
11403
11404 Synchronous example:
11405
11406 ```js
11407 findAuthor() {
11408 if (Math.random() > 0.5) {
11409 throw new Error();
11410 }
11411 return new Author();
11412 }
11413
11414 try {
11415 return findAuthor(); // succeed or fail
11416 } catch(error) {
11417 return findOtherAuther();
11418 } finally {
11419 // always runs
11420 // doesn't affect the return value
11421 }
11422 ```
11423
11424 Asynchronous example:
11425
11426 ```js
11427 findAuthor().catch(function(reason){
11428 return findOtherAuther();
11429 }).finally(function(){
11430 // author was either found, or not
11431 });
11432 ```
11433
11434 @method finally
11435 @param {Function} callback
11436 @return {Promise}
11437 */
11438
11439
11440 Promise.prototype.finally = function _finally(callback) {
11441 var promise = this;
11442 var constructor = promise.constructor;
11443
11444 if (isFunction(callback)) {
11445 return promise.then(function (value) {
11446 return constructor.resolve(callback()).then(function () {
11447 return value;
11448 });
11449 }, function (reason) {
11450 return constructor.resolve(callback()).then(function () {
11451 throw reason;
11452 });
11453 });
11454 }
11455
11456 return promise.then(callback, callback);
11457 };
11458
11459 return Promise;
11460}();
11461
11462Promise$1.prototype.then = then;
11463Promise$1.all = all;
11464Promise$1.race = race;
11465Promise$1.resolve = resolve$1;
11466Promise$1.reject = reject$1;
11467Promise$1._setScheduler = setScheduler;
11468Promise$1._setAsap = setAsap;
11469Promise$1._asap = asap;
11470
11471/*global self*/
11472function polyfill() {
11473 var local = void 0;
11474
11475 if (typeof global !== 'undefined') {
11476 local = global;
11477 } else if (typeof self !== 'undefined') {
11478 local = self;
11479 } else {
11480 try {
11481 local = Function('return this')();
11482 } catch (e) {
11483 throw new Error('polyfill failed because global object is unavailable in this environment');
11484 }
11485 }
11486
11487 var P = local.Promise;
11488
11489 if (P) {
11490 var promiseToString = null;
11491 try {
11492 promiseToString = Object.prototype.toString.call(P.resolve());
11493 } catch (e) {
11494 // silently ignored
11495 }
11496
11497 if (promiseToString === '[object Promise]' && !P.cast) {
11498 return;
11499 }
11500 }
11501
11502 local.Promise = Promise$1;
11503}
11504
11505// Strange compat..
11506Promise$1.polyfill = polyfill;
11507Promise$1.Promise = Promise$1;
11508
11509return Promise$1;
11510
11511})));
11512
11513
11514
11515
11516
11517}).call(this,_dereq_('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
11518},{"_process":34}],18:[function(_dereq_,module,exports){
11519/**
11520 * inspired by is-number <https://github.com/jonschlinkert/is-number>
11521 * but significantly simplified and sped up by ignoring number and string constructors
11522 * ie these return false:
11523 * new Number(1)
11524 * new String('1')
11525 */
11526
11527'use strict';
11528
11529var allBlankCharCodes = _dereq_('is-string-blank');
11530
11531module.exports = function(n) {
11532 var type = typeof n;
11533 if(type === 'string') {
11534 var original = n;
11535 n = +n;
11536 // whitespace strings cast to zero - filter them out
11537 if(n===0 && allBlankCharCodes(original)) return false;
11538 }
11539 else if(type !== 'number') return false;
11540
11541 return n - n < 1;
11542};
11543
11544},{"is-string-blank":23}],19:[function(_dereq_,module,exports){
11545module.exports = fromQuat;
11546
11547/**
11548 * Creates a matrix from a quaternion rotation.
11549 *
11550 * @param {mat4} out mat4 receiving operation result
11551 * @param {quat4} q Rotation quaternion
11552 * @returns {mat4} out
11553 */
11554function fromQuat(out, q) {
11555 var x = q[0], y = q[1], z = q[2], w = q[3],
11556 x2 = x + x,
11557 y2 = y + y,
11558 z2 = z + z,
11559
11560 xx = x * x2,
11561 yx = y * x2,
11562 yy = y * y2,
11563 zx = z * x2,
11564 zy = z * y2,
11565 zz = z * z2,
11566 wx = w * x2,
11567 wy = w * y2,
11568 wz = w * z2;
11569
11570 out[0] = 1 - yy - zz;
11571 out[1] = yx + wz;
11572 out[2] = zx - wy;
11573 out[3] = 0;
11574
11575 out[4] = yx - wz;
11576 out[5] = 1 - xx - zz;
11577 out[6] = zy + wx;
11578 out[7] = 0;
11579
11580 out[8] = zx + wy;
11581 out[9] = zy - wx;
11582 out[10] = 1 - xx - yy;
11583 out[11] = 0;
11584
11585 out[12] = 0;
11586 out[13] = 0;
11587 out[14] = 0;
11588 out[15] = 1;
11589
11590 return out;
11591};
11592},{}],20:[function(_dereq_,module,exports){
11593(function (global){
11594'use strict'
11595
11596var isBrowser = _dereq_('is-browser')
11597var hasHover
11598
11599if (typeof global.matchMedia === 'function') {
11600 hasHover = !global.matchMedia('(hover: none)').matches
11601}
11602else {
11603 hasHover = isBrowser
11604}
11605
11606module.exports = hasHover
11607
11608}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
11609},{"is-browser":22}],21:[function(_dereq_,module,exports){
11610'use strict'
11611
11612var isBrowser = _dereq_('is-browser')
11613
11614function detect() {
11615 var supported = false
11616
11617 try {
11618 var opts = Object.defineProperty({}, 'passive', {
11619 get: function() {
11620 supported = true
11621 }
11622 })
11623
11624 window.addEventListener('test', null, opts)
11625 window.removeEventListener('test', null, opts)
11626 } catch(e) {
11627 supported = false
11628 }
11629
11630 return supported
11631}
11632
11633module.exports = isBrowser && detect()
11634
11635},{"is-browser":22}],22:[function(_dereq_,module,exports){
11636module.exports = true;
11637},{}],23:[function(_dereq_,module,exports){
11638'use strict';
11639
11640/**
11641 * Is this string all whitespace?
11642 * This solution kind of makes my brain hurt, but it's significantly faster
11643 * than !str.trim() or any other solution I could find.
11644 *
11645 * whitespace codes from: http://en.wikipedia.org/wiki/Whitespace_character
11646 * and verified with:
11647 *
11648 * for(var i = 0; i < 65536; i++) {
11649 * var s = String.fromCharCode(i);
11650 * if(+s===0 && !s.trim()) console.log(i, s);
11651 * }
11652 *
11653 * which counts a couple of these as *not* whitespace, but finds nothing else
11654 * that *is* whitespace. Note that charCodeAt stops at 16 bits, but it appears
11655 * that there are no whitespace characters above this, and code points above
11656 * this do not map onto white space characters.
11657 */
11658
11659module.exports = function(str){
11660 var l = str.length,
11661 a;
11662 for(var i = 0; i < l; i++) {
11663 a = str.charCodeAt(i);
11664 if((a < 9 || a > 13) && (a !== 32) && (a !== 133) && (a !== 160) &&
11665 (a !== 5760) && (a !== 6158) && (a < 8192 || a > 8205) &&
11666 (a !== 8232) && (a !== 8233) && (a !== 8239) && (a !== 8287) &&
11667 (a !== 8288) && (a !== 12288) && (a !== 65279)) {
11668 return false;
11669 }
11670 }
11671 return true;
11672}
11673
11674},{}],24:[function(_dereq_,module,exports){
11675var rootPosition = { left: 0, top: 0 }
11676
11677module.exports = mouseEventOffset
11678function mouseEventOffset (ev, target, out) {
11679 target = target || ev.currentTarget || ev.srcElement
11680 if (!Array.isArray(out)) {
11681 out = [ 0, 0 ]
11682 }
11683 var cx = ev.clientX || 0
11684 var cy = ev.clientY || 0
11685 var rect = getBoundingClientOffset(target)
11686 out[0] = cx - rect.left
11687 out[1] = cy - rect.top
11688 return out
11689}
11690
11691function getBoundingClientOffset (element) {
11692 if (element === window ||
11693 element === document ||
11694 element === document.body) {
11695 return rootPosition
11696 } else {
11697 return element.getBoundingClientRect()
11698 }
11699}
11700
11701},{}],25:[function(_dereq_,module,exports){
11702
11703module.exports = parse
11704
11705/**
11706 * expected argument lengths
11707 * @type {Object}
11708 */
11709
11710var length = {a: 7, c: 6, h: 1, l: 2, m: 2, q: 4, s: 4, t: 2, v: 1, z: 0}
11711
11712/**
11713 * segment pattern
11714 * @type {RegExp}
11715 */
11716
11717var segment = /([astvzqmhlc])([^astvzqmhlc]*)/ig
11718
11719/**
11720 * parse an svg path data string. Generates an Array
11721 * of commands where each command is an Array of the
11722 * form `[command, arg1, arg2, ...]`
11723 *
11724 * @param {String} path
11725 * @return {Array}
11726 */
11727
11728function parse(path) {
11729 var data = []
11730 path.replace(segment, function(_, command, args){
11731 var type = command.toLowerCase()
11732 args = parseValues(args)
11733
11734 // overloaded moveTo
11735 if (type == 'm' && args.length > 2) {
11736 data.push([command].concat(args.splice(0, 2)))
11737 type = 'l'
11738 command = command == 'm' ? 'l' : 'L'
11739 }
11740
11741 while (true) {
11742 if (args.length == length[type]) {
11743 args.unshift(command)
11744 return data.push(args)
11745 }
11746 if (args.length < length[type]) throw new Error('malformed path data')
11747 data.push([command].concat(args.splice(0, length[type])))
11748 }
11749 })
11750 return data
11751}
11752
11753var number = /-?[0-9]*\.?[0-9]+(?:e[-+]?\d+)?/ig
11754
11755function parseValues(args) {
11756 var numbers = args.match(number)
11757 return numbers ? numbers.map(Number) : []
11758}
11759
11760},{}],26:[function(_dereq_,module,exports){
11761/*
11762 * @copyright 2016 Sean Connelly (@voidqk), http://syntheti.cc
11763 * @license MIT
11764 * @preserve Project Home: https://github.com/voidqk/polybooljs
11765 */
11766
11767var BuildLog = _dereq_('./lib/build-log');
11768var Epsilon = _dereq_('./lib/epsilon');
11769var Intersecter = _dereq_('./lib/intersecter');
11770var SegmentChainer = _dereq_('./lib/segment-chainer');
11771var SegmentSelector = _dereq_('./lib/segment-selector');
11772var GeoJSON = _dereq_('./lib/geojson');
11773
11774var buildLog = false;
11775var epsilon = Epsilon();
11776
11777var PolyBool;
11778PolyBool = {
11779 // getter/setter for buildLog
11780 buildLog: function(bl){
11781 if (bl === true)
11782 buildLog = BuildLog();
11783 else if (bl === false)
11784 buildLog = false;
11785 return buildLog === false ? false : buildLog.list;
11786 },
11787 // getter/setter for epsilon
11788 epsilon: function(v){
11789 return epsilon.epsilon(v);
11790 },
11791
11792 // core API
11793 segments: function(poly){
11794 var i = Intersecter(true, epsilon, buildLog);
11795 poly.regions.forEach(i.addRegion);
11796 return {
11797 segments: i.calculate(poly.inverted),
11798 inverted: poly.inverted
11799 };
11800 },
11801 combine: function(segments1, segments2){
11802 var i3 = Intersecter(false, epsilon, buildLog);
11803 return {
11804 combined: i3.calculate(
11805 segments1.segments, segments1.inverted,
11806 segments2.segments, segments2.inverted
11807 ),
11808 inverted1: segments1.inverted,
11809 inverted2: segments2.inverted
11810 };
11811 },
11812 selectUnion: function(combined){
11813 return {
11814 segments: SegmentSelector.union(combined.combined, buildLog),
11815 inverted: combined.inverted1 || combined.inverted2
11816 }
11817 },
11818 selectIntersect: function(combined){
11819 return {
11820 segments: SegmentSelector.intersect(combined.combined, buildLog),
11821 inverted: combined.inverted1 && combined.inverted2
11822 }
11823 },
11824 selectDifference: function(combined){
11825 return {
11826 segments: SegmentSelector.difference(combined.combined, buildLog),
11827 inverted: combined.inverted1 && !combined.inverted2
11828 }
11829 },
11830 selectDifferenceRev: function(combined){
11831 return {
11832 segments: SegmentSelector.differenceRev(combined.combined, buildLog),
11833 inverted: !combined.inverted1 && combined.inverted2
11834 }
11835 },
11836 selectXor: function(combined){
11837 return {
11838 segments: SegmentSelector.xor(combined.combined, buildLog),
11839 inverted: combined.inverted1 !== combined.inverted2
11840 }
11841 },
11842 polygon: function(segments){
11843 return {
11844 regions: SegmentChainer(segments.segments, epsilon, buildLog),
11845 inverted: segments.inverted
11846 };
11847 },
11848
11849 // GeoJSON converters
11850 polygonFromGeoJSON: function(geojson){
11851 return GeoJSON.toPolygon(PolyBool, geojson);
11852 },
11853 polygonToGeoJSON: function(poly){
11854 return GeoJSON.fromPolygon(PolyBool, epsilon, poly);
11855 },
11856
11857 // helper functions for common operations
11858 union: function(poly1, poly2){
11859 return operate(poly1, poly2, PolyBool.selectUnion);
11860 },
11861 intersect: function(poly1, poly2){
11862 return operate(poly1, poly2, PolyBool.selectIntersect);
11863 },
11864 difference: function(poly1, poly2){
11865 return operate(poly1, poly2, PolyBool.selectDifference);
11866 },
11867 differenceRev: function(poly1, poly2){
11868 return operate(poly1, poly2, PolyBool.selectDifferenceRev);
11869 },
11870 xor: function(poly1, poly2){
11871 return operate(poly1, poly2, PolyBool.selectXor);
11872 }
11873};
11874
11875function operate(poly1, poly2, selector){
11876 var seg1 = PolyBool.segments(poly1);
11877 var seg2 = PolyBool.segments(poly2);
11878 var comb = PolyBool.combine(seg1, seg2);
11879 var seg3 = selector(comb);
11880 return PolyBool.polygon(seg3);
11881}
11882
11883if (typeof window === 'object')
11884 window.PolyBool = PolyBool;
11885
11886module.exports = PolyBool;
11887
11888},{"./lib/build-log":27,"./lib/epsilon":28,"./lib/geojson":29,"./lib/intersecter":30,"./lib/segment-chainer":32,"./lib/segment-selector":33}],27:[function(_dereq_,module,exports){
11889// (c) Copyright 2016, Sean Connelly (@voidqk), http://syntheti.cc
11890// MIT License
11891// Project Home: https://github.com/voidqk/polybooljs
11892
11893//
11894// used strictly for logging the processing of the algorithm... only useful if you intend on
11895// looking under the covers (for pretty UI's or debugging)
11896//
11897
11898function BuildLog(){
11899 var my;
11900 var nextSegmentId = 0;
11901 var curVert = false;
11902
11903 function push(type, data){
11904 my.list.push({
11905 type: type,
11906 data: data ? JSON.parse(JSON.stringify(data)) : void 0
11907 });
11908 return my;
11909 }
11910
11911 my = {
11912 list: [],
11913 segmentId: function(){
11914 return nextSegmentId++;
11915 },
11916 checkIntersection: function(seg1, seg2){
11917 return push('check', { seg1: seg1, seg2: seg2 });
11918 },
11919 segmentChop: function(seg, end){
11920 push('div_seg', { seg: seg, pt: end });
11921 return push('chop', { seg: seg, pt: end });
11922 },
11923 statusRemove: function(seg){
11924 return push('pop_seg', { seg: seg });
11925 },
11926 segmentUpdate: function(seg){
11927 return push('seg_update', { seg: seg });
11928 },
11929 segmentNew: function(seg, primary){
11930 return push('new_seg', { seg: seg, primary: primary });
11931 },
11932 segmentRemove: function(seg){
11933 return push('rem_seg', { seg: seg });
11934 },
11935 tempStatus: function(seg, above, below){
11936 return push('temp_status', { seg: seg, above: above, below: below });
11937 },
11938 rewind: function(seg){
11939 return push('rewind', { seg: seg });
11940 },
11941 status: function(seg, above, below){
11942 return push('status', { seg: seg, above: above, below: below });
11943 },
11944 vert: function(x){
11945 if (x === curVert)
11946 return my;
11947 curVert = x;
11948 return push('vert', { x: x });
11949 },
11950 log: function(data){
11951 if (typeof data !== 'string')
11952 data = JSON.stringify(data, false, ' ');
11953 return push('log', { txt: data });
11954 },
11955 reset: function(){
11956 return push('reset');
11957 },
11958 selected: function(segs){
11959 return push('selected', { segs: segs });
11960 },
11961 chainStart: function(seg){
11962 return push('chain_start', { seg: seg });
11963 },
11964 chainRemoveHead: function(index, pt){
11965 return push('chain_rem_head', { index: index, pt: pt });
11966 },
11967 chainRemoveTail: function(index, pt){
11968 return push('chain_rem_tail', { index: index, pt: pt });
11969 },
11970 chainNew: function(pt1, pt2){
11971 return push('chain_new', { pt1: pt1, pt2: pt2 });
11972 },
11973 chainMatch: function(index){
11974 return push('chain_match', { index: index });
11975 },
11976 chainClose: function(index){
11977 return push('chain_close', { index: index });
11978 },
11979 chainAddHead: function(index, pt){
11980 return push('chain_add_head', { index: index, pt: pt });
11981 },
11982 chainAddTail: function(index, pt){
11983 return push('chain_add_tail', { index: index, pt: pt, });
11984 },
11985 chainConnect: function(index1, index2){
11986 return push('chain_con', { index1: index1, index2: index2 });
11987 },
11988 chainReverse: function(index){
11989 return push('chain_rev', { index: index });
11990 },
11991 chainJoin: function(index1, index2){
11992 return push('chain_join', { index1: index1, index2: index2 });
11993 },
11994 done: function(){
11995 return push('done');
11996 }
11997 };
11998 return my;
11999}
12000
12001module.exports = BuildLog;
12002
12003},{}],28:[function(_dereq_,module,exports){
12004// (c) Copyright 2016, Sean Connelly (@voidqk), http://syntheti.cc
12005// MIT License
12006// Project Home: https://github.com/voidqk/polybooljs
12007
12008//
12009// provides the raw computation functions that takes epsilon into account
12010//
12011// zero is defined to be between (-epsilon, epsilon) exclusive
12012//
12013
12014function Epsilon(eps){
12015 if (typeof eps !== 'number')
12016 eps = 0.0000000001; // sane default? sure why not
12017 var my = {
12018 epsilon: function(v){
12019 if (typeof v === 'number')
12020 eps = v;
12021 return eps;
12022 },
12023 pointAboveOrOnLine: function(pt, left, right){
12024 var Ax = left[0];
12025 var Ay = left[1];
12026 var Bx = right[0];
12027 var By = right[1];
12028 var Cx = pt[0];
12029 var Cy = pt[1];
12030 return (Bx - Ax) * (Cy - Ay) - (By - Ay) * (Cx - Ax) >= -eps;
12031 },
12032 pointBetween: function(p, left, right){
12033 // p must be collinear with left->right
12034 // returns false if p == left, p == right, or left == right
12035 var d_py_ly = p[1] - left[1];
12036 var d_rx_lx = right[0] - left[0];
12037 var d_px_lx = p[0] - left[0];
12038 var d_ry_ly = right[1] - left[1];
12039
12040 var dot = d_px_lx * d_rx_lx + d_py_ly * d_ry_ly;
12041 // if `dot` is 0, then `p` == `left` or `left` == `right` (reject)
12042 // if `dot` is less than 0, then `p` is to the left of `left` (reject)
12043 if (dot < eps)
12044 return false;
12045
12046 var sqlen = d_rx_lx * d_rx_lx + d_ry_ly * d_ry_ly;
12047 // if `dot` > `sqlen`, then `p` is to the right of `right` (reject)
12048 // therefore, if `dot - sqlen` is greater than 0, then `p` is to the right of `right` (reject)
12049 if (dot - sqlen > -eps)
12050 return false;
12051
12052 return true;
12053 },
12054 pointsSameX: function(p1, p2){
12055 return Math.abs(p1[0] - p2[0]) < eps;
12056 },
12057 pointsSameY: function(p1, p2){
12058 return Math.abs(p1[1] - p2[1]) < eps;
12059 },
12060 pointsSame: function(p1, p2){
12061 return my.pointsSameX(p1, p2) && my.pointsSameY(p1, p2);
12062 },
12063 pointsCompare: function(p1, p2){
12064 // returns -1 if p1 is smaller, 1 if p2 is smaller, 0 if equal
12065 if (my.pointsSameX(p1, p2))
12066 return my.pointsSameY(p1, p2) ? 0 : (p1[1] < p2[1] ? -1 : 1);
12067 return p1[0] < p2[0] ? -1 : 1;
12068 },
12069 pointsCollinear: function(pt1, pt2, pt3){
12070 // does pt1->pt2->pt3 make a straight line?
12071 // essentially this is just checking to see if the slope(pt1->pt2) === slope(pt2->pt3)
12072 // if slopes are equal, then they must be collinear, because they share pt2
12073 var dx1 = pt1[0] - pt2[0];
12074 var dy1 = pt1[1] - pt2[1];
12075 var dx2 = pt2[0] - pt3[0];
12076 var dy2 = pt2[1] - pt3[1];
12077 return Math.abs(dx1 * dy2 - dx2 * dy1) < eps;
12078 },
12079 linesIntersect: function(a0, a1, b0, b1){
12080 // returns false if the lines are coincident (e.g., parallel or on top of each other)
12081 //
12082 // returns an object if the lines intersect:
12083 // {
12084 // pt: [x, y], where the intersection point is at
12085 // alongA: where intersection point is along A,
12086 // alongB: where intersection point is along B
12087 // }
12088 //
12089 // alongA and alongB will each be one of: -2, -1, 0, 1, 2
12090 //
12091 // with the following meaning:
12092 //
12093 // -2 intersection point is before segment's first point
12094 // -1 intersection point is directly on segment's first point
12095 // 0 intersection point is between segment's first and second points (exclusive)
12096 // 1 intersection point is directly on segment's second point
12097 // 2 intersection point is after segment's second point
12098 var adx = a1[0] - a0[0];
12099 var ady = a1[1] - a0[1];
12100 var bdx = b1[0] - b0[0];
12101 var bdy = b1[1] - b0[1];
12102
12103 var axb = adx * bdy - ady * bdx;
12104 if (Math.abs(axb) < eps)
12105 return false; // lines are coincident
12106
12107 var dx = a0[0] - b0[0];
12108 var dy = a0[1] - b0[1];
12109
12110 var A = (bdx * dy - bdy * dx) / axb;
12111 var B = (adx * dy - ady * dx) / axb;
12112
12113 var ret = {
12114 alongA: 0,
12115 alongB: 0,
12116 pt: [
12117 a0[0] + A * adx,
12118 a0[1] + A * ady
12119 ]
12120 };
12121
12122 // categorize where intersection point is along A and B
12123
12124 if (A <= -eps)
12125 ret.alongA = -2;
12126 else if (A < eps)
12127 ret.alongA = -1;
12128 else if (A - 1 <= -eps)
12129 ret.alongA = 0;
12130 else if (A - 1 < eps)
12131 ret.alongA = 1;
12132 else
12133 ret.alongA = 2;
12134
12135 if (B <= -eps)
12136 ret.alongB = -2;
12137 else if (B < eps)
12138 ret.alongB = -1;
12139 else if (B - 1 <= -eps)
12140 ret.alongB = 0;
12141 else if (B - 1 < eps)
12142 ret.alongB = 1;
12143 else
12144 ret.alongB = 2;
12145
12146 return ret;
12147 },
12148 pointInsideRegion: function(pt, region){
12149 var x = pt[0];
12150 var y = pt[1];
12151 var last_x = region[region.length - 1][0];
12152 var last_y = region[region.length - 1][1];
12153 var inside = false;
12154 for (var i = 0; i < region.length; i++){
12155 var curr_x = region[i][0];
12156 var curr_y = region[i][1];
12157
12158 // if y is between curr_y and last_y, and
12159 // x is to the right of the boundary created by the line
12160 if ((curr_y - y > eps) != (last_y - y > eps) &&
12161 (last_x - curr_x) * (y - curr_y) / (last_y - curr_y) + curr_x - x > eps)
12162 inside = !inside
12163
12164 last_x = curr_x;
12165 last_y = curr_y;
12166 }
12167 return inside;
12168 }
12169 };
12170 return my;
12171}
12172
12173module.exports = Epsilon;
12174
12175},{}],29:[function(_dereq_,module,exports){
12176// (c) Copyright 2017, Sean Connelly (@voidqk), http://syntheti.cc
12177// MIT License
12178// Project Home: https://github.com/voidqk/polybooljs
12179
12180//
12181// convert between PolyBool polygon format and GeoJSON formats (Polygon and MultiPolygon)
12182//
12183
12184var GeoJSON = {
12185 // convert a GeoJSON object to a PolyBool polygon
12186 toPolygon: function(PolyBool, geojson){
12187
12188 // converts list of LineString's to segments
12189 function GeoPoly(coords){
12190 // check for empty coords
12191 if (coords.length <= 0)
12192 return PolyBool.segments({ inverted: false, regions: [] });
12193
12194 // convert LineString to segments
12195 function LineString(ls){
12196 // remove tail which should be the same as head
12197 var reg = ls.slice(0, ls.length - 1);
12198 return PolyBool.segments({ inverted: false, regions: [reg] });
12199 }
12200
12201 // the first LineString is considered the outside
12202 var out = LineString(coords[0]);
12203
12204 // the rest of the LineStrings are considered interior holes, so subtract them from the
12205 // current result
12206 for (var i = 1; i < coords.length; i++)
12207 out = PolyBool.selectDifference(PolyBool.combine(out, LineString(coords[i])));
12208
12209 return out;
12210 }
12211
12212 if (geojson.type === 'Polygon'){
12213 // single polygon, so just convert it and we're done
12214 return PolyBool.polygon(GeoPoly(geojson.coordinates));
12215 }
12216 else if (geojson.type === 'MultiPolygon'){
12217 // multiple polygons, so union all the polygons together
12218 var out = PolyBool.segments({ inverted: false, regions: [] });
12219 for (var i = 0; i < geojson.coordinates.length; i++)
12220 out = PolyBool.selectUnion(PolyBool.combine(out, GeoPoly(geojson.coordinates[i])));
12221 return PolyBool.polygon(out);
12222 }
12223 throw new Error('PolyBool: Cannot convert GeoJSON object to PolyBool polygon');
12224 },
12225
12226 // convert a PolyBool polygon to a GeoJSON object
12227 fromPolygon: function(PolyBool, eps, poly){
12228 // make sure out polygon is clean
12229 poly = PolyBool.polygon(PolyBool.segments(poly));
12230
12231 // test if r1 is inside r2
12232 function regionInsideRegion(r1, r2){
12233 // we're guaranteed no lines intersect (because the polygon is clean), but a vertex
12234 // could be on the edge -- so we just average pt[0] and pt[1] to produce a point on the
12235 // edge of the first line, which cannot be on an edge
12236 return eps.pointInsideRegion([
12237 (r1[0][0] + r1[1][0]) * 0.5,
12238 (r1[0][1] + r1[1][1]) * 0.5
12239 ], r2);
12240 }
12241
12242 // calculate inside heirarchy
12243 //
12244 // _____________________ _______ roots -> A -> F
12245 // | A | | F | | |
12246 // | _______ _______ | | ___ | +-- B +-- G
12247 // | | B | | C | | | | | | | |
12248 // | | ___ | | ___ | | | | | | | +-- D
12249 // | | | D | | | | E | | | | | G | | |
12250 // | | |___| | | |___| | | | | | | +-- C
12251 // | |_______| |_______| | | |___| | |
12252 // |_____________________| |_______| +-- E
12253
12254 function newNode(region){
12255 return {
12256 region: region,
12257 children: []
12258 };
12259 }
12260
12261 var roots = newNode(null);
12262
12263 function addChild(root, region){
12264 // first check if we're inside any children
12265 for (var i = 0; i < root.children.length; i++){
12266 var child = root.children[i];
12267 if (regionInsideRegion(region, child.region)){
12268 // we are, so insert inside them instead
12269 addChild(child, region);
12270 return;
12271 }
12272 }
12273
12274 // not inside any children, so check to see if any children are inside us
12275 var node = newNode(region);
12276 for (var i = 0; i < root.children.length; i++){
12277 var child = root.children[i];
12278 if (regionInsideRegion(child.region, region)){
12279 // oops... move the child beneath us, and remove them from root
12280 node.children.push(child);
12281 root.children.splice(i, 1);
12282 i--;
12283 }
12284 }
12285
12286 // now we can add ourselves
12287 root.children.push(node);
12288 }
12289
12290 // add all regions to the root
12291 for (var i = 0; i < poly.regions.length; i++){
12292 var region = poly.regions[i];
12293 if (region.length < 3) // regions must have at least 3 points (sanity check)
12294 continue;
12295 addChild(roots, region);
12296 }
12297
12298 // with our heirarchy, we can distinguish between exterior borders, and interior holes
12299 // the root nodes are exterior, children are interior, children's children are exterior,
12300 // children's children's children are interior, etc
12301
12302 // while we're at it, exteriors are counter-clockwise, and interiors are clockwise
12303
12304 function forceWinding(region, clockwise){
12305 // first, see if we're clockwise or counter-clockwise
12306 // https://en.wikipedia.org/wiki/Shoelace_formula
12307 var winding = 0;
12308 var last_x = region[region.length - 1][0];
12309 var last_y = region[region.length - 1][1];
12310 var copy = [];
12311 for (var i = 0; i < region.length; i++){
12312 var curr_x = region[i][0];
12313 var curr_y = region[i][1];
12314 copy.push([curr_x, curr_y]); // create a copy while we're at it
12315 winding += curr_y * last_x - curr_x * last_y;
12316 last_x = curr_x;
12317 last_y = curr_y;
12318 }
12319 // this assumes Cartesian coordinates (Y is positive going up)
12320 var isclockwise = winding < 0;
12321 if (isclockwise !== clockwise)
12322 copy.reverse();
12323 // while we're here, the last point must be the first point...
12324 copy.push([copy[0][0], copy[0][1]]);
12325 return copy;
12326 }
12327
12328 var geopolys = [];
12329
12330 function addExterior(node){
12331 var poly = [forceWinding(node.region, false)];
12332 geopolys.push(poly);
12333 // children of exteriors are interior
12334 for (var i = 0; i < node.children.length; i++)
12335 poly.push(getInterior(node.children[i]));
12336 }
12337
12338 function getInterior(node){
12339 // children of interiors are exterior
12340 for (var i = 0; i < node.children.length; i++)
12341 addExterior(node.children[i]);
12342 // return the clockwise interior
12343 return forceWinding(node.region, true);
12344 }
12345
12346 // root nodes are exterior
12347 for (var i = 0; i < roots.children.length; i++)
12348 addExterior(roots.children[i]);
12349
12350 // lastly, construct the approrpriate GeoJSON object
12351
12352 if (geopolys.length <= 0) // empty GeoJSON Polygon
12353 return { type: 'Polygon', coordinates: [] };
12354 if (geopolys.length == 1) // use a GeoJSON Polygon
12355 return { type: 'Polygon', coordinates: geopolys[0] };
12356 return { // otherwise, use a GeoJSON MultiPolygon
12357 type: 'MultiPolygon',
12358 coordinates: geopolys
12359 };
12360 }
12361};
12362
12363module.exports = GeoJSON;
12364
12365},{}],30:[function(_dereq_,module,exports){
12366// (c) Copyright 2016, Sean Connelly (@voidqk), http://syntheti.cc
12367// MIT License
12368// Project Home: https://github.com/voidqk/polybooljs
12369
12370//
12371// this is the core work-horse
12372//
12373
12374var LinkedList = _dereq_('./linked-list');
12375
12376function Intersecter(selfIntersection, eps, buildLog){
12377 // selfIntersection is true/false depending on the phase of the overall algorithm
12378
12379 //
12380 // segment creation
12381 //
12382
12383 function segmentNew(start, end){
12384 return {
12385 id: buildLog ? buildLog.segmentId() : -1,
12386 start: start,
12387 end: end,
12388 myFill: {
12389 above: null, // is there fill above us?
12390 below: null // is there fill below us?
12391 },
12392 otherFill: null
12393 };
12394 }
12395
12396 function segmentCopy(start, end, seg){
12397 return {
12398 id: buildLog ? buildLog.segmentId() : -1,
12399 start: start,
12400 end: end,
12401 myFill: {
12402 above: seg.myFill.above,
12403 below: seg.myFill.below
12404 },
12405 otherFill: null
12406 };
12407 }
12408
12409 //
12410 // event logic
12411 //
12412
12413 var event_root = LinkedList.create();
12414
12415 function eventCompare(p1_isStart, p1_1, p1_2, p2_isStart, p2_1, p2_2){
12416 // compare the selected points first
12417 var comp = eps.pointsCompare(p1_1, p2_1);
12418 if (comp !== 0)
12419 return comp;
12420 // the selected points are the same
12421
12422 if (eps.pointsSame(p1_2, p2_2)) // if the non-selected points are the same too...
12423 return 0; // then the segments are equal
12424
12425 if (p1_isStart !== p2_isStart) // if one is a start and the other isn't...
12426 return p1_isStart ? 1 : -1; // favor the one that isn't the start
12427
12428 // otherwise, we'll have to calculate which one is below the other manually
12429 return eps.pointAboveOrOnLine(p1_2,
12430 p2_isStart ? p2_1 : p2_2, // order matters
12431 p2_isStart ? p2_2 : p2_1
12432 ) ? 1 : -1;
12433 }
12434
12435 function eventAdd(ev, other_pt){
12436 event_root.insertBefore(ev, function(here){
12437 // should ev be inserted before here?
12438 var comp = eventCompare(
12439 ev .isStart, ev .pt, other_pt,
12440 here.isStart, here.pt, here.other.pt
12441 );
12442 return comp < 0;
12443 });
12444 }
12445
12446 function eventAddSegmentStart(seg, primary){
12447 var ev_start = LinkedList.node({
12448 isStart: true,
12449 pt: seg.start,
12450 seg: seg,
12451 primary: primary,
12452 other: null,
12453 status: null
12454 });
12455 eventAdd(ev_start, seg.end);
12456 return ev_start;
12457 }
12458
12459 function eventAddSegmentEnd(ev_start, seg, primary){
12460 var ev_end = LinkedList.node({
12461 isStart: false,
12462 pt: seg.end,
12463 seg: seg,
12464 primary: primary,
12465 other: ev_start,
12466 status: null
12467 });
12468 ev_start.other = ev_end;
12469 eventAdd(ev_end, ev_start.pt);
12470 }
12471
12472 function eventAddSegment(seg, primary){
12473 var ev_start = eventAddSegmentStart(seg, primary);
12474 eventAddSegmentEnd(ev_start, seg, primary);
12475 return ev_start;
12476 }
12477
12478 function eventUpdateEnd(ev, end){
12479 // slides an end backwards
12480 // (start)------------(end) to:
12481 // (start)---(end)
12482
12483 if (buildLog)
12484 buildLog.segmentChop(ev.seg, end);
12485
12486 ev.other.remove();
12487 ev.seg.end = end;
12488 ev.other.pt = end;
12489 eventAdd(ev.other, ev.pt);
12490 }
12491
12492 function eventDivide(ev, pt){
12493 var ns = segmentCopy(pt, ev.seg.end, ev.seg);
12494 eventUpdateEnd(ev, pt);
12495 return eventAddSegment(ns, ev.primary);
12496 }
12497
12498 function calculate(primaryPolyInverted, secondaryPolyInverted){
12499 // if selfIntersection is true then there is no secondary polygon, so that isn't used
12500
12501 //
12502 // status logic
12503 //
12504
12505 var status_root = LinkedList.create();
12506
12507 function statusCompare(ev1, ev2){
12508 var a1 = ev1.seg.start;
12509 var a2 = ev1.seg.end;
12510 var b1 = ev2.seg.start;
12511 var b2 = ev2.seg.end;
12512
12513 if (eps.pointsCollinear(a1, b1, b2)){
12514 if (eps.pointsCollinear(a2, b1, b2))
12515 return 1;//eventCompare(true, a1, a2, true, b1, b2);
12516 return eps.pointAboveOrOnLine(a2, b1, b2) ? 1 : -1;
12517 }
12518 return eps.pointAboveOrOnLine(a1, b1, b2) ? 1 : -1;
12519 }
12520
12521 function statusFindSurrounding(ev){
12522 return status_root.findTransition(function(here){
12523 var comp = statusCompare(ev, here.ev);
12524 return comp > 0;
12525 });
12526 }
12527
12528 function checkIntersection(ev1, ev2){
12529 // returns the segment equal to ev1, or false if nothing equal
12530
12531 var seg1 = ev1.seg;
12532 var seg2 = ev2.seg;
12533 var a1 = seg1.start;
12534 var a2 = seg1.end;
12535 var b1 = seg2.start;
12536 var b2 = seg2.end;
12537
12538 if (buildLog)
12539 buildLog.checkIntersection(seg1, seg2);
12540
12541 var i = eps.linesIntersect(a1, a2, b1, b2);
12542
12543 if (i === false){
12544 // segments are parallel or coincident
12545
12546 // if points aren't collinear, then the segments are parallel, so no intersections
12547 if (!eps.pointsCollinear(a1, a2, b1))
12548 return false;
12549 // otherwise, segments are on top of each other somehow (aka coincident)
12550
12551 if (eps.pointsSame(a1, b2) || eps.pointsSame(a2, b1))
12552 return false; // segments touch at endpoints... no intersection
12553
12554 var a1_equ_b1 = eps.pointsSame(a1, b1);
12555 var a2_equ_b2 = eps.pointsSame(a2, b2);
12556
12557 if (a1_equ_b1 && a2_equ_b2)
12558 return ev2; // segments are exactly equal
12559
12560 var a1_between = !a1_equ_b1 && eps.pointBetween(a1, b1, b2);
12561 var a2_between = !a2_equ_b2 && eps.pointBetween(a2, b1, b2);
12562
12563 // handy for debugging:
12564 // buildLog.log({
12565 // a1_equ_b1: a1_equ_b1,
12566 // a2_equ_b2: a2_equ_b2,
12567 // a1_between: a1_between,
12568 // a2_between: a2_between
12569 // });
12570
12571 if (a1_equ_b1){
12572 if (a2_between){
12573 // (a1)---(a2)
12574 // (b1)----------(b2)
12575 eventDivide(ev2, a2);
12576 }
12577 else{
12578 // (a1)----------(a2)
12579 // (b1)---(b2)
12580 eventDivide(ev1, b2);
12581 }
12582 return ev2;
12583 }
12584 else if (a1_between){
12585 if (!a2_equ_b2){
12586 // make a2 equal to b2
12587 if (a2_between){
12588 // (a1)---(a2)
12589 // (b1)-----------------(b2)
12590 eventDivide(ev2, a2);
12591 }
12592 else{
12593 // (a1)----------(a2)
12594 // (b1)----------(b2)
12595 eventDivide(ev1, b2);
12596 }
12597 }
12598
12599 // (a1)---(a2)
12600 // (b1)----------(b2)
12601 eventDivide(ev2, a1);
12602 }
12603 }
12604 else{
12605 // otherwise, lines intersect at i.pt, which may or may not be between the endpoints
12606
12607 // is A divided between its endpoints? (exclusive)
12608 if (i.alongA === 0){
12609 if (i.alongB === -1) // yes, at exactly b1
12610 eventDivide(ev1, b1);
12611 else if (i.alongB === 0) // yes, somewhere between B's endpoints
12612 eventDivide(ev1, i.pt);
12613 else if (i.alongB === 1) // yes, at exactly b2
12614 eventDivide(ev1, b2);
12615 }
12616
12617 // is B divided between its endpoints? (exclusive)
12618 if (i.alongB === 0){
12619 if (i.alongA === -1) // yes, at exactly a1
12620 eventDivide(ev2, a1);
12621 else if (i.alongA === 0) // yes, somewhere between A's endpoints (exclusive)
12622 eventDivide(ev2, i.pt);
12623 else if (i.alongA === 1) // yes, at exactly a2
12624 eventDivide(ev2, a2);
12625 }
12626 }
12627 return false;
12628 }
12629
12630 //
12631 // main event loop
12632 //
12633 var segments = [];
12634 while (!event_root.isEmpty()){
12635 var ev = event_root.getHead();
12636
12637 if (buildLog)
12638 buildLog.vert(ev.pt[0]);
12639
12640 if (ev.isStart){
12641
12642 if (buildLog)
12643 buildLog.segmentNew(ev.seg, ev.primary);
12644
12645 var surrounding = statusFindSurrounding(ev);
12646 var above = surrounding.before ? surrounding.before.ev : null;
12647 var below = surrounding.after ? surrounding.after.ev : null;
12648
12649 if (buildLog){
12650 buildLog.tempStatus(
12651 ev.seg,
12652 above ? above.seg : false,
12653 below ? below.seg : false
12654 );
12655 }
12656
12657 function checkBothIntersections(){
12658 if (above){
12659 var eve = checkIntersection(ev, above);
12660 if (eve)
12661 return eve;
12662 }
12663 if (below)
12664 return checkIntersection(ev, below);
12665 return false;
12666 }
12667
12668 var eve = checkBothIntersections();
12669 if (eve){
12670 // ev and eve are equal
12671 // we'll keep eve and throw away ev
12672
12673 // merge ev.seg's fill information into eve.seg
12674
12675 if (selfIntersection){
12676 var toggle; // are we a toggling edge?
12677 if (ev.seg.myFill.below === null)
12678 toggle = true;
12679 else
12680 toggle = ev.seg.myFill.above !== ev.seg.myFill.below;
12681
12682 // merge two segments that belong to the same polygon
12683 // think of this as sandwiching two segments together, where `eve.seg` is
12684 // the bottom -- this will cause the above fill flag to toggle
12685 if (toggle)
12686 eve.seg.myFill.above = !eve.seg.myFill.above;
12687 }
12688 else{
12689 // merge two segments that belong to different polygons
12690 // each segment has distinct knowledge, so no special logic is needed
12691 // note that this can only happen once per segment in this phase, because we
12692 // are guaranteed that all self-intersections are gone
12693 eve.seg.otherFill = ev.seg.myFill;
12694 }
12695
12696 if (buildLog)
12697 buildLog.segmentUpdate(eve.seg);
12698
12699 ev.other.remove();
12700 ev.remove();
12701 }
12702
12703 if (event_root.getHead() !== ev){
12704 // something was inserted before us in the event queue, so loop back around and
12705 // process it before continuing
12706 if (buildLog)
12707 buildLog.rewind(ev.seg);
12708 continue;
12709 }
12710
12711 //
12712 // calculate fill flags
12713 //
12714 if (selfIntersection){
12715 var toggle; // are we a toggling edge?
12716 if (ev.seg.myFill.below === null) // if we are a new segment...
12717 toggle = true; // then we toggle
12718 else // we are a segment that has previous knowledge from a division
12719 toggle = ev.seg.myFill.above !== ev.seg.myFill.below; // calculate toggle
12720
12721 // next, calculate whether we are filled below us
12722 if (!below){ // if nothing is below us...
12723 // we are filled below us if the polygon is inverted
12724 ev.seg.myFill.below = primaryPolyInverted;
12725 }
12726 else{
12727 // otherwise, we know the answer -- it's the same if whatever is below
12728 // us is filled above it
12729 ev.seg.myFill.below = below.seg.myFill.above;
12730 }
12731
12732 // since now we know if we're filled below us, we can calculate whether
12733 // we're filled above us by applying toggle to whatever is below us
12734 if (toggle)
12735 ev.seg.myFill.above = !ev.seg.myFill.below;
12736 else
12737 ev.seg.myFill.above = ev.seg.myFill.below;
12738 }
12739 else{
12740 // now we fill in any missing transition information, since we are all-knowing
12741 // at this point
12742
12743 if (ev.seg.otherFill === null){
12744 // if we don't have other information, then we need to figure out if we're
12745 // inside the other polygon
12746 var inside;
12747 if (!below){
12748 // if nothing is below us, then we're inside if the other polygon is
12749 // inverted
12750 inside =
12751 ev.primary ? secondaryPolyInverted : primaryPolyInverted;
12752 }
12753 else{ // otherwise, something is below us
12754 // so copy the below segment's other polygon's above
12755 if (ev.primary === below.primary)
12756 inside = below.seg.otherFill.above;
12757 else
12758 inside = below.seg.myFill.above;
12759 }
12760 ev.seg.otherFill = {
12761 above: inside,
12762 below: inside
12763 };
12764 }
12765 }
12766
12767 if (buildLog){
12768 buildLog.status(
12769 ev.seg,
12770 above ? above.seg : false,
12771 below ? below.seg : false
12772 );
12773 }
12774
12775 // insert the status and remember it for later removal
12776 ev.other.status = surrounding.insert(LinkedList.node({ ev: ev }));
12777 }
12778 else{
12779 var st = ev.status;
12780
12781 if (st === null){
12782 throw new Error('PolyBool: Zero-length segment detected; your epsilon is ' +
12783 'probably too small or too large');
12784 }
12785
12786 // removing the status will create two new adjacent edges, so we'll need to check
12787 // for those
12788 if (status_root.exists(st.prev) && status_root.exists(st.next))
12789 checkIntersection(st.prev.ev, st.next.ev);
12790
12791 if (buildLog)
12792 buildLog.statusRemove(st.ev.seg);
12793
12794 // remove the status
12795 st.remove();
12796
12797 // if we've reached this point, we've calculated everything there is to know, so
12798 // save the segment for reporting
12799 if (!ev.primary){
12800 // make sure `seg.myFill` actually points to the primary polygon though
12801 var s = ev.seg.myFill;
12802 ev.seg.myFill = ev.seg.otherFill;
12803 ev.seg.otherFill = s;
12804 }
12805 segments.push(ev.seg);
12806 }
12807
12808 // remove the event and continue
12809 event_root.getHead().remove();
12810 }
12811
12812 if (buildLog)
12813 buildLog.done();
12814
12815 return segments;
12816 }
12817
12818 // return the appropriate API depending on what we're doing
12819 if (!selfIntersection){
12820 // performing combination of polygons, so only deal with already-processed segments
12821 return {
12822 calculate: function(segments1, inverted1, segments2, inverted2){
12823 // segmentsX come from the self-intersection API, or this API
12824 // invertedX is whether we treat that list of segments as an inverted polygon or not
12825 // returns segments that can be used for further operations
12826 segments1.forEach(function(seg){
12827 eventAddSegment(segmentCopy(seg.start, seg.end, seg), true);
12828 });
12829 segments2.forEach(function(seg){
12830 eventAddSegment(segmentCopy(seg.start, seg.end, seg), false);
12831 });
12832 return calculate(inverted1, inverted2);
12833 }
12834 };
12835 }
12836
12837 // otherwise, performing self-intersection, so deal with regions
12838 return {
12839 addRegion: function(region){
12840 // regions are a list of points:
12841 // [ [0, 0], [100, 0], [50, 100] ]
12842 // you can add multiple regions before running calculate
12843 var pt1;
12844 var pt2 = region[region.length - 1];
12845 for (var i = 0; i < region.length; i++){
12846 pt1 = pt2;
12847 pt2 = region[i];
12848
12849 var forward = eps.pointsCompare(pt1, pt2);
12850 if (forward === 0) // points are equal, so we have a zero-length segment
12851 continue; // just skip it
12852
12853 eventAddSegment(
12854 segmentNew(
12855 forward < 0 ? pt1 : pt2,
12856 forward < 0 ? pt2 : pt1
12857 ),
12858 true
12859 );
12860 }
12861 },
12862 calculate: function(inverted){
12863 // is the polygon inverted?
12864 // returns segments
12865 return calculate(inverted, false);
12866 }
12867 };
12868}
12869
12870module.exports = Intersecter;
12871
12872},{"./linked-list":31}],31:[function(_dereq_,module,exports){
12873// (c) Copyright 2016, Sean Connelly (@voidqk), http://syntheti.cc
12874// MIT License
12875// Project Home: https://github.com/voidqk/polybooljs
12876
12877//
12878// simple linked list implementation that allows you to traverse down nodes and save positions
12879//
12880
12881var LinkedList = {
12882 create: function(){
12883 var my = {
12884 root: { root: true, next: null },
12885 exists: function(node){
12886 if (node === null || node === my.root)
12887 return false;
12888 return true;
12889 },
12890 isEmpty: function(){
12891 return my.root.next === null;
12892 },
12893 getHead: function(){
12894 return my.root.next;
12895 },
12896 insertBefore: function(node, check){
12897 var last = my.root;
12898 var here = my.root.next;
12899 while (here !== null){
12900 if (check(here)){
12901 node.prev = here.prev;
12902 node.next = here;
12903 here.prev.next = node;
12904 here.prev = node;
12905 return;
12906 }
12907 last = here;
12908 here = here.next;
12909 }
12910 last.next = node;
12911 node.prev = last;
12912 node.next = null;
12913 },
12914 findTransition: function(check){
12915 var prev = my.root;
12916 var here = my.root.next;
12917 while (here !== null){
12918 if (check(here))
12919 break;
12920 prev = here;
12921 here = here.next;
12922 }
12923 return {
12924 before: prev === my.root ? null : prev,
12925 after: here,
12926 insert: function(node){
12927 node.prev = prev;
12928 node.next = here;
12929 prev.next = node;
12930 if (here !== null)
12931 here.prev = node;
12932 return node;
12933 }
12934 };
12935 }
12936 };
12937 return my;
12938 },
12939 node: function(data){
12940 data.prev = null;
12941 data.next = null;
12942 data.remove = function(){
12943 data.prev.next = data.next;
12944 if (data.next)
12945 data.next.prev = data.prev;
12946 data.prev = null;
12947 data.next = null;
12948 };
12949 return data;
12950 }
12951};
12952
12953module.exports = LinkedList;
12954
12955},{}],32:[function(_dereq_,module,exports){
12956// (c) Copyright 2016, Sean Connelly (@voidqk), http://syntheti.cc
12957// MIT License
12958// Project Home: https://github.com/voidqk/polybooljs
12959
12960//
12961// converts a list of segments into a list of regions, while also removing unnecessary verticies
12962//
12963
12964function SegmentChainer(segments, eps, buildLog){
12965 var chains = [];
12966 var regions = [];
12967
12968 segments.forEach(function(seg){
12969 var pt1 = seg.start;
12970 var pt2 = seg.end;
12971 if (eps.pointsSame(pt1, pt2)){
12972 console.warn('PolyBool: Warning: Zero-length segment detected; your epsilon is ' +
12973 'probably too small or too large');
12974 return;
12975 }
12976
12977 if (buildLog)
12978 buildLog.chainStart(seg);
12979
12980 // search for two chains that this segment matches
12981 var first_match = {
12982 index: 0,
12983 matches_head: false,
12984 matches_pt1: false
12985 };
12986 var second_match = {
12987 index: 0,
12988 matches_head: false,
12989 matches_pt1: false
12990 };
12991 var next_match = first_match;
12992 function setMatch(index, matches_head, matches_pt1){
12993 // return true if we've matched twice
12994 next_match.index = index;
12995 next_match.matches_head = matches_head;
12996 next_match.matches_pt1 = matches_pt1;
12997 if (next_match === first_match){
12998 next_match = second_match;
12999 return false;
13000 }
13001 next_match = null;
13002 return true; // we've matched twice, we're done here
13003 }
13004 for (var i = 0; i < chains.length; i++){
13005 var chain = chains[i];
13006 var head = chain[0];
13007 var head2 = chain[1];
13008 var tail = chain[chain.length - 1];
13009 var tail2 = chain[chain.length - 2];
13010 if (eps.pointsSame(head, pt1)){
13011 if (setMatch(i, true, true))
13012 break;
13013 }
13014 else if (eps.pointsSame(head, pt2)){
13015 if (setMatch(i, true, false))
13016 break;
13017 }
13018 else if (eps.pointsSame(tail, pt1)){
13019 if (setMatch(i, false, true))
13020 break;
13021 }
13022 else if (eps.pointsSame(tail, pt2)){
13023 if (setMatch(i, false, false))
13024 break;
13025 }
13026 }
13027
13028 if (next_match === first_match){
13029 // we didn't match anything, so create a new chain
13030 chains.push([ pt1, pt2 ]);
13031 if (buildLog)
13032 buildLog.chainNew(pt1, pt2);
13033 return;
13034 }
13035
13036 if (next_match === second_match){
13037 // we matched a single chain
13038
13039 if (buildLog)
13040 buildLog.chainMatch(first_match.index);
13041
13042 // add the other point to the apporpriate end, and check to see if we've closed the
13043 // chain into a loop
13044
13045 var index = first_match.index;
13046 var pt = first_match.matches_pt1 ? pt2 : pt1; // if we matched pt1, then we add pt2, etc
13047 var addToHead = first_match.matches_head; // if we matched at head, then add to the head
13048
13049 var chain = chains[index];
13050 var grow = addToHead ? chain[0] : chain[chain.length - 1];
13051 var grow2 = addToHead ? chain[1] : chain[chain.length - 2];
13052 var oppo = addToHead ? chain[chain.length - 1] : chain[0];
13053 var oppo2 = addToHead ? chain[chain.length - 2] : chain[1];
13054
13055 if (eps.pointsCollinear(grow2, grow, pt)){
13056 // grow isn't needed because it's directly between grow2 and pt:
13057 // grow2 ---grow---> pt
13058 if (addToHead){
13059 if (buildLog)
13060 buildLog.chainRemoveHead(first_match.index, pt);
13061 chain.shift();
13062 }
13063 else{
13064 if (buildLog)
13065 buildLog.chainRemoveTail(first_match.index, pt);
13066 chain.pop();
13067 }
13068 grow = grow2; // old grow is gone... new grow is what grow2 was
13069 }
13070
13071 if (eps.pointsSame(oppo, pt)){
13072 // we're closing the loop, so remove chain from chains
13073 chains.splice(index, 1);
13074
13075 if (eps.pointsCollinear(oppo2, oppo, grow)){
13076 // oppo isn't needed because it's directly between oppo2 and grow:
13077 // oppo2 ---oppo--->grow
13078 if (addToHead){
13079 if (buildLog)
13080 buildLog.chainRemoveTail(first_match.index, grow);
13081 chain.pop();
13082 }
13083 else{
13084 if (buildLog)
13085 buildLog.chainRemoveHead(first_match.index, grow);
13086 chain.shift();
13087 }
13088 }
13089
13090 if (buildLog)
13091 buildLog.chainClose(first_match.index);
13092
13093 // we have a closed chain!
13094 regions.push(chain);
13095 return;
13096 }
13097
13098 // not closing a loop, so just add it to the apporpriate side
13099 if (addToHead){
13100 if (buildLog)
13101 buildLog.chainAddHead(first_match.index, pt);
13102 chain.unshift(pt);
13103 }
13104 else{
13105 if (buildLog)
13106 buildLog.chainAddTail(first_match.index, pt);
13107 chain.push(pt);
13108 }
13109 return;
13110 }
13111
13112 // otherwise, we matched two chains, so we need to combine those chains together
13113
13114 function reverseChain(index){
13115 if (buildLog)
13116 buildLog.chainReverse(index);
13117 chains[index].reverse(); // gee, that's easy
13118 }
13119
13120 function appendChain(index1, index2){
13121 // index1 gets index2 appended to it, and index2 is removed
13122 var chain1 = chains[index1];
13123 var chain2 = chains[index2];
13124 var tail = chain1[chain1.length - 1];
13125 var tail2 = chain1[chain1.length - 2];
13126 var head = chain2[0];
13127 var head2 = chain2[1];
13128
13129 if (eps.pointsCollinear(tail2, tail, head)){
13130 // tail isn't needed because it's directly between tail2 and head
13131 // tail2 ---tail---> head
13132 if (buildLog)
13133 buildLog.chainRemoveTail(index1, tail);
13134 chain1.pop();
13135 tail = tail2; // old tail is gone... new tail is what tail2 was
13136 }
13137
13138 if (eps.pointsCollinear(tail, head, head2)){
13139 // head isn't needed because it's directly between tail and head2
13140 // tail ---head---> head2
13141 if (buildLog)
13142 buildLog.chainRemoveHead(index2, head);
13143 chain2.shift();
13144 }
13145
13146 if (buildLog)
13147 buildLog.chainJoin(index1, index2);
13148 chains[index1] = chain1.concat(chain2);
13149 chains.splice(index2, 1);
13150 }
13151
13152 var F = first_match.index;
13153 var S = second_match.index;
13154
13155 if (buildLog)
13156 buildLog.chainConnect(F, S);
13157
13158 var reverseF = chains[F].length < chains[S].length; // reverse the shorter chain, if needed
13159 if (first_match.matches_head){
13160 if (second_match.matches_head){
13161 if (reverseF){
13162 // <<<< F <<<< --- >>>> S >>>>
13163 reverseChain(F);
13164 // >>>> F >>>> --- >>>> S >>>>
13165 appendChain(F, S);
13166 }
13167 else{
13168 // <<<< F <<<< --- >>>> S >>>>
13169 reverseChain(S);
13170 // <<<< F <<<< --- <<<< S <<<< logically same as:
13171 // >>>> S >>>> --- >>>> F >>>>
13172 appendChain(S, F);
13173 }
13174 }
13175 else{
13176 // <<<< F <<<< --- <<<< S <<<< logically same as:
13177 // >>>> S >>>> --- >>>> F >>>>
13178 appendChain(S, F);
13179 }
13180 }
13181 else{
13182 if (second_match.matches_head){
13183 // >>>> F >>>> --- >>>> S >>>>
13184 appendChain(F, S);
13185 }
13186 else{
13187 if (reverseF){
13188 // >>>> F >>>> --- <<<< S <<<<
13189 reverseChain(F);
13190 // <<<< F <<<< --- <<<< S <<<< logically same as:
13191 // >>>> S >>>> --- >>>> F >>>>
13192 appendChain(S, F);
13193 }
13194 else{
13195 // >>>> F >>>> --- <<<< S <<<<
13196 reverseChain(S);
13197 // >>>> F >>>> --- >>>> S >>>>
13198 appendChain(F, S);
13199 }
13200 }
13201 }
13202 });
13203
13204 return regions;
13205}
13206
13207module.exports = SegmentChainer;
13208
13209},{}],33:[function(_dereq_,module,exports){
13210// (c) Copyright 2016, Sean Connelly (@voidqk), http://syntheti.cc
13211// MIT License
13212// Project Home: https://github.com/voidqk/polybooljs
13213
13214//
13215// filter a list of segments based on boolean operations
13216//
13217
13218function select(segments, selection, buildLog){
13219 var result = [];
13220 segments.forEach(function(seg){
13221 var index =
13222 (seg.myFill.above ? 8 : 0) +
13223 (seg.myFill.below ? 4 : 0) +
13224 ((seg.otherFill && seg.otherFill.above) ? 2 : 0) +
13225 ((seg.otherFill && seg.otherFill.below) ? 1 : 0);
13226 if (selection[index] !== 0){
13227 // copy the segment to the results, while also calculating the fill status
13228 result.push({
13229 id: buildLog ? buildLog.segmentId() : -1,
13230 start: seg.start,
13231 end: seg.end,
13232 myFill: {
13233 above: selection[index] === 1, // 1 if filled above
13234 below: selection[index] === 2 // 2 if filled below
13235 },
13236 otherFill: null
13237 });
13238 }
13239 });
13240
13241 if (buildLog)
13242 buildLog.selected(result);
13243
13244 return result;
13245}
13246
13247var SegmentSelector = {
13248 union: function(segments, buildLog){ // primary | secondary
13249 // above1 below1 above2 below2 Keep? Value
13250 // 0 0 0 0 => no 0
13251 // 0 0 0 1 => yes filled below 2
13252 // 0 0 1 0 => yes filled above 1
13253 // 0 0 1 1 => no 0
13254 // 0 1 0 0 => yes filled below 2
13255 // 0 1 0 1 => yes filled below 2
13256 // 0 1 1 0 => no 0
13257 // 0 1 1 1 => no 0
13258 // 1 0 0 0 => yes filled above 1
13259 // 1 0 0 1 => no 0
13260 // 1 0 1 0 => yes filled above 1
13261 // 1 0 1 1 => no 0
13262 // 1 1 0 0 => no 0
13263 // 1 1 0 1 => no 0
13264 // 1 1 1 0 => no 0
13265 // 1 1 1 1 => no 0
13266 return select(segments, [
13267 0, 2, 1, 0,
13268 2, 2, 0, 0,
13269 1, 0, 1, 0,
13270 0, 0, 0, 0
13271 ], buildLog);
13272 },
13273 intersect: function(segments, buildLog){ // primary & secondary
13274 // above1 below1 above2 below2 Keep? Value
13275 // 0 0 0 0 => no 0
13276 // 0 0 0 1 => no 0
13277 // 0 0 1 0 => no 0
13278 // 0 0 1 1 => no 0
13279 // 0 1 0 0 => no 0
13280 // 0 1 0 1 => yes filled below 2
13281 // 0 1 1 0 => no 0
13282 // 0 1 1 1 => yes filled below 2
13283 // 1 0 0 0 => no 0
13284 // 1 0 0 1 => no 0
13285 // 1 0 1 0 => yes filled above 1
13286 // 1 0 1 1 => yes filled above 1
13287 // 1 1 0 0 => no 0
13288 // 1 1 0 1 => yes filled below 2
13289 // 1 1 1 0 => yes filled above 1
13290 // 1 1 1 1 => no 0
13291 return select(segments, [
13292 0, 0, 0, 0,
13293 0, 2, 0, 2,
13294 0, 0, 1, 1,
13295 0, 2, 1, 0
13296 ], buildLog);
13297 },
13298 difference: function(segments, buildLog){ // primary - secondary
13299 // above1 below1 above2 below2 Keep? Value
13300 // 0 0 0 0 => no 0
13301 // 0 0 0 1 => no 0
13302 // 0 0 1 0 => no 0
13303 // 0 0 1 1 => no 0
13304 // 0 1 0 0 => yes filled below 2
13305 // 0 1 0 1 => no 0
13306 // 0 1 1 0 => yes filled below 2
13307 // 0 1 1 1 => no 0
13308 // 1 0 0 0 => yes filled above 1
13309 // 1 0 0 1 => yes filled above 1
13310 // 1 0 1 0 => no 0
13311 // 1 0 1 1 => no 0
13312 // 1 1 0 0 => no 0
13313 // 1 1 0 1 => yes filled above 1
13314 // 1 1 1 0 => yes filled below 2
13315 // 1 1 1 1 => no 0
13316 return select(segments, [
13317 0, 0, 0, 0,
13318 2, 0, 2, 0,
13319 1, 1, 0, 0,
13320 0, 1, 2, 0
13321 ], buildLog);
13322 },
13323 differenceRev: function(segments, buildLog){ // secondary - primary
13324 // above1 below1 above2 below2 Keep? Value
13325 // 0 0 0 0 => no 0
13326 // 0 0 0 1 => yes filled below 2
13327 // 0 0 1 0 => yes filled above 1
13328 // 0 0 1 1 => no 0
13329 // 0 1 0 0 => no 0
13330 // 0 1 0 1 => no 0
13331 // 0 1 1 0 => yes filled above 1
13332 // 0 1 1 1 => yes filled above 1
13333 // 1 0 0 0 => no 0
13334 // 1 0 0 1 => yes filled below 2
13335 // 1 0 1 0 => no 0
13336 // 1 0 1 1 => yes filled below 2
13337 // 1 1 0 0 => no 0
13338 // 1 1 0 1 => no 0
13339 // 1 1 1 0 => no 0
13340 // 1 1 1 1 => no 0
13341 return select(segments, [
13342 0, 2, 1, 0,
13343 0, 0, 1, 1,
13344 0, 2, 0, 2,
13345 0, 0, 0, 0
13346 ], buildLog);
13347 },
13348 xor: function(segments, buildLog){ // primary ^ secondary
13349 // above1 below1 above2 below2 Keep? Value
13350 // 0 0 0 0 => no 0
13351 // 0 0 0 1 => yes filled below 2
13352 // 0 0 1 0 => yes filled above 1
13353 // 0 0 1 1 => no 0
13354 // 0 1 0 0 => yes filled below 2
13355 // 0 1 0 1 => no 0
13356 // 0 1 1 0 => no 0
13357 // 0 1 1 1 => yes filled above 1
13358 // 1 0 0 0 => yes filled above 1
13359 // 1 0 0 1 => no 0
13360 // 1 0 1 0 => no 0
13361 // 1 0 1 1 => yes filled below 2
13362 // 1 1 0 0 => no 0
13363 // 1 1 0 1 => yes filled above 1
13364 // 1 1 1 0 => yes filled below 2
13365 // 1 1 1 1 => no 0
13366 return select(segments, [
13367 0, 2, 1, 0,
13368 2, 0, 0, 1,
13369 1, 0, 0, 2,
13370 0, 1, 2, 0
13371 ], buildLog);
13372 }
13373};
13374
13375module.exports = SegmentSelector;
13376
13377},{}],34:[function(_dereq_,module,exports){
13378// shim for using process in browser
13379var process = module.exports = {};
13380
13381// cached from whatever global is present so that test runners that stub it
13382// don't break things. But we need to wrap it in a try catch in case it is
13383// wrapped in strict mode code which doesn't define any globals. It's inside a
13384// function because try/catches deoptimize in certain engines.
13385
13386var cachedSetTimeout;
13387var cachedClearTimeout;
13388
13389function defaultSetTimout() {
13390 throw new Error('setTimeout has not been defined');
13391}
13392function defaultClearTimeout () {
13393 throw new Error('clearTimeout has not been defined');
13394}
13395(function () {
13396 try {
13397 if (typeof setTimeout === 'function') {
13398 cachedSetTimeout = setTimeout;
13399 } else {
13400 cachedSetTimeout = defaultSetTimout;
13401 }
13402 } catch (e) {
13403 cachedSetTimeout = defaultSetTimout;
13404 }
13405 try {
13406 if (typeof clearTimeout === 'function') {
13407 cachedClearTimeout = clearTimeout;
13408 } else {
13409 cachedClearTimeout = defaultClearTimeout;
13410 }
13411 } catch (e) {
13412 cachedClearTimeout = defaultClearTimeout;
13413 }
13414} ())
13415function runTimeout(fun) {
13416 if (cachedSetTimeout === setTimeout) {
13417 //normal enviroments in sane situations
13418 return setTimeout(fun, 0);
13419 }
13420 // if setTimeout wasn't available but was latter defined
13421 if ((cachedSetTimeout === defaultSetTimout || !cachedSetTimeout) && setTimeout) {
13422 cachedSetTimeout = setTimeout;
13423 return setTimeout(fun, 0);
13424 }
13425 try {
13426 // when when somebody has screwed with setTimeout but no I.E. maddness
13427 return cachedSetTimeout(fun, 0);
13428 } catch(e){
13429 try {
13430 // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally
13431 return cachedSetTimeout.call(null, fun, 0);
13432 } catch(e){
13433 // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error
13434 return cachedSetTimeout.call(this, fun, 0);
13435 }
13436 }
13437
13438
13439}
13440function runClearTimeout(marker) {
13441 if (cachedClearTimeout === clearTimeout) {
13442 //normal enviroments in sane situations
13443 return clearTimeout(marker);
13444 }
13445 // if clearTimeout wasn't available but was latter defined
13446 if ((cachedClearTimeout === defaultClearTimeout || !cachedClearTimeout) && clearTimeout) {
13447 cachedClearTimeout = clearTimeout;
13448 return clearTimeout(marker);
13449 }
13450 try {
13451 // when when somebody has screwed with setTimeout but no I.E. maddness
13452 return cachedClearTimeout(marker);
13453 } catch (e){
13454 try {
13455 // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally
13456 return cachedClearTimeout.call(null, marker);
13457 } catch (e){
13458 // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error.
13459 // Some versions of I.E. have different rules for clearTimeout vs setTimeout
13460 return cachedClearTimeout.call(this, marker);
13461 }
13462 }
13463
13464
13465
13466}
13467var queue = [];
13468var draining = false;
13469var currentQueue;
13470var queueIndex = -1;
13471
13472function cleanUpNextTick() {
13473 if (!draining || !currentQueue) {
13474 return;
13475 }
13476 draining = false;
13477 if (currentQueue.length) {
13478 queue = currentQueue.concat(queue);
13479 } else {
13480 queueIndex = -1;
13481 }
13482 if (queue.length) {
13483 drainQueue();
13484 }
13485}
13486
13487function drainQueue() {
13488 if (draining) {
13489 return;
13490 }
13491 var timeout = runTimeout(cleanUpNextTick);
13492 draining = true;
13493
13494 var len = queue.length;
13495 while(len) {
13496 currentQueue = queue;
13497 queue = [];
13498 while (++queueIndex < len) {
13499 if (currentQueue) {
13500 currentQueue[queueIndex].run();
13501 }
13502 }
13503 queueIndex = -1;
13504 len = queue.length;
13505 }
13506 currentQueue = null;
13507 draining = false;
13508 runClearTimeout(timeout);
13509}
13510
13511process.nextTick = function (fun) {
13512 var args = new Array(arguments.length - 1);
13513 if (arguments.length > 1) {
13514 for (var i = 1; i < arguments.length; i++) {
13515 args[i - 1] = arguments[i];
13516 }
13517 }
13518 queue.push(new Item(fun, args));
13519 if (queue.length === 1 && !draining) {
13520 runTimeout(drainQueue);
13521 }
13522};
13523
13524// v8 likes predictible objects
13525function Item(fun, array) {
13526 this.fun = fun;
13527 this.array = array;
13528}
13529Item.prototype.run = function () {
13530 this.fun.apply(null, this.array);
13531};
13532process.title = 'browser';
13533process.browser = true;
13534process.env = {};
13535process.argv = [];
13536process.version = ''; // empty string to avoid regexp issues
13537process.versions = {};
13538
13539function noop() {}
13540
13541process.on = noop;
13542process.addListener = noop;
13543process.once = noop;
13544process.off = noop;
13545process.removeListener = noop;
13546process.removeAllListeners = noop;
13547process.emit = noop;
13548process.prependListener = noop;
13549process.prependOnceListener = noop;
13550
13551process.listeners = function (name) { return [] }
13552
13553process.binding = function (name) {
13554 throw new Error('process.binding is not supported');
13555};
13556
13557process.cwd = function () { return '/' };
13558process.chdir = function (dir) {
13559 throw new Error('process.chdir is not supported');
13560};
13561process.umask = function() { return 0; };
13562
13563},{}],35:[function(_dereq_,module,exports){
13564// TinyColor v1.4.1
13565// https://github.com/bgrins/TinyColor
13566// Brian Grinstead, MIT License
13567
13568(function(Math) {
13569
13570var trimLeft = /^\s+/,
13571 trimRight = /\s+$/,
13572 tinyCounter = 0,
13573 mathRound = Math.round,
13574 mathMin = Math.min,
13575 mathMax = Math.max,
13576 mathRandom = Math.random;
13577
13578function tinycolor (color, opts) {
13579
13580 color = (color) ? color : '';
13581 opts = opts || { };
13582
13583 // If input is already a tinycolor, return itself
13584 if (color instanceof tinycolor) {
13585 return color;
13586 }
13587 // If we are called as a function, call using new instead
13588 if (!(this instanceof tinycolor)) {
13589 return new tinycolor(color, opts);
13590 }
13591
13592 var rgb = inputToRGB(color);
13593 this._originalInput = color,
13594 this._r = rgb.r,
13595 this._g = rgb.g,
13596 this._b = rgb.b,
13597 this._a = rgb.a,
13598 this._roundA = mathRound(100*this._a) / 100,
13599 this._format = opts.format || rgb.format;
13600 this._gradientType = opts.gradientType;
13601
13602 // Don't let the range of [0,255] come back in [0,1].
13603 // Potentially lose a little bit of precision here, but will fix issues where
13604 // .5 gets interpreted as half of the total, instead of half of 1
13605 // If it was supposed to be 128, this was already taken care of by `inputToRgb`
13606 if (this._r < 1) { this._r = mathRound(this._r); }
13607 if (this._g < 1) { this._g = mathRound(this._g); }
13608 if (this._b < 1) { this._b = mathRound(this._b); }
13609
13610 this._ok = rgb.ok;
13611 this._tc_id = tinyCounter++;
13612}
13613
13614tinycolor.prototype = {
13615 isDark: function() {
13616 return this.getBrightness() < 128;
13617 },
13618 isLight: function() {
13619 return !this.isDark();
13620 },
13621 isValid: function() {
13622 return this._ok;
13623 },
13624 getOriginalInput: function() {
13625 return this._originalInput;
13626 },
13627 getFormat: function() {
13628 return this._format;
13629 },
13630 getAlpha: function() {
13631 return this._a;
13632 },
13633 getBrightness: function() {
13634 //http://www.w3.org/TR/AERT#color-contrast
13635 var rgb = this.toRgb();
13636 return (rgb.r * 299 + rgb.g * 587 + rgb.b * 114) / 1000;
13637 },
13638 getLuminance: function() {
13639 //http://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef
13640 var rgb = this.toRgb();
13641 var RsRGB, GsRGB, BsRGB, R, G, B;
13642 RsRGB = rgb.r/255;
13643 GsRGB = rgb.g/255;
13644 BsRGB = rgb.b/255;
13645
13646 if (RsRGB <= 0.03928) {R = RsRGB / 12.92;} else {R = Math.pow(((RsRGB + 0.055) / 1.055), 2.4);}
13647 if (GsRGB <= 0.03928) {G = GsRGB / 12.92;} else {G = Math.pow(((GsRGB + 0.055) / 1.055), 2.4);}
13648 if (BsRGB <= 0.03928) {B = BsRGB / 12.92;} else {B = Math.pow(((BsRGB + 0.055) / 1.055), 2.4);}
13649 return (0.2126 * R) + (0.7152 * G) + (0.0722 * B);
13650 },
13651 setAlpha: function(value) {
13652 this._a = boundAlpha(value);
13653 this._roundA = mathRound(100*this._a) / 100;
13654 return this;
13655 },
13656 toHsv: function() {
13657 var hsv = rgbToHsv(this._r, this._g, this._b);
13658 return { h: hsv.h * 360, s: hsv.s, v: hsv.v, a: this._a };
13659 },
13660 toHsvString: function() {
13661 var hsv = rgbToHsv(this._r, this._g, this._b);
13662 var h = mathRound(hsv.h * 360), s = mathRound(hsv.s * 100), v = mathRound(hsv.v * 100);
13663 return (this._a == 1) ?
13664 "hsv(" + h + ", " + s + "%, " + v + "%)" :
13665 "hsva(" + h + ", " + s + "%, " + v + "%, "+ this._roundA + ")";
13666 },
13667 toHsl: function() {
13668 var hsl = rgbToHsl(this._r, this._g, this._b);
13669 return { h: hsl.h * 360, s: hsl.s, l: hsl.l, a: this._a };
13670 },
13671 toHslString: function() {
13672 var hsl = rgbToHsl(this._r, this._g, this._b);
13673 var h = mathRound(hsl.h * 360), s = mathRound(hsl.s * 100), l = mathRound(hsl.l * 100);
13674 return (this._a == 1) ?
13675 "hsl(" + h + ", " + s + "%, " + l + "%)" :
13676 "hsla(" + h + ", " + s + "%, " + l + "%, "+ this._roundA + ")";
13677 },
13678 toHex: function(allow3Char) {
13679 return rgbToHex(this._r, this._g, this._b, allow3Char);
13680 },
13681 toHexString: function(allow3Char) {
13682 return '#' + this.toHex(allow3Char);
13683 },
13684 toHex8: function(allow4Char) {
13685 return rgbaToHex(this._r, this._g, this._b, this._a, allow4Char);
13686 },
13687 toHex8String: function(allow4Char) {
13688 return '#' + this.toHex8(allow4Char);
13689 },
13690 toRgb: function() {
13691 return { r: mathRound(this._r), g: mathRound(this._g), b: mathRound(this._b), a: this._a };
13692 },
13693 toRgbString: function() {
13694 return (this._a == 1) ?
13695 "rgb(" + mathRound(this._r) + ", " + mathRound(this._g) + ", " + mathRound(this._b) + ")" :
13696 "rgba(" + mathRound(this._r) + ", " + mathRound(this._g) + ", " + mathRound(this._b) + ", " + this._roundA + ")";
13697 },
13698 toPercentageRgb: function() {
13699 return { r: mathRound(bound01(this._r, 255) * 100) + "%", g: mathRound(bound01(this._g, 255) * 100) + "%", b: mathRound(bound01(this._b, 255) * 100) + "%", a: this._a };
13700 },
13701 toPercentageRgbString: function() {
13702 return (this._a == 1) ?
13703 "rgb(" + mathRound(bound01(this._r, 255) * 100) + "%, " + mathRound(bound01(this._g, 255) * 100) + "%, " + mathRound(bound01(this._b, 255) * 100) + "%)" :
13704 "rgba(" + mathRound(bound01(this._r, 255) * 100) + "%, " + mathRound(bound01(this._g, 255) * 100) + "%, " + mathRound(bound01(this._b, 255) * 100) + "%, " + this._roundA + ")";
13705 },
13706 toName: function() {
13707 if (this._a === 0) {
13708 return "transparent";
13709 }
13710
13711 if (this._a < 1) {
13712 return false;
13713 }
13714
13715 return hexNames[rgbToHex(this._r, this._g, this._b, true)] || false;
13716 },
13717 toFilter: function(secondColor) {
13718 var hex8String = '#' + rgbaToArgbHex(this._r, this._g, this._b, this._a);
13719 var secondHex8String = hex8String;
13720 var gradientType = this._gradientType ? "GradientType = 1, " : "";
13721
13722 if (secondColor) {
13723 var s = tinycolor(secondColor);
13724 secondHex8String = '#' + rgbaToArgbHex(s._r, s._g, s._b, s._a);
13725 }
13726
13727 return "progid:DXImageTransform.Microsoft.gradient("+gradientType+"startColorstr="+hex8String+",endColorstr="+secondHex8String+")";
13728 },
13729 toString: function(format) {
13730 var formatSet = !!format;
13731 format = format || this._format;
13732
13733 var formattedString = false;
13734 var hasAlpha = this._a < 1 && this._a >= 0;
13735 var needsAlphaFormat = !formatSet && hasAlpha && (format === "hex" || format === "hex6" || format === "hex3" || format === "hex4" || format === "hex8" || format === "name");
13736
13737 if (needsAlphaFormat) {
13738 // Special case for "transparent", all other non-alpha formats
13739 // will return rgba when there is transparency.
13740 if (format === "name" && this._a === 0) {
13741 return this.toName();
13742 }
13743 return this.toRgbString();
13744 }
13745 if (format === "rgb") {
13746 formattedString = this.toRgbString();
13747 }
13748 if (format === "prgb") {
13749 formattedString = this.toPercentageRgbString();
13750 }
13751 if (format === "hex" || format === "hex6") {
13752 formattedString = this.toHexString();
13753 }
13754 if (format === "hex3") {
13755 formattedString = this.toHexString(true);
13756 }
13757 if (format === "hex4") {
13758 formattedString = this.toHex8String(true);
13759 }
13760 if (format === "hex8") {
13761 formattedString = this.toHex8String();
13762 }
13763 if (format === "name") {
13764 formattedString = this.toName();
13765 }
13766 if (format === "hsl") {
13767 formattedString = this.toHslString();
13768 }
13769 if (format === "hsv") {
13770 formattedString = this.toHsvString();
13771 }
13772
13773 return formattedString || this.toHexString();
13774 },
13775 clone: function() {
13776 return tinycolor(this.toString());
13777 },
13778
13779 _applyModification: function(fn, args) {
13780 var color = fn.apply(null, [this].concat([].slice.call(args)));
13781 this._r = color._r;
13782 this._g = color._g;
13783 this._b = color._b;
13784 this.setAlpha(color._a);
13785 return this;
13786 },
13787 lighten: function() {
13788 return this._applyModification(lighten, arguments);
13789 },
13790 brighten: function() {
13791 return this._applyModification(brighten, arguments);
13792 },
13793 darken: function() {
13794 return this._applyModification(darken, arguments);
13795 },
13796 desaturate: function() {
13797 return this._applyModification(desaturate, arguments);
13798 },
13799 saturate: function() {
13800 return this._applyModification(saturate, arguments);
13801 },
13802 greyscale: function() {
13803 return this._applyModification(greyscale, arguments);
13804 },
13805 spin: function() {
13806 return this._applyModification(spin, arguments);
13807 },
13808
13809 _applyCombination: function(fn, args) {
13810 return fn.apply(null, [this].concat([].slice.call(args)));
13811 },
13812 analogous: function() {
13813 return this._applyCombination(analogous, arguments);
13814 },
13815 complement: function() {
13816 return this._applyCombination(complement, arguments);
13817 },
13818 monochromatic: function() {
13819 return this._applyCombination(monochromatic, arguments);
13820 },
13821 splitcomplement: function() {
13822 return this._applyCombination(splitcomplement, arguments);
13823 },
13824 triad: function() {
13825 return this._applyCombination(triad, arguments);
13826 },
13827 tetrad: function() {
13828 return this._applyCombination(tetrad, arguments);
13829 }
13830};
13831
13832// If input is an object, force 1 into "1.0" to handle ratios properly
13833// String input requires "1.0" as input, so 1 will be treated as 1
13834tinycolor.fromRatio = function(color, opts) {
13835 if (typeof color == "object") {
13836 var newColor = {};
13837 for (var i in color) {
13838 if (color.hasOwnProperty(i)) {
13839 if (i === "a") {
13840 newColor[i] = color[i];
13841 }
13842 else {
13843 newColor[i] = convertToPercentage(color[i]);
13844 }
13845 }
13846 }
13847 color = newColor;
13848 }
13849
13850 return tinycolor(color, opts);
13851};
13852
13853// Given a string or object, convert that input to RGB
13854// Possible string inputs:
13855//
13856// "red"
13857// "#f00" or "f00"
13858// "#ff0000" or "ff0000"
13859// "#ff000000" or "ff000000"
13860// "rgb 255 0 0" or "rgb (255, 0, 0)"
13861// "rgb 1.0 0 0" or "rgb (1, 0, 0)"
13862// "rgba (255, 0, 0, 1)" or "rgba 255, 0, 0, 1"
13863// "rgba (1.0, 0, 0, 1)" or "rgba 1.0, 0, 0, 1"
13864// "hsl(0, 100%, 50%)" or "hsl 0 100% 50%"
13865// "hsla(0, 100%, 50%, 1)" or "hsla 0 100% 50%, 1"
13866// "hsv(0, 100%, 100%)" or "hsv 0 100% 100%"
13867//
13868function inputToRGB(color) {
13869
13870 var rgb = { r: 0, g: 0, b: 0 };
13871 var a = 1;
13872 var s = null;
13873 var v = null;
13874 var l = null;
13875 var ok = false;
13876 var format = false;
13877
13878 if (typeof color == "string") {
13879 color = stringInputToObject(color);
13880 }
13881
13882 if (typeof color == "object") {
13883 if (isValidCSSUnit(color.r) && isValidCSSUnit(color.g) && isValidCSSUnit(color.b)) {
13884 rgb = rgbToRgb(color.r, color.g, color.b);
13885 ok = true;
13886 format = String(color.r).substr(-1) === "%" ? "prgb" : "rgb";
13887 }
13888 else if (isValidCSSUnit(color.h) && isValidCSSUnit(color.s) && isValidCSSUnit(color.v)) {
13889 s = convertToPercentage(color.s);
13890 v = convertToPercentage(color.v);
13891 rgb = hsvToRgb(color.h, s, v);
13892 ok = true;
13893 format = "hsv";
13894 }
13895 else if (isValidCSSUnit(color.h) && isValidCSSUnit(color.s) && isValidCSSUnit(color.l)) {
13896 s = convertToPercentage(color.s);
13897 l = convertToPercentage(color.l);
13898 rgb = hslToRgb(color.h, s, l);
13899 ok = true;
13900 format = "hsl";
13901 }
13902
13903 if (color.hasOwnProperty("a")) {
13904 a = color.a;
13905 }
13906 }
13907
13908 a = boundAlpha(a);
13909
13910 return {
13911 ok: ok,
13912 format: color.format || format,
13913 r: mathMin(255, mathMax(rgb.r, 0)),
13914 g: mathMin(255, mathMax(rgb.g, 0)),
13915 b: mathMin(255, mathMax(rgb.b, 0)),
13916 a: a
13917 };
13918}
13919
13920
13921// Conversion Functions
13922// --------------------
13923
13924// `rgbToHsl`, `rgbToHsv`, `hslToRgb`, `hsvToRgb` modified from:
13925// <http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript>
13926
13927// `rgbToRgb`
13928// Handle bounds / percentage checking to conform to CSS color spec
13929// <http://www.w3.org/TR/css3-color/>
13930// *Assumes:* r, g, b in [0, 255] or [0, 1]
13931// *Returns:* { r, g, b } in [0, 255]
13932function rgbToRgb(r, g, b){
13933 return {
13934 r: bound01(r, 255) * 255,
13935 g: bound01(g, 255) * 255,
13936 b: bound01(b, 255) * 255
13937 };
13938}
13939
13940// `rgbToHsl`
13941// Converts an RGB color value to HSL.
13942// *Assumes:* r, g, and b are contained in [0, 255] or [0, 1]
13943// *Returns:* { h, s, l } in [0,1]
13944function rgbToHsl(r, g, b) {
13945
13946 r = bound01(r, 255);
13947 g = bound01(g, 255);
13948 b = bound01(b, 255);
13949
13950 var max = mathMax(r, g, b), min = mathMin(r, g, b);
13951 var h, s, l = (max + min) / 2;
13952
13953 if(max == min) {
13954 h = s = 0; // achromatic
13955 }
13956 else {
13957 var d = max - min;
13958 s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
13959 switch(max) {
13960 case r: h = (g - b) / d + (g < b ? 6 : 0); break;
13961 case g: h = (b - r) / d + 2; break;
13962 case b: h = (r - g) / d + 4; break;
13963 }
13964
13965 h /= 6;
13966 }
13967
13968 return { h: h, s: s, l: l };
13969}
13970
13971// `hslToRgb`
13972// Converts an HSL color value to RGB.
13973// *Assumes:* h is contained in [0, 1] or [0, 360] and s and l are contained [0, 1] or [0, 100]
13974// *Returns:* { r, g, b } in the set [0, 255]
13975function hslToRgb(h, s, l) {
13976 var r, g, b;
13977
13978 h = bound01(h, 360);
13979 s = bound01(s, 100);
13980 l = bound01(l, 100);
13981
13982 function hue2rgb(p, q, t) {
13983 if(t < 0) t += 1;
13984 if(t > 1) t -= 1;
13985 if(t < 1/6) return p + (q - p) * 6 * t;
13986 if(t < 1/2) return q;
13987 if(t < 2/3) return p + (q - p) * (2/3 - t) * 6;
13988 return p;
13989 }
13990
13991 if(s === 0) {
13992 r = g = b = l; // achromatic
13993 }
13994 else {
13995 var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
13996 var p = 2 * l - q;
13997 r = hue2rgb(p, q, h + 1/3);
13998 g = hue2rgb(p, q, h);
13999 b = hue2rgb(p, q, h - 1/3);
14000 }
14001
14002 return { r: r * 255, g: g * 255, b: b * 255 };
14003}
14004
14005// `rgbToHsv`
14006// Converts an RGB color value to HSV
14007// *Assumes:* r, g, and b are contained in the set [0, 255] or [0, 1]
14008// *Returns:* { h, s, v } in [0,1]
14009function rgbToHsv(r, g, b) {
14010
14011 r = bound01(r, 255);
14012 g = bound01(g, 255);
14013 b = bound01(b, 255);
14014
14015 var max = mathMax(r, g, b), min = mathMin(r, g, b);
14016 var h, s, v = max;
14017
14018 var d = max - min;
14019 s = max === 0 ? 0 : d / max;
14020
14021 if(max == min) {
14022 h = 0; // achromatic
14023 }
14024 else {
14025 switch(max) {
14026 case r: h = (g - b) / d + (g < b ? 6 : 0); break;
14027 case g: h = (b - r) / d + 2; break;
14028 case b: h = (r - g) / d + 4; break;
14029 }
14030 h /= 6;
14031 }
14032 return { h: h, s: s, v: v };
14033}
14034
14035// `hsvToRgb`
14036// Converts an HSV color value to RGB.
14037// *Assumes:* h is contained in [0, 1] or [0, 360] and s and v are contained in [0, 1] or [0, 100]
14038// *Returns:* { r, g, b } in the set [0, 255]
14039 function hsvToRgb(h, s, v) {
14040
14041 h = bound01(h, 360) * 6;
14042 s = bound01(s, 100);
14043 v = bound01(v, 100);
14044
14045 var i = Math.floor(h),
14046 f = h - i,
14047 p = v * (1 - s),
14048 q = v * (1 - f * s),
14049 t = v * (1 - (1 - f) * s),
14050 mod = i % 6,
14051 r = [v, q, p, p, t, v][mod],
14052 g = [t, v, v, q, p, p][mod],
14053 b = [p, p, t, v, v, q][mod];
14054
14055 return { r: r * 255, g: g * 255, b: b * 255 };
14056}
14057
14058// `rgbToHex`
14059// Converts an RGB color to hex
14060// Assumes r, g, and b are contained in the set [0, 255]
14061// Returns a 3 or 6 character hex
14062function rgbToHex(r, g, b, allow3Char) {
14063
14064 var hex = [
14065 pad2(mathRound(r).toString(16)),
14066 pad2(mathRound(g).toString(16)),
14067 pad2(mathRound(b).toString(16))
14068 ];
14069
14070 // Return a 3 character hex if possible
14071 if (allow3Char && hex[0].charAt(0) == hex[0].charAt(1) && hex[1].charAt(0) == hex[1].charAt(1) && hex[2].charAt(0) == hex[2].charAt(1)) {
14072 return hex[0].charAt(0) + hex[1].charAt(0) + hex[2].charAt(0);
14073 }
14074
14075 return hex.join("");
14076}
14077
14078// `rgbaToHex`
14079// Converts an RGBA color plus alpha transparency to hex
14080// Assumes r, g, b are contained in the set [0, 255] and
14081// a in [0, 1]. Returns a 4 or 8 character rgba hex
14082function rgbaToHex(r, g, b, a, allow4Char) {
14083
14084 var hex = [
14085 pad2(mathRound(r).toString(16)),
14086 pad2(mathRound(g).toString(16)),
14087 pad2(mathRound(b).toString(16)),
14088 pad2(convertDecimalToHex(a))
14089 ];
14090
14091 // Return a 4 character hex if possible
14092 if (allow4Char && hex[0].charAt(0) == hex[0].charAt(1) && hex[1].charAt(0) == hex[1].charAt(1) && hex[2].charAt(0) == hex[2].charAt(1) && hex[3].charAt(0) == hex[3].charAt(1)) {
14093 return hex[0].charAt(0) + hex[1].charAt(0) + hex[2].charAt(0) + hex[3].charAt(0);
14094 }
14095
14096 return hex.join("");
14097}
14098
14099// `rgbaToArgbHex`
14100// Converts an RGBA color to an ARGB Hex8 string
14101// Rarely used, but required for "toFilter()"
14102function rgbaToArgbHex(r, g, b, a) {
14103
14104 var hex = [
14105 pad2(convertDecimalToHex(a)),
14106 pad2(mathRound(r).toString(16)),
14107 pad2(mathRound(g).toString(16)),
14108 pad2(mathRound(b).toString(16))
14109 ];
14110
14111 return hex.join("");
14112}
14113
14114// `equals`
14115// Can be called with any tinycolor input
14116tinycolor.equals = function (color1, color2) {
14117 if (!color1 || !color2) { return false; }
14118 return tinycolor(color1).toRgbString() == tinycolor(color2).toRgbString();
14119};
14120
14121tinycolor.random = function() {
14122 return tinycolor.fromRatio({
14123 r: mathRandom(),
14124 g: mathRandom(),
14125 b: mathRandom()
14126 });
14127};
14128
14129
14130// Modification Functions
14131// ----------------------
14132// Thanks to less.js for some of the basics here
14133// <https://github.com/cloudhead/less.js/blob/master/lib/less/functions.js>
14134
14135function desaturate(color, amount) {
14136 amount = (amount === 0) ? 0 : (amount || 10);
14137 var hsl = tinycolor(color).toHsl();
14138 hsl.s -= amount / 100;
14139 hsl.s = clamp01(hsl.s);
14140 return tinycolor(hsl);
14141}
14142
14143function saturate(color, amount) {
14144 amount = (amount === 0) ? 0 : (amount || 10);
14145 var hsl = tinycolor(color).toHsl();
14146 hsl.s += amount / 100;
14147 hsl.s = clamp01(hsl.s);
14148 return tinycolor(hsl);
14149}
14150
14151function greyscale(color) {
14152 return tinycolor(color).desaturate(100);
14153}
14154
14155function lighten (color, amount) {
14156 amount = (amount === 0) ? 0 : (amount || 10);
14157 var hsl = tinycolor(color).toHsl();
14158 hsl.l += amount / 100;
14159 hsl.l = clamp01(hsl.l);
14160 return tinycolor(hsl);
14161}
14162
14163function brighten(color, amount) {
14164 amount = (amount === 0) ? 0 : (amount || 10);
14165 var rgb = tinycolor(color).toRgb();
14166 rgb.r = mathMax(0, mathMin(255, rgb.r - mathRound(255 * - (amount / 100))));
14167 rgb.g = mathMax(0, mathMin(255, rgb.g - mathRound(255 * - (amount / 100))));
14168 rgb.b = mathMax(0, mathMin(255, rgb.b - mathRound(255 * - (amount / 100))));
14169 return tinycolor(rgb);
14170}
14171
14172function darken (color, amount) {
14173 amount = (amount === 0) ? 0 : (amount || 10);
14174 var hsl = tinycolor(color).toHsl();
14175 hsl.l -= amount / 100;
14176 hsl.l = clamp01(hsl.l);
14177 return tinycolor(hsl);
14178}
14179
14180// Spin takes a positive or negative amount within [-360, 360] indicating the change of hue.
14181// Values outside of this range will be wrapped into this range.
14182function spin(color, amount) {
14183 var hsl = tinycolor(color).toHsl();
14184 var hue = (hsl.h + amount) % 360;
14185 hsl.h = hue < 0 ? 360 + hue : hue;
14186 return tinycolor(hsl);
14187}
14188
14189// Combination Functions
14190// ---------------------
14191// Thanks to jQuery xColor for some of the ideas behind these
14192// <https://github.com/infusion/jQuery-xcolor/blob/master/jquery.xcolor.js>
14193
14194function complement(color) {
14195 var hsl = tinycolor(color).toHsl();
14196 hsl.h = (hsl.h + 180) % 360;
14197 return tinycolor(hsl);
14198}
14199
14200function triad(color) {
14201 var hsl = tinycolor(color).toHsl();
14202 var h = hsl.h;
14203 return [
14204 tinycolor(color),
14205 tinycolor({ h: (h + 120) % 360, s: hsl.s, l: hsl.l }),
14206 tinycolor({ h: (h + 240) % 360, s: hsl.s, l: hsl.l })
14207 ];
14208}
14209
14210function tetrad(color) {
14211 var hsl = tinycolor(color).toHsl();
14212 var h = hsl.h;
14213 return [
14214 tinycolor(color),
14215 tinycolor({ h: (h + 90) % 360, s: hsl.s, l: hsl.l }),
14216 tinycolor({ h: (h + 180) % 360, s: hsl.s, l: hsl.l }),
14217 tinycolor({ h: (h + 270) % 360, s: hsl.s, l: hsl.l })
14218 ];
14219}
14220
14221function splitcomplement(color) {
14222 var hsl = tinycolor(color).toHsl();
14223 var h = hsl.h;
14224 return [
14225 tinycolor(color),
14226 tinycolor({ h: (h + 72) % 360, s: hsl.s, l: hsl.l}),
14227 tinycolor({ h: (h + 216) % 360, s: hsl.s, l: hsl.l})
14228 ];
14229}
14230
14231function analogous(color, results, slices) {
14232 results = results || 6;
14233 slices = slices || 30;
14234
14235 var hsl = tinycolor(color).toHsl();
14236 var part = 360 / slices;
14237 var ret = [tinycolor(color)];
14238
14239 for (hsl.h = ((hsl.h - (part * results >> 1)) + 720) % 360; --results; ) {
14240 hsl.h = (hsl.h + part) % 360;
14241 ret.push(tinycolor(hsl));
14242 }
14243 return ret;
14244}
14245
14246function monochromatic(color, results) {
14247 results = results || 6;
14248 var hsv = tinycolor(color).toHsv();
14249 var h = hsv.h, s = hsv.s, v = hsv.v;
14250 var ret = [];
14251 var modification = 1 / results;
14252
14253 while (results--) {
14254 ret.push(tinycolor({ h: h, s: s, v: v}));
14255 v = (v + modification) % 1;
14256 }
14257
14258 return ret;
14259}
14260
14261// Utility Functions
14262// ---------------------
14263
14264tinycolor.mix = function(color1, color2, amount) {
14265 amount = (amount === 0) ? 0 : (amount || 50);
14266
14267 var rgb1 = tinycolor(color1).toRgb();
14268 var rgb2 = tinycolor(color2).toRgb();
14269
14270 var p = amount / 100;
14271
14272 var rgba = {
14273 r: ((rgb2.r - rgb1.r) * p) + rgb1.r,
14274 g: ((rgb2.g - rgb1.g) * p) + rgb1.g,
14275 b: ((rgb2.b - rgb1.b) * p) + rgb1.b,
14276 a: ((rgb2.a - rgb1.a) * p) + rgb1.a
14277 };
14278
14279 return tinycolor(rgba);
14280};
14281
14282
14283// Readability Functions
14284// ---------------------
14285// <http://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef (WCAG Version 2)
14286
14287// `contrast`
14288// Analyze the 2 colors and returns the color contrast defined by (WCAG Version 2)
14289tinycolor.readability = function(color1, color2) {
14290 var c1 = tinycolor(color1);
14291 var c2 = tinycolor(color2);
14292 return (Math.max(c1.getLuminance(),c2.getLuminance())+0.05) / (Math.min(c1.getLuminance(),c2.getLuminance())+0.05);
14293};
14294
14295// `isReadable`
14296// Ensure that foreground and background color combinations meet WCAG2 guidelines.
14297// The third argument is an optional Object.
14298// the 'level' property states 'AA' or 'AAA' - if missing or invalid, it defaults to 'AA';
14299// the 'size' property states 'large' or 'small' - if missing or invalid, it defaults to 'small'.
14300// If the entire object is absent, isReadable defaults to {level:"AA",size:"small"}.
14301
14302// *Example*
14303// tinycolor.isReadable("#000", "#111") => false
14304// tinycolor.isReadable("#000", "#111",{level:"AA",size:"large"}) => false
14305tinycolor.isReadable = function(color1, color2, wcag2) {
14306 var readability = tinycolor.readability(color1, color2);
14307 var wcag2Parms, out;
14308
14309 out = false;
14310
14311 wcag2Parms = validateWCAG2Parms(wcag2);
14312 switch (wcag2Parms.level + wcag2Parms.size) {
14313 case "AAsmall":
14314 case "AAAlarge":
14315 out = readability >= 4.5;
14316 break;
14317 case "AAlarge":
14318 out = readability >= 3;
14319 break;
14320 case "AAAsmall":
14321 out = readability >= 7;
14322 break;
14323 }
14324 return out;
14325
14326};
14327
14328// `mostReadable`
14329// Given a base color and a list of possible foreground or background
14330// colors for that base, returns the most readable color.
14331// Optionally returns Black or White if the most readable color is unreadable.
14332// *Example*
14333// tinycolor.mostReadable(tinycolor.mostReadable("#123", ["#124", "#125"],{includeFallbackColors:false}).toHexString(); // "#112255"
14334// tinycolor.mostReadable(tinycolor.mostReadable("#123", ["#124", "#125"],{includeFallbackColors:true}).toHexString(); // "#ffffff"
14335// tinycolor.mostReadable("#a8015a", ["#faf3f3"],{includeFallbackColors:true,level:"AAA",size:"large"}).toHexString(); // "#faf3f3"
14336// tinycolor.mostReadable("#a8015a", ["#faf3f3"],{includeFallbackColors:true,level:"AAA",size:"small"}).toHexString(); // "#ffffff"
14337tinycolor.mostReadable = function(baseColor, colorList, args) {
14338 var bestColor = null;
14339 var bestScore = 0;
14340 var readability;
14341 var includeFallbackColors, level, size ;
14342 args = args || {};
14343 includeFallbackColors = args.includeFallbackColors ;
14344 level = args.level;
14345 size = args.size;
14346
14347 for (var i= 0; i < colorList.length ; i++) {
14348 readability = tinycolor.readability(baseColor, colorList[i]);
14349 if (readability > bestScore) {
14350 bestScore = readability;
14351 bestColor = tinycolor(colorList[i]);
14352 }
14353 }
14354
14355 if (tinycolor.isReadable(baseColor, bestColor, {"level":level,"size":size}) || !includeFallbackColors) {
14356 return bestColor;
14357 }
14358 else {
14359 args.includeFallbackColors=false;
14360 return tinycolor.mostReadable(baseColor,["#fff", "#000"],args);
14361 }
14362};
14363
14364
14365// Big List of Colors
14366// ------------------
14367// <http://www.w3.org/TR/css3-color/#svg-color>
14368var names = tinycolor.names = {
14369 aliceblue: "f0f8ff",
14370 antiquewhite: "faebd7",
14371 aqua: "0ff",
14372 aquamarine: "7fffd4",
14373 azure: "f0ffff",
14374 beige: "f5f5dc",
14375 bisque: "ffe4c4",
14376 black: "000",
14377 blanchedalmond: "ffebcd",
14378 blue: "00f",
14379 blueviolet: "8a2be2",
14380 brown: "a52a2a",
14381 burlywood: "deb887",
14382 burntsienna: "ea7e5d",
14383 cadetblue: "5f9ea0",
14384 chartreuse: "7fff00",
14385 chocolate: "d2691e",
14386 coral: "ff7f50",
14387 cornflowerblue: "6495ed",
14388 cornsilk: "fff8dc",
14389 crimson: "dc143c",
14390 cyan: "0ff",
14391 darkblue: "00008b",
14392 darkcyan: "008b8b",
14393 darkgoldenrod: "b8860b",
14394 darkgray: "a9a9a9",
14395 darkgreen: "006400",
14396 darkgrey: "a9a9a9",
14397 darkkhaki: "bdb76b",
14398 darkmagenta: "8b008b",
14399 darkolivegreen: "556b2f",
14400 darkorange: "ff8c00",
14401 darkorchid: "9932cc",
14402 darkred: "8b0000",
14403 darksalmon: "e9967a",
14404 darkseagreen: "8fbc8f",
14405 darkslateblue: "483d8b",
14406 darkslategray: "2f4f4f",
14407 darkslategrey: "2f4f4f",
14408 darkturquoise: "00ced1",
14409 darkviolet: "9400d3",
14410 deeppink: "ff1493",
14411 deepskyblue: "00bfff",
14412 dimgray: "696969",
14413 dimgrey: "696969",
14414 dodgerblue: "1e90ff",
14415 firebrick: "b22222",
14416 floralwhite: "fffaf0",
14417 forestgreen: "228b22",
14418 fuchsia: "f0f",
14419 gainsboro: "dcdcdc",
14420 ghostwhite: "f8f8ff",
14421 gold: "ffd700",
14422 goldenrod: "daa520",
14423 gray: "808080",
14424 green: "008000",
14425 greenyellow: "adff2f",
14426 grey: "808080",
14427 honeydew: "f0fff0",
14428 hotpink: "ff69b4",
14429 indianred: "cd5c5c",
14430 indigo: "4b0082",
14431 ivory: "fffff0",
14432 khaki: "f0e68c",
14433 lavender: "e6e6fa",
14434 lavenderblush: "fff0f5",
14435 lawngreen: "7cfc00",
14436 lemonchiffon: "fffacd",
14437 lightblue: "add8e6",
14438 lightcoral: "f08080",
14439 lightcyan: "e0ffff",
14440 lightgoldenrodyellow: "fafad2",
14441 lightgray: "d3d3d3",
14442 lightgreen: "90ee90",
14443 lightgrey: "d3d3d3",
14444 lightpink: "ffb6c1",
14445 lightsalmon: "ffa07a",
14446 lightseagreen: "20b2aa",
14447 lightskyblue: "87cefa",
14448 lightslategray: "789",
14449 lightslategrey: "789",
14450 lightsteelblue: "b0c4de",
14451 lightyellow: "ffffe0",
14452 lime: "0f0",
14453 limegreen: "32cd32",
14454 linen: "faf0e6",
14455 magenta: "f0f",
14456 maroon: "800000",
14457 mediumaquamarine: "66cdaa",
14458 mediumblue: "0000cd",
14459 mediumorchid: "ba55d3",
14460 mediumpurple: "9370db",
14461 mediumseagreen: "3cb371",
14462 mediumslateblue: "7b68ee",
14463 mediumspringgreen: "00fa9a",
14464 mediumturquoise: "48d1cc",
14465 mediumvioletred: "c71585",
14466 midnightblue: "191970",
14467 mintcream: "f5fffa",
14468 mistyrose: "ffe4e1",
14469 moccasin: "ffe4b5",
14470 navajowhite: "ffdead",
14471 navy: "000080",
14472 oldlace: "fdf5e6",
14473 olive: "808000",
14474 olivedrab: "6b8e23",
14475 orange: "ffa500",
14476 orangered: "ff4500",
14477 orchid: "da70d6",
14478 palegoldenrod: "eee8aa",
14479 palegreen: "98fb98",
14480 paleturquoise: "afeeee",
14481 palevioletred: "db7093",
14482 papayawhip: "ffefd5",
14483 peachpuff: "ffdab9",
14484 peru: "cd853f",
14485 pink: "ffc0cb",
14486 plum: "dda0dd",
14487 powderblue: "b0e0e6",
14488 purple: "800080",
14489 rebeccapurple: "663399",
14490 red: "f00",
14491 rosybrown: "bc8f8f",
14492 royalblue: "4169e1",
14493 saddlebrown: "8b4513",
14494 salmon: "fa8072",
14495 sandybrown: "f4a460",
14496 seagreen: "2e8b57",
14497 seashell: "fff5ee",
14498 sienna: "a0522d",
14499 silver: "c0c0c0",
14500 skyblue: "87ceeb",
14501 slateblue: "6a5acd",
14502 slategray: "708090",
14503 slategrey: "708090",
14504 snow: "fffafa",
14505 springgreen: "00ff7f",
14506 steelblue: "4682b4",
14507 tan: "d2b48c",
14508 teal: "008080",
14509 thistle: "d8bfd8",
14510 tomato: "ff6347",
14511 turquoise: "40e0d0",
14512 violet: "ee82ee",
14513 wheat: "f5deb3",
14514 white: "fff",
14515 whitesmoke: "f5f5f5",
14516 yellow: "ff0",
14517 yellowgreen: "9acd32"
14518};
14519
14520// Make it easy to access colors via `hexNames[hex]`
14521var hexNames = tinycolor.hexNames = flip(names);
14522
14523
14524// Utilities
14525// ---------
14526
14527// `{ 'name1': 'val1' }` becomes `{ 'val1': 'name1' }`
14528function flip(o) {
14529 var flipped = { };
14530 for (var i in o) {
14531 if (o.hasOwnProperty(i)) {
14532 flipped[o[i]] = i;
14533 }
14534 }
14535 return flipped;
14536}
14537
14538// Return a valid alpha value [0,1] with all invalid values being set to 1
14539function boundAlpha(a) {
14540 a = parseFloat(a);
14541
14542 if (isNaN(a) || a < 0 || a > 1) {
14543 a = 1;
14544 }
14545
14546 return a;
14547}
14548
14549// Take input from [0, n] and return it as [0, 1]
14550function bound01(n, max) {
14551 if (isOnePointZero(n)) { n = "100%"; }
14552
14553 var processPercent = isPercentage(n);
14554 n = mathMin(max, mathMax(0, parseFloat(n)));
14555
14556 // Automatically convert percentage into number
14557 if (processPercent) {
14558 n = parseInt(n * max, 10) / 100;
14559 }
14560
14561 // Handle floating point rounding errors
14562 if ((Math.abs(n - max) < 0.000001)) {
14563 return 1;
14564 }
14565
14566 // Convert into [0, 1] range if it isn't already
14567 return (n % max) / parseFloat(max);
14568}
14569
14570// Force a number between 0 and 1
14571function clamp01(val) {
14572 return mathMin(1, mathMax(0, val));
14573}
14574
14575// Parse a base-16 hex value into a base-10 integer
14576function parseIntFromHex(val) {
14577 return parseInt(val, 16);
14578}
14579
14580// Need to handle 1.0 as 100%, since once it is a number, there is no difference between it and 1
14581// <http://stackoverflow.com/questions/7422072/javascript-how-to-detect-number-as-a-decimal-including-1-0>
14582function isOnePointZero(n) {
14583 return typeof n == "string" && n.indexOf('.') != -1 && parseFloat(n) === 1;
14584}
14585
14586// Check to see if string passed in is a percentage
14587function isPercentage(n) {
14588 return typeof n === "string" && n.indexOf('%') != -1;
14589}
14590
14591// Force a hex value to have 2 characters
14592function pad2(c) {
14593 return c.length == 1 ? '0' + c : '' + c;
14594}
14595
14596// Replace a decimal with it's percentage value
14597function convertToPercentage(n) {
14598 if (n <= 1) {
14599 n = (n * 100) + "%";
14600 }
14601
14602 return n;
14603}
14604
14605// Converts a decimal to a hex value
14606function convertDecimalToHex(d) {
14607 return Math.round(parseFloat(d) * 255).toString(16);
14608}
14609// Converts a hex value to a decimal
14610function convertHexToDecimal(h) {
14611 return (parseIntFromHex(h) / 255);
14612}
14613
14614var matchers = (function() {
14615
14616 // <http://www.w3.org/TR/css3-values/#integers>
14617 var CSS_INTEGER = "[-\\+]?\\d+%?";
14618
14619 // <http://www.w3.org/TR/css3-values/#number-value>
14620 var CSS_NUMBER = "[-\\+]?\\d*\\.\\d+%?";
14621
14622 // Allow positive/negative integer/number. Don't capture the either/or, just the entire outcome.
14623 var CSS_UNIT = "(?:" + CSS_NUMBER + ")|(?:" + CSS_INTEGER + ")";
14624
14625 // Actual matching.
14626 // Parentheses and commas are optional, but not required.
14627 // Whitespace can take the place of commas or opening paren
14628 var PERMISSIVE_MATCH3 = "[\\s|\\(]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")\\s*\\)?";
14629 var PERMISSIVE_MATCH4 = "[\\s|\\(]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")\\s*\\)?";
14630
14631 return {
14632 CSS_UNIT: new RegExp(CSS_UNIT),
14633 rgb: new RegExp("rgb" + PERMISSIVE_MATCH3),
14634 rgba: new RegExp("rgba" + PERMISSIVE_MATCH4),
14635 hsl: new RegExp("hsl" + PERMISSIVE_MATCH3),
14636 hsla: new RegExp("hsla" + PERMISSIVE_MATCH4),
14637 hsv: new RegExp("hsv" + PERMISSIVE_MATCH3),
14638 hsva: new RegExp("hsva" + PERMISSIVE_MATCH4),
14639 hex3: /^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,
14640 hex6: /^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/,
14641 hex4: /^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,
14642 hex8: /^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/
14643 };
14644})();
14645
14646// `isValidCSSUnit`
14647// Take in a single string / number and check to see if it looks like a CSS unit
14648// (see `matchers` above for definition).
14649function isValidCSSUnit(color) {
14650 return !!matchers.CSS_UNIT.exec(color);
14651}
14652
14653// `stringInputToObject`
14654// Permissive string parsing. Take in a number of formats, and output an object
14655// based on detected format. Returns `{ r, g, b }` or `{ h, s, l }` or `{ h, s, v}`
14656function stringInputToObject(color) {
14657
14658 color = color.replace(trimLeft,'').replace(trimRight, '').toLowerCase();
14659 var named = false;
14660 if (names[color]) {
14661 color = names[color];
14662 named = true;
14663 }
14664 else if (color == 'transparent') {
14665 return { r: 0, g: 0, b: 0, a: 0, format: "name" };
14666 }
14667
14668 // Try to match string input using regular expressions.
14669 // Keep most of the number bounding out of this function - don't worry about [0,1] or [0,100] or [0,360]
14670 // Just return an object and let the conversion functions handle that.
14671 // This way the result will be the same whether the tinycolor is initialized with string or object.
14672 var match;
14673 if ((match = matchers.rgb.exec(color))) {
14674 return { r: match[1], g: match[2], b: match[3] };
14675 }
14676 if ((match = matchers.rgba.exec(color))) {
14677 return { r: match[1], g: match[2], b: match[3], a: match[4] };
14678 }
14679 if ((match = matchers.hsl.exec(color))) {
14680 return { h: match[1], s: match[2], l: match[3] };
14681 }
14682 if ((match = matchers.hsla.exec(color))) {
14683 return { h: match[1], s: match[2], l: match[3], a: match[4] };
14684 }
14685 if ((match = matchers.hsv.exec(color))) {
14686 return { h: match[1], s: match[2], v: match[3] };
14687 }
14688 if ((match = matchers.hsva.exec(color))) {
14689 return { h: match[1], s: match[2], v: match[3], a: match[4] };
14690 }
14691 if ((match = matchers.hex8.exec(color))) {
14692 return {
14693 r: parseIntFromHex(match[1]),
14694 g: parseIntFromHex(match[2]),
14695 b: parseIntFromHex(match[3]),
14696 a: convertHexToDecimal(match[4]),
14697 format: named ? "name" : "hex8"
14698 };
14699 }
14700 if ((match = matchers.hex6.exec(color))) {
14701 return {
14702 r: parseIntFromHex(match[1]),
14703 g: parseIntFromHex(match[2]),
14704 b: parseIntFromHex(match[3]),
14705 format: named ? "name" : "hex"
14706 };
14707 }
14708 if ((match = matchers.hex4.exec(color))) {
14709 return {
14710 r: parseIntFromHex(match[1] + '' + match[1]),
14711 g: parseIntFromHex(match[2] + '' + match[2]),
14712 b: parseIntFromHex(match[3] + '' + match[3]),
14713 a: convertHexToDecimal(match[4] + '' + match[4]),
14714 format: named ? "name" : "hex8"
14715 };
14716 }
14717 if ((match = matchers.hex3.exec(color))) {
14718 return {
14719 r: parseIntFromHex(match[1] + '' + match[1]),
14720 g: parseIntFromHex(match[2] + '' + match[2]),
14721 b: parseIntFromHex(match[3] + '' + match[3]),
14722 format: named ? "name" : "hex"
14723 };
14724 }
14725
14726 return false;
14727}
14728
14729function validateWCAG2Parms(parms) {
14730 // return valid WCAG2 parms for isReadable.
14731 // If input parms are invalid, return {"level":"AA", "size":"small"}
14732 var level, size;
14733 parms = parms || {"level":"AA", "size":"small"};
14734 level = (parms.level || "AA").toUpperCase();
14735 size = (parms.size || "small").toLowerCase();
14736 if (level !== "AA" && level !== "AAA") {
14737 level = "AA";
14738 }
14739 if (size !== "small" && size !== "large") {
14740 size = "small";
14741 }
14742 return {"level":level, "size":size};
14743}
14744
14745// Node: Export function
14746if (typeof module !== "undefined" && module.exports) {
14747 module.exports = tinycolor;
14748}
14749// AMD/requirejs: Define the module
14750else if (typeof define === 'function' && define.amd) {
14751 define(function () {return tinycolor;});
14752}
14753// Browser: Expose to window
14754else {
14755 window.tinycolor = tinycolor;
14756}
14757
14758})(Math);
14759
14760},{}],36:[function(_dereq_,module,exports){
14761/**
14762* Copyright 2012-2020, Plotly, Inc.
14763* All rights reserved.
14764*
14765* This source code is licensed under the MIT license found in the
14766* LICENSE file in the root directory of this source tree.
14767*/
14768
14769'use strict';
14770
14771/**
14772 * All paths are tuned for maximum scalability of the arrowhead,
14773 * ie throughout arrowwidth=0.3..3 the head is joined smoothly
14774 * to the line, with the line coming from the left and ending at (0, 0).
14775 *
14776 * `backoff` is the distance to move the arrowhead and the end of the line,
14777 * in order that the arrowhead points to the desired place, either at
14778 * the tip of the arrow or (in the case of circle or square)
14779 * the center of the symbol.
14780 *
14781 * `noRotate`, if truthy, says that this arrowhead should not rotate with the
14782 * arrow. That's the case for squares, which should always be straight, and
14783 * circles, for which it's irrelevant.
14784 */
14785
14786module.exports = [
14787 // no arrow
14788 {
14789 path: '',
14790 backoff: 0
14791 },
14792 // wide with flat back
14793 {
14794 path: 'M-2.4,-3V3L0.6,0Z',
14795 backoff: 0.6
14796 },
14797 // narrower with flat back
14798 {
14799 path: 'M-3.7,-2.5V2.5L1.3,0Z',
14800 backoff: 1.3
14801 },
14802 // barbed
14803 {
14804 path: 'M-4.45,-3L-1.65,-0.2V0.2L-4.45,3L1.55,0Z',
14805 backoff: 1.55
14806 },
14807 // wide line-drawn
14808 {
14809 path: 'M-2.2,-2.2L-0.2,-0.2V0.2L-2.2,2.2L-1.4,3L1.6,0L-1.4,-3Z',
14810 backoff: 1.6
14811 },
14812 // narrower line-drawn
14813 {
14814 path: 'M-4.4,-2.1L-0.6,-0.2V0.2L-4.4,2.1L-4,3L2,0L-4,-3Z',
14815 backoff: 2
14816 },
14817 // circle
14818 {
14819 path: 'M2,0A2,2 0 1,1 0,-2A2,2 0 0,1 2,0Z',
14820 backoff: 0,
14821 noRotate: true
14822 },
14823 // square
14824 {
14825 path: 'M2,2V-2H-2V2Z',
14826 backoff: 0,
14827 noRotate: true
14828 }
14829];
14830
14831},{}],37:[function(_dereq_,module,exports){
14832/**
14833* Copyright 2012-2020, Plotly, Inc.
14834* All rights reserved.
14835*
14836* This source code is licensed under the MIT license found in the
14837* LICENSE file in the root directory of this source tree.
14838*/
14839
14840'use strict';
14841
14842var ARROWPATHS = _dereq_('./arrow_paths');
14843var fontAttrs = _dereq_('../../plots/font_attributes');
14844var cartesianConstants = _dereq_('../../plots/cartesian/constants');
14845var templatedArray = _dereq_('../../plot_api/plot_template').templatedArray;
14846
14847
14848module.exports = templatedArray('annotation', {
14849 visible: {
14850 valType: 'boolean',
14851
14852 dflt: true,
14853 editType: 'calc+arraydraw',
14854
14855 },
14856
14857 text: {
14858 valType: 'string',
14859
14860 editType: 'calc+arraydraw',
14861
14862 },
14863 textangle: {
14864 valType: 'angle',
14865 dflt: 0,
14866
14867 editType: 'calc+arraydraw',
14868
14869 },
14870 font: fontAttrs({
14871 editType: 'calc+arraydraw',
14872 colorEditType: 'arraydraw',
14873
14874 }),
14875 width: {
14876 valType: 'number',
14877 min: 1,
14878 dflt: null,
14879
14880 editType: 'calc+arraydraw',
14881
14882 },
14883 height: {
14884 valType: 'number',
14885 min: 1,
14886 dflt: null,
14887
14888 editType: 'calc+arraydraw',
14889
14890 },
14891 opacity: {
14892 valType: 'number',
14893 min: 0,
14894 max: 1,
14895 dflt: 1,
14896
14897 editType: 'arraydraw',
14898
14899 },
14900 align: {
14901 valType: 'enumerated',
14902 values: ['left', 'center', 'right'],
14903 dflt: 'center',
14904
14905 editType: 'arraydraw',
14906
14907 },
14908 valign: {
14909 valType: 'enumerated',
14910 values: ['top', 'middle', 'bottom'],
14911 dflt: 'middle',
14912
14913 editType: 'arraydraw',
14914
14915 },
14916 bgcolor: {
14917 valType: 'color',
14918 dflt: 'rgba(0,0,0,0)',
14919
14920 editType: 'arraydraw',
14921
14922 },
14923 bordercolor: {
14924 valType: 'color',
14925 dflt: 'rgba(0,0,0,0)',
14926
14927 editType: 'arraydraw',
14928
14929 },
14930 borderpad: {
14931 valType: 'number',
14932 min: 0,
14933 dflt: 1,
14934
14935 editType: 'calc+arraydraw',
14936
14937 },
14938 borderwidth: {
14939 valType: 'number',
14940 min: 0,
14941 dflt: 1,
14942
14943 editType: 'calc+arraydraw',
14944
14945 },
14946 // arrow
14947 showarrow: {
14948 valType: 'boolean',
14949 dflt: true,
14950
14951 editType: 'calc+arraydraw',
14952
14953 },
14954 arrowcolor: {
14955 valType: 'color',
14956
14957 editType: 'arraydraw',
14958
14959 },
14960 arrowhead: {
14961 valType: 'integer',
14962 min: 0,
14963 max: ARROWPATHS.length,
14964 dflt: 1,
14965
14966 editType: 'arraydraw',
14967
14968 },
14969 startarrowhead: {
14970 valType: 'integer',
14971 min: 0,
14972 max: ARROWPATHS.length,
14973 dflt: 1,
14974
14975 editType: 'arraydraw',
14976
14977 },
14978 arrowside: {
14979 valType: 'flaglist',
14980 flags: ['end', 'start'],
14981 extras: ['none'],
14982 dflt: 'end',
14983
14984 editType: 'arraydraw',
14985
14986 },
14987 arrowsize: {
14988 valType: 'number',
14989 min: 0.3,
14990 dflt: 1,
14991
14992 editType: 'calc+arraydraw',
14993
14994 },
14995 startarrowsize: {
14996 valType: 'number',
14997 min: 0.3,
14998 dflt: 1,
14999
15000 editType: 'calc+arraydraw',
15001
15002 },
15003 arrowwidth: {
15004 valType: 'number',
15005 min: 0.1,
15006
15007 editType: 'calc+arraydraw',
15008
15009 },
15010 standoff: {
15011 valType: 'number',
15012 min: 0,
15013 dflt: 0,
15014
15015 editType: 'calc+arraydraw',
15016
15017 },
15018 startstandoff: {
15019 valType: 'number',
15020 min: 0,
15021 dflt: 0,
15022
15023 editType: 'calc+arraydraw',
15024
15025 },
15026 ax: {
15027 valType: 'any',
15028
15029 editType: 'calc+arraydraw',
15030
15031 },
15032 ay: {
15033 valType: 'any',
15034
15035 editType: 'calc+arraydraw',
15036
15037 },
15038 axref: {
15039 valType: 'enumerated',
15040 dflt: 'pixel',
15041 values: [
15042 'pixel',
15043 cartesianConstants.idRegex.x.toString()
15044 ],
15045
15046 editType: 'calc',
15047
15048 },
15049 ayref: {
15050 valType: 'enumerated',
15051 dflt: 'pixel',
15052 values: [
15053 'pixel',
15054 cartesianConstants.idRegex.y.toString()
15055 ],
15056
15057 editType: 'calc',
15058
15059 },
15060 // positioning
15061 xref: {
15062 valType: 'enumerated',
15063 values: [
15064 'paper',
15065 cartesianConstants.idRegex.x.toString()
15066 ],
15067
15068 editType: 'calc',
15069
15070 },
15071 x: {
15072 valType: 'any',
15073
15074 editType: 'calc+arraydraw',
15075
15076 },
15077 xanchor: {
15078 valType: 'enumerated',
15079 values: ['auto', 'left', 'center', 'right'],
15080 dflt: 'auto',
15081
15082 editType: 'calc+arraydraw',
15083
15084 },
15085 xshift: {
15086 valType: 'number',
15087 dflt: 0,
15088
15089 editType: 'calc+arraydraw',
15090
15091 },
15092 yref: {
15093 valType: 'enumerated',
15094 values: [
15095 'paper',
15096 cartesianConstants.idRegex.y.toString()
15097 ],
15098
15099 editType: 'calc',
15100
15101 },
15102 y: {
15103 valType: 'any',
15104
15105 editType: 'calc+arraydraw',
15106
15107 },
15108 yanchor: {
15109 valType: 'enumerated',
15110 values: ['auto', 'top', 'middle', 'bottom'],
15111 dflt: 'auto',
15112
15113 editType: 'calc+arraydraw',
15114
15115 },
15116 yshift: {
15117 valType: 'number',
15118 dflt: 0,
15119
15120 editType: 'calc+arraydraw',
15121
15122 },
15123 clicktoshow: {
15124 valType: 'enumerated',
15125 values: [false, 'onoff', 'onout'],
15126 dflt: false,
15127
15128 editType: 'arraydraw',
15129
15130 },
15131 xclick: {
15132 valType: 'any',
15133
15134 editType: 'arraydraw',
15135
15136 },
15137 yclick: {
15138 valType: 'any',
15139
15140 editType: 'arraydraw',
15141
15142 },
15143 hovertext: {
15144 valType: 'string',
15145
15146 editType: 'arraydraw',
15147
15148 },
15149 hoverlabel: {
15150 bgcolor: {
15151 valType: 'color',
15152
15153 editType: 'arraydraw',
15154
15155 },
15156 bordercolor: {
15157 valType: 'color',
15158
15159 editType: 'arraydraw',
15160
15161 },
15162 font: fontAttrs({
15163 editType: 'arraydraw',
15164
15165 }),
15166 editType: 'arraydraw'
15167 },
15168 captureevents: {
15169 valType: 'boolean',
15170
15171 editType: 'arraydraw',
15172
15173 },
15174 editType: 'calc',
15175
15176 _deprecated: {
15177 ref: {
15178 valType: 'string',
15179
15180 editType: 'calc',
15181
15182 }
15183 }
15184});
15185
15186},{"../../plot_api/plot_template":212,"../../plots/cartesian/constants":228,"../../plots/font_attributes":250,"./arrow_paths":36}],38:[function(_dereq_,module,exports){
15187/**
15188* Copyright 2012-2020, Plotly, Inc.
15189* All rights reserved.
15190*
15191* This source code is licensed under the MIT license found in the
15192* LICENSE file in the root directory of this source tree.
15193*/
15194
15195
15196'use strict';
15197
15198var Lib = _dereq_('../../lib');
15199var Axes = _dereq_('../../plots/cartesian/axes');
15200
15201var draw = _dereq_('./draw').draw;
15202
15203
15204module.exports = function calcAutorange(gd) {
15205 var fullLayout = gd._fullLayout;
15206 var annotationList = Lib.filterVisible(fullLayout.annotations);
15207
15208 if(annotationList.length && gd._fullData.length) {
15209 return Lib.syncOrAsync([draw, annAutorange], gd);
15210 }
15211};
15212
15213function annAutorange(gd) {
15214 var fullLayout = gd._fullLayout;
15215
15216 // find the bounding boxes for each of these annotations'
15217 // relative to their anchor points
15218 // use the arrow and the text bg rectangle,
15219 // as the whole anno may include hidden text in its bbox
15220 Lib.filterVisible(fullLayout.annotations).forEach(function(ann) {
15221 var xa = Axes.getFromId(gd, ann.xref);
15222 var ya = Axes.getFromId(gd, ann.yref);
15223
15224 ann._extremes = {};
15225 if(xa) calcAxisExpansion(ann, xa);
15226 if(ya) calcAxisExpansion(ann, ya);
15227 });
15228}
15229
15230function calcAxisExpansion(ann, ax) {
15231 var axId = ax._id;
15232 var letter = axId.charAt(0);
15233 var pos = ann[letter];
15234 var apos = ann['a' + letter];
15235 var ref = ann[letter + 'ref'];
15236 var aref = ann['a' + letter + 'ref'];
15237 var padplus = ann['_' + letter + 'padplus'];
15238 var padminus = ann['_' + letter + 'padminus'];
15239 var shift = {x: 1, y: -1}[letter] * ann[letter + 'shift'];
15240 var headSize = 3 * ann.arrowsize * ann.arrowwidth || 0;
15241 var headPlus = headSize + shift;
15242 var headMinus = headSize - shift;
15243 var startHeadSize = 3 * ann.startarrowsize * ann.arrowwidth || 0;
15244 var startHeadPlus = startHeadSize + shift;
15245 var startHeadMinus = startHeadSize - shift;
15246 var extremes;
15247
15248 if(aref === ref) {
15249 // expand for the arrowhead (padded by arrowhead)
15250 var extremeArrowHead = Axes.findExtremes(ax, [ax.r2c(pos)], {
15251 ppadplus: headPlus,
15252 ppadminus: headMinus
15253 });
15254 // again for the textbox (padded by textbox)
15255 var extremeText = Axes.findExtremes(ax, [ax.r2c(apos)], {
15256 ppadplus: Math.max(padplus, startHeadPlus),
15257 ppadminus: Math.max(padminus, startHeadMinus)
15258 });
15259 extremes = {
15260 min: [extremeArrowHead.min[0], extremeText.min[0]],
15261 max: [extremeArrowHead.max[0], extremeText.max[0]]
15262 };
15263 } else {
15264 startHeadPlus = apos ? startHeadPlus + apos : startHeadPlus;
15265 startHeadMinus = apos ? startHeadMinus - apos : startHeadMinus;
15266 extremes = Axes.findExtremes(ax, [ax.r2c(pos)], {
15267 ppadplus: Math.max(padplus, headPlus, startHeadPlus),
15268 ppadminus: Math.max(padminus, headMinus, startHeadMinus)
15269 });
15270 }
15271
15272 ann._extremes[axId] = extremes;
15273}
15274
15275},{"../../lib":178,"../../plots/cartesian/axes":222,"./draw":43}],39:[function(_dereq_,module,exports){
15276/**
15277* Copyright 2012-2020, Plotly, Inc.
15278* All rights reserved.
15279*
15280* This source code is licensed under the MIT license found in the
15281* LICENSE file in the root directory of this source tree.
15282*/
15283
15284'use strict';
15285
15286var Lib = _dereq_('../../lib');
15287var Registry = _dereq_('../../registry');
15288var arrayEditor = _dereq_('../../plot_api/plot_template').arrayEditor;
15289
15290module.exports = {
15291 hasClickToShow: hasClickToShow,
15292 onClick: onClick
15293};
15294
15295/*
15296 * hasClickToShow: does the given hoverData have ANY annotations which will
15297 * turn ON if we click here? (used by hover events to set cursor)
15298 *
15299 * gd: graphDiv
15300 * hoverData: a hoverData array, as included with the *plotly_hover* or
15301 * *plotly_click* events in the `points` attribute
15302 *
15303 * returns: boolean
15304 */
15305function hasClickToShow(gd, hoverData) {
15306 var sets = getToggleSets(gd, hoverData);
15307 return sets.on.length > 0 || sets.explicitOff.length > 0;
15308}
15309
15310/*
15311 * onClick: perform the toggling (via Plotly.update) implied by clicking
15312 * at this hoverData
15313 *
15314 * gd: graphDiv
15315 * hoverData: a hoverData array, as included with the *plotly_hover* or
15316 * *plotly_click* events in the `points` attribute
15317 *
15318 * returns: Promise that the update is complete
15319 */
15320function onClick(gd, hoverData) {
15321 var toggleSets = getToggleSets(gd, hoverData);
15322 var onSet = toggleSets.on;
15323 var offSet = toggleSets.off.concat(toggleSets.explicitOff);
15324 var update = {};
15325 var annotationsOut = gd._fullLayout.annotations;
15326 var i, editHelpers;
15327
15328 if(!(onSet.length || offSet.length)) return;
15329
15330 for(i = 0; i < onSet.length; i++) {
15331 editHelpers = arrayEditor(gd.layout, 'annotations', annotationsOut[onSet[i]]);
15332 editHelpers.modifyItem('visible', true);
15333 Lib.extendFlat(update, editHelpers.getUpdateObj());
15334 }
15335
15336 for(i = 0; i < offSet.length; i++) {
15337 editHelpers = arrayEditor(gd.layout, 'annotations', annotationsOut[offSet[i]]);
15338 editHelpers.modifyItem('visible', false);
15339 Lib.extendFlat(update, editHelpers.getUpdateObj());
15340 }
15341
15342 return Registry.call('update', gd, {}, update);
15343}
15344
15345/*
15346 * getToggleSets: find the annotations which will turn on or off at this
15347 * hoverData
15348 *
15349 * gd: graphDiv
15350 * hoverData: a hoverData array, as included with the *plotly_hover* or
15351 * *plotly_click* events in the `points` attribute
15352 *
15353 * returns: {
15354 * on: Array (indices of annotations to turn on),
15355 * off: Array (indices to turn off because you're not hovering on them),
15356 * explicitOff: Array (indices to turn off because you *are* hovering on them)
15357 * }
15358 */
15359function getToggleSets(gd, hoverData) {
15360 var annotations = gd._fullLayout.annotations;
15361 var onSet = [];
15362 var offSet = [];
15363 var explicitOffSet = [];
15364 var hoverLen = (hoverData || []).length;
15365
15366 var i, j, anni, showMode, pointj, xa, ya, toggleType;
15367
15368 for(i = 0; i < annotations.length; i++) {
15369 anni = annotations[i];
15370 showMode = anni.clicktoshow;
15371
15372 if(showMode) {
15373 for(j = 0; j < hoverLen; j++) {
15374 pointj = hoverData[j];
15375 xa = pointj.xaxis;
15376 ya = pointj.yaxis;
15377
15378 if(xa._id === anni.xref &&
15379 ya._id === anni.yref &&
15380 xa.d2r(pointj.x) === clickData2r(anni._xclick, xa) &&
15381 ya.d2r(pointj.y) === clickData2r(anni._yclick, ya)
15382 ) {
15383 // match! toggle this annotation
15384 // regardless of its clicktoshow mode
15385 // but if it's onout mode, off is implicit
15386 if(anni.visible) {
15387 if(showMode === 'onout') toggleType = offSet;
15388 else toggleType = explicitOffSet;
15389 } else {
15390 toggleType = onSet;
15391 }
15392 toggleType.push(i);
15393 break;
15394 }
15395 }
15396
15397 if(j === hoverLen) {
15398 // no match - only turn this annotation OFF, and only if
15399 // showmode is 'onout'
15400 if(anni.visible && showMode === 'onout') offSet.push(i);
15401 }
15402 }
15403 }
15404
15405 return {on: onSet, off: offSet, explicitOff: explicitOffSet};
15406}
15407
15408// to handle log axes until v2
15409function clickData2r(d, ax) {
15410 return ax.type === 'log' ? ax.l2r(d) : ax.d2r(d);
15411}
15412
15413},{"../../lib":178,"../../plot_api/plot_template":212,"../../registry":269}],40:[function(_dereq_,module,exports){
15414/**
15415* Copyright 2012-2020, Plotly, Inc.
15416* All rights reserved.
15417*
15418* This source code is licensed under the MIT license found in the
15419* LICENSE file in the root directory of this source tree.
15420*/
15421
15422'use strict';
15423
15424var Lib = _dereq_('../../lib');
15425var Color = _dereq_('../color');
15426
15427// defaults common to 'annotations' and 'annotations3d'
15428module.exports = function handleAnnotationCommonDefaults(annIn, annOut, fullLayout, coerce) {
15429 coerce('opacity');
15430 var bgColor = coerce('bgcolor');
15431
15432 var borderColor = coerce('bordercolor');
15433 var borderOpacity = Color.opacity(borderColor);
15434
15435 coerce('borderpad');
15436
15437 var borderWidth = coerce('borderwidth');
15438 var showArrow = coerce('showarrow');
15439
15440 coerce('text', showArrow ? ' ' : fullLayout._dfltTitle.annotation);
15441 coerce('textangle');
15442 Lib.coerceFont(coerce, 'font', fullLayout.font);
15443
15444 coerce('width');
15445 coerce('align');
15446
15447 var h = coerce('height');
15448 if(h) coerce('valign');
15449
15450 if(showArrow) {
15451 var arrowside = coerce('arrowside');
15452 var arrowhead;
15453 var arrowsize;
15454
15455 if(arrowside.indexOf('end') !== -1) {
15456 arrowhead = coerce('arrowhead');
15457 arrowsize = coerce('arrowsize');
15458 }
15459
15460 if(arrowside.indexOf('start') !== -1) {
15461 coerce('startarrowhead', arrowhead);
15462 coerce('startarrowsize', arrowsize);
15463 }
15464 coerce('arrowcolor', borderOpacity ? annOut.bordercolor : Color.defaultLine);
15465 coerce('arrowwidth', ((borderOpacity && borderWidth) || 1) * 2);
15466 coerce('standoff');
15467 coerce('startstandoff');
15468 }
15469
15470 var hoverText = coerce('hovertext');
15471 var globalHoverLabel = fullLayout.hoverlabel || {};
15472
15473 if(hoverText) {
15474 var hoverBG = coerce('hoverlabel.bgcolor', globalHoverLabel.bgcolor ||
15475 (Color.opacity(bgColor) ? Color.rgb(bgColor) : Color.defaultLine)
15476 );
15477
15478 var hoverBorder = coerce('hoverlabel.bordercolor', globalHoverLabel.bordercolor ||
15479 Color.contrast(hoverBG)
15480 );
15481
15482 Lib.coerceFont(coerce, 'hoverlabel.font', {
15483 family: globalHoverLabel.font.family,
15484 size: globalHoverLabel.font.size,
15485 color: globalHoverLabel.font.color || hoverBorder
15486 });
15487 }
15488
15489 coerce('captureevents', !!hoverText);
15490};
15491
15492},{"../../lib":178,"../color":52}],41:[function(_dereq_,module,exports){
15493/**
15494* Copyright 2012-2020, Plotly, Inc.
15495* All rights reserved.
15496*
15497* This source code is licensed under the MIT license found in the
15498* LICENSE file in the root directory of this source tree.
15499*/
15500
15501
15502'use strict';
15503
15504var isNumeric = _dereq_('fast-isnumeric');
15505var toLogRange = _dereq_('../../lib/to_log_range');
15506
15507/*
15508 * convertCoords: when converting an axis between log and linear
15509 * you need to alter any annotations on that axis to keep them
15510 * pointing at the same data point.
15511 * In v2.0 this will become obsolete
15512 *
15513 * gd: the plot div
15514 * ax: the axis being changed
15515 * newType: the type it's getting
15516 * doExtra: function(attr, val) from inside relayout that sets the attribute.
15517 * Use this to make the changes as it's aware if any other changes in the
15518 * same relayout call should override this conversion.
15519 */
15520module.exports = function convertCoords(gd, ax, newType, doExtra) {
15521 ax = ax || {};
15522
15523 var toLog = (newType === 'log') && (ax.type === 'linear');
15524 var fromLog = (newType === 'linear') && (ax.type === 'log');
15525
15526 if(!(toLog || fromLog)) return;
15527
15528 var annotations = gd._fullLayout.annotations;
15529 var axLetter = ax._id.charAt(0);
15530 var ann;
15531 var attrPrefix;
15532
15533 function convert(attr) {
15534 var currentVal = ann[attr];
15535 var newVal = null;
15536
15537 if(toLog) newVal = toLogRange(currentVal, ax.range);
15538 else newVal = Math.pow(10, currentVal);
15539
15540 // if conversion failed, delete the value so it gets a default value
15541 if(!isNumeric(newVal)) newVal = null;
15542
15543 doExtra(attrPrefix + attr, newVal);
15544 }
15545
15546 for(var i = 0; i < annotations.length; i++) {
15547 ann = annotations[i];
15548 attrPrefix = 'annotations[' + i + '].';
15549
15550 if(ann[axLetter + 'ref'] === ax._id) convert(axLetter);
15551 if(ann['a' + axLetter + 'ref'] === ax._id) convert('a' + axLetter);
15552 }
15553};
15554
15555},{"../../lib/to_log_range":201,"fast-isnumeric":18}],42:[function(_dereq_,module,exports){
15556/**
15557* Copyright 2012-2020, Plotly, Inc.
15558* All rights reserved.
15559*
15560* This source code is licensed under the MIT license found in the
15561* LICENSE file in the root directory of this source tree.
15562*/
15563
15564
15565'use strict';
15566
15567var Lib = _dereq_('../../lib');
15568var Axes = _dereq_('../../plots/cartesian/axes');
15569var handleArrayContainerDefaults = _dereq_('../../plots/array_container_defaults');
15570
15571var handleAnnotationCommonDefaults = _dereq_('./common_defaults');
15572var attributes = _dereq_('./attributes');
15573
15574
15575module.exports = function supplyLayoutDefaults(layoutIn, layoutOut) {
15576 handleArrayContainerDefaults(layoutIn, layoutOut, {
15577 name: 'annotations',
15578 handleItemDefaults: handleAnnotationDefaults
15579 });
15580};
15581
15582function handleAnnotationDefaults(annIn, annOut, fullLayout) {
15583 function coerce(attr, dflt) {
15584 return Lib.coerce(annIn, annOut, attributes, attr, dflt);
15585 }
15586
15587 var visible = coerce('visible');
15588 var clickToShow = coerce('clicktoshow');
15589
15590 if(!(visible || clickToShow)) return;
15591
15592 handleAnnotationCommonDefaults(annIn, annOut, fullLayout, coerce);
15593
15594 var showArrow = annOut.showarrow;
15595
15596 // positioning
15597 var axLetters = ['x', 'y'];
15598 var arrowPosDflt = [-10, -30];
15599 var gdMock = {_fullLayout: fullLayout};
15600
15601 for(var i = 0; i < 2; i++) {
15602 var axLetter = axLetters[i];
15603
15604 // xref, yref
15605 var axRef = Axes.coerceRef(annIn, annOut, gdMock, axLetter, '', 'paper');
15606
15607 if(axRef !== 'paper') {
15608 var ax = Axes.getFromId(gdMock, axRef);
15609 ax._annIndices.push(annOut._index);
15610 }
15611
15612 // x, y
15613 Axes.coercePosition(annOut, gdMock, coerce, axRef, axLetter, 0.5);
15614
15615 if(showArrow) {
15616 var arrowPosAttr = 'a' + axLetter;
15617 // axref, ayref
15618 var aaxRef = Axes.coerceRef(annIn, annOut, gdMock, arrowPosAttr, 'pixel');
15619
15620 // for now the arrow can only be on the same axis or specified as pixels
15621 // TODO: sometime it might be interesting to allow it to be on *any* axis
15622 // but that would require updates to drawing & autorange code and maybe more
15623 if(aaxRef !== 'pixel' && aaxRef !== axRef) {
15624 aaxRef = annOut[arrowPosAttr] = 'pixel';
15625 }
15626
15627 // ax, ay
15628 var aDflt = (aaxRef === 'pixel') ? arrowPosDflt[i] : 0.4;
15629 Axes.coercePosition(annOut, gdMock, coerce, aaxRef, arrowPosAttr, aDflt);
15630 }
15631
15632 // xanchor, yanchor
15633 coerce(axLetter + 'anchor');
15634
15635 // xshift, yshift
15636 coerce(axLetter + 'shift');
15637 }
15638
15639 // if you have one coordinate you should have both
15640 Lib.noneOrAll(annIn, annOut, ['x', 'y']);
15641
15642 // if you have one part of arrow length you should have both
15643 if(showArrow) {
15644 Lib.noneOrAll(annIn, annOut, ['ax', 'ay']);
15645 }
15646
15647 if(clickToShow) {
15648 var xClick = coerce('xclick');
15649 var yClick = coerce('yclick');
15650
15651 // put the actual click data to bind to into private attributes
15652 // so we don't have to do this little bit of logic on every hover event
15653 annOut._xclick = (xClick === undefined) ?
15654 annOut.x :
15655 Axes.cleanPosition(xClick, gdMock, annOut.xref);
15656 annOut._yclick = (yClick === undefined) ?
15657 annOut.y :
15658 Axes.cleanPosition(yClick, gdMock, annOut.yref);
15659 }
15660}
15661
15662},{"../../lib":178,"../../plots/array_container_defaults":218,"../../plots/cartesian/axes":222,"./attributes":37,"./common_defaults":40}],43:[function(_dereq_,module,exports){
15663/**
15664* Copyright 2012-2020, Plotly, Inc.
15665* All rights reserved.
15666*
15667* This source code is licensed under the MIT license found in the
15668* LICENSE file in the root directory of this source tree.
15669*/
15670
15671'use strict';
15672
15673var d3 = _dereq_('d3');
15674
15675var Registry = _dereq_('../../registry');
15676var Plots = _dereq_('../../plots/plots');
15677var Lib = _dereq_('../../lib');
15678var Axes = _dereq_('../../plots/cartesian/axes');
15679var Color = _dereq_('../color');
15680var Drawing = _dereq_('../drawing');
15681var Fx = _dereq_('../fx');
15682var svgTextUtils = _dereq_('../../lib/svg_text_utils');
15683var setCursor = _dereq_('../../lib/setcursor');
15684var dragElement = _dereq_('../dragelement');
15685var arrayEditor = _dereq_('../../plot_api/plot_template').arrayEditor;
15686
15687var drawArrowHead = _dereq_('./draw_arrow_head');
15688
15689// Annotations are stored in gd.layout.annotations, an array of objects
15690// index can point to one item in this array,
15691// or non-numeric to simply add a new one
15692// or -1 to modify all existing
15693// opt can be the full options object, or one key (to be set to value)
15694// or undefined to simply redraw
15695// if opt is blank, val can be 'add' or a full options object to add a new
15696// annotation at that point in the array, or 'remove' to delete this one
15697
15698module.exports = {
15699 draw: draw,
15700 drawOne: drawOne,
15701 drawRaw: drawRaw
15702};
15703
15704/*
15705 * draw: draw all annotations without any new modifications
15706 */
15707function draw(gd) {
15708 var fullLayout = gd._fullLayout;
15709
15710 fullLayout._infolayer.selectAll('.annotation').remove();
15711
15712 for(var i = 0; i < fullLayout.annotations.length; i++) {
15713 if(fullLayout.annotations[i].visible) {
15714 drawOne(gd, i);
15715 }
15716 }
15717
15718 return Plots.previousPromises(gd);
15719}
15720
15721/*
15722 * drawOne: draw a single cartesian or paper-ref annotation, potentially with modifications
15723 *
15724 * index (int): the annotation to draw
15725 */
15726function drawOne(gd, index) {
15727 var fullLayout = gd._fullLayout;
15728 var options = fullLayout.annotations[index] || {};
15729 var xa = Axes.getFromId(gd, options.xref);
15730 var ya = Axes.getFromId(gd, options.yref);
15731
15732 if(xa) xa.setScale();
15733 if(ya) ya.setScale();
15734
15735 drawRaw(gd, options, index, false, xa, ya);
15736}
15737
15738/**
15739 * drawRaw: draw a single annotation, potentially with modifications
15740 *
15741 * @param {DOM element} gd
15742 * @param {object} options : this annotation's fullLayout options
15743 * @param {integer} index : index in 'annotations' container of the annotation to draw
15744 * @param {string} subplotId : id of the annotation's subplot
15745 * - use false for 2d (i.e. cartesian or paper-ref) annotations
15746 * @param {object | undefined} xa : full x-axis object to compute subplot pos-to-px
15747 * @param {object | undefined} ya : ... y-axis
15748 */
15749function drawRaw(gd, options, index, subplotId, xa, ya) {
15750 var fullLayout = gd._fullLayout;
15751 var gs = gd._fullLayout._size;
15752 var edits = gd._context.edits;
15753
15754 var className, containerStr;
15755
15756 if(subplotId) {
15757 className = 'annotation-' + subplotId;
15758 containerStr = subplotId + '.annotations';
15759 } else {
15760 className = 'annotation';
15761 containerStr = 'annotations';
15762 }
15763
15764 var editHelpers = arrayEditor(gd.layout, containerStr, options);
15765 var modifyBase = editHelpers.modifyBase;
15766 var modifyItem = editHelpers.modifyItem;
15767 var getUpdateObj = editHelpers.getUpdateObj;
15768
15769 // remove the existing annotation if there is one
15770 fullLayout._infolayer
15771 .selectAll('.' + className + '[data-index="' + index + '"]')
15772 .remove();
15773
15774 var annClipID = 'clip' + fullLayout._uid + '_ann' + index;
15775
15776 // this annotation is gone - quit now after deleting it
15777 // TODO: use d3 idioms instead of deleting and redrawing every time
15778 if(!options._input || options.visible === false) {
15779 d3.selectAll('#' + annClipID).remove();
15780 return;
15781 }
15782
15783 // calculated pixel positions
15784 // x & y each will get text, head, and tail as appropriate
15785 var annPosPx = {x: {}, y: {}};
15786 var textangle = +options.textangle || 0;
15787
15788 // create the components
15789 // made a single group to contain all, so opacity can work right
15790 // with border/arrow together this could handle a whole bunch of
15791 // cleanup at this point, but works for now
15792 var annGroup = fullLayout._infolayer.append('g')
15793 .classed(className, true)
15794 .attr('data-index', String(index))
15795 .style('opacity', options.opacity);
15796
15797 // another group for text+background so that they can rotate together
15798 var annTextGroup = annGroup.append('g')
15799 .classed('annotation-text-g', true);
15800
15801 var editTextPosition = edits[options.showarrow ? 'annotationTail' : 'annotationPosition'];
15802 var textEvents = options.captureevents || edits.annotationText || editTextPosition;
15803
15804 function makeEventData(initialEvent) {
15805 var eventData = {
15806 index: index,
15807 annotation: options._input,
15808 fullAnnotation: options,
15809 event: initialEvent
15810 };
15811 if(subplotId) {
15812 eventData.subplotId = subplotId;
15813 }
15814 return eventData;
15815 }
15816
15817 var annTextGroupInner = annTextGroup.append('g')
15818 .style('pointer-events', textEvents ? 'all' : null)
15819 .call(setCursor, 'pointer')
15820 .on('click', function() {
15821 gd._dragging = false;
15822 gd.emit('plotly_clickannotation', makeEventData(d3.event));
15823 });
15824
15825 if(options.hovertext) {
15826 annTextGroupInner
15827 .on('mouseover', function() {
15828 var hoverOptions = options.hoverlabel;
15829 var hoverFont = hoverOptions.font;
15830 var bBox = this.getBoundingClientRect();
15831 var bBoxRef = gd.getBoundingClientRect();
15832
15833 Fx.loneHover({
15834 x0: bBox.left - bBoxRef.left,
15835 x1: bBox.right - bBoxRef.left,
15836 y: (bBox.top + bBox.bottom) / 2 - bBoxRef.top,
15837 text: options.hovertext,
15838 color: hoverOptions.bgcolor,
15839 borderColor: hoverOptions.bordercolor,
15840 fontFamily: hoverFont.family,
15841 fontSize: hoverFont.size,
15842 fontColor: hoverFont.color
15843 }, {
15844 container: fullLayout._hoverlayer.node(),
15845 outerContainer: fullLayout._paper.node(),
15846 gd: gd
15847 });
15848 })
15849 .on('mouseout', function() {
15850 Fx.loneUnhover(fullLayout._hoverlayer.node());
15851 });
15852 }
15853
15854 var borderwidth = options.borderwidth;
15855 var borderpad = options.borderpad;
15856 var borderfull = borderwidth + borderpad;
15857
15858 var annTextBG = annTextGroupInner.append('rect')
15859 .attr('class', 'bg')
15860 .style('stroke-width', borderwidth + 'px')
15861 .call(Color.stroke, options.bordercolor)
15862 .call(Color.fill, options.bgcolor);
15863
15864 var isSizeConstrained = options.width || options.height;
15865
15866 var annTextClip = fullLayout._topclips
15867 .selectAll('#' + annClipID)
15868 .data(isSizeConstrained ? [0] : []);
15869
15870 annTextClip.enter().append('clipPath')
15871 .classed('annclip', true)
15872 .attr('id', annClipID)
15873 .append('rect');
15874 annTextClip.exit().remove();
15875
15876 var font = options.font;
15877
15878 var text = fullLayout._meta ?
15879 Lib.templateString(options.text, fullLayout._meta) :
15880 options.text;
15881
15882 var annText = annTextGroupInner.append('text')
15883 .classed('annotation-text', true)
15884 .text(text);
15885
15886 function textLayout(s) {
15887 s.call(Drawing.font, font)
15888 .attr({
15889 'text-anchor': {
15890 left: 'start',
15891 right: 'end'
15892 }[options.align] || 'middle'
15893 });
15894
15895 svgTextUtils.convertToTspans(s, gd, drawGraphicalElements);
15896 return s;
15897 }
15898
15899 function drawGraphicalElements() {
15900 // if the text has *only* a link, make the whole box into a link
15901 var anchor3 = annText.selectAll('a');
15902 if(anchor3.size() === 1 && anchor3.text() === annText.text()) {
15903 var wholeLink = annTextGroupInner.insert('a', ':first-child').attr({
15904 'xlink:xlink:href': anchor3.attr('xlink:href'),
15905 'xlink:xlink:show': anchor3.attr('xlink:show')
15906 })
15907 .style({cursor: 'pointer'});
15908
15909 wholeLink.node().appendChild(annTextBG.node());
15910 }
15911
15912 var mathjaxGroup = annTextGroupInner.select('.annotation-text-math-group');
15913 var hasMathjax = !mathjaxGroup.empty();
15914 var anntextBB = Drawing.bBox(
15915 (hasMathjax ? mathjaxGroup : annText).node());
15916 var textWidth = anntextBB.width;
15917 var textHeight = anntextBB.height;
15918 var annWidth = options.width || textWidth;
15919 var annHeight = options.height || textHeight;
15920 var outerWidth = Math.round(annWidth + 2 * borderfull);
15921 var outerHeight = Math.round(annHeight + 2 * borderfull);
15922
15923 function shiftFraction(v, anchor) {
15924 if(anchor === 'auto') {
15925 if(v < 1 / 3) anchor = 'left';
15926 else if(v > 2 / 3) anchor = 'right';
15927 else anchor = 'center';
15928 }
15929 return {
15930 center: 0,
15931 middle: 0,
15932 left: 0.5,
15933 bottom: -0.5,
15934 right: -0.5,
15935 top: 0.5
15936 }[anchor];
15937 }
15938
15939 var annotationIsOffscreen = false;
15940 var letters = ['x', 'y'];
15941
15942 for(var i = 0; i < letters.length; i++) {
15943 var axLetter = letters[i];
15944 var axRef = options[axLetter + 'ref'] || axLetter;
15945 var tailRef = options['a' + axLetter + 'ref'];
15946 var ax = {x: xa, y: ya}[axLetter];
15947 var dimAngle = (textangle + (axLetter === 'x' ? 0 : -90)) * Math.PI / 180;
15948 // note that these two can be either positive or negative
15949 var annSizeFromWidth = outerWidth * Math.cos(dimAngle);
15950 var annSizeFromHeight = outerHeight * Math.sin(dimAngle);
15951 // but this one is the positive total size
15952 var annSize = Math.abs(annSizeFromWidth) + Math.abs(annSizeFromHeight);
15953 var anchor = options[axLetter + 'anchor'];
15954 var overallShift = options[axLetter + 'shift'] * (axLetter === 'x' ? 1 : -1);
15955 var posPx = annPosPx[axLetter];
15956 var basePx;
15957 var textPadShift;
15958 var alignPosition;
15959 var autoAlignFraction;
15960 var textShift;
15961
15962 /*
15963 * calculate the *primary* pixel position
15964 * which is the arrowhead if there is one,
15965 * otherwise the text anchor point
15966 */
15967 if(ax) {
15968 // check if annotation is off screen, to bypass DOM manipulations
15969 var posFraction = ax.r2fraction(options[axLetter]);
15970 if(posFraction < 0 || posFraction > 1) {
15971 if(tailRef === axRef) {
15972 posFraction = ax.r2fraction(options['a' + axLetter]);
15973 if(posFraction < 0 || posFraction > 1) {
15974 annotationIsOffscreen = true;
15975 }
15976 } else {
15977 annotationIsOffscreen = true;
15978 }
15979 }
15980 basePx = ax._offset + ax.r2p(options[axLetter]);
15981 autoAlignFraction = 0.5;
15982 } else {
15983 if(axLetter === 'x') {
15984 alignPosition = options[axLetter];
15985 basePx = gs.l + gs.w * alignPosition;
15986 } else {
15987 alignPosition = 1 - options[axLetter];
15988 basePx = gs.t + gs.h * alignPosition;
15989 }
15990 autoAlignFraction = options.showarrow ? 0.5 : alignPosition;
15991 }
15992
15993 // now translate this into pixel positions of head, tail, and text
15994 // as well as paddings for autorange
15995 if(options.showarrow) {
15996 posPx.head = basePx;
15997
15998 var arrowLength = options['a' + axLetter];
15999
16000 // with an arrow, the text rotates around the anchor point
16001 textShift = annSizeFromWidth * shiftFraction(0.5, options.xanchor) -
16002 annSizeFromHeight * shiftFraction(0.5, options.yanchor);
16003
16004 if(tailRef === axRef) {
16005 posPx.tail = ax._offset + ax.r2p(arrowLength);
16006 // tail is data-referenced: autorange pads the text in px from the tail
16007 textPadShift = textShift;
16008 } else {
16009 posPx.tail = basePx + arrowLength;
16010 // tail is specified in px from head, so autorange also pads vs head
16011 textPadShift = textShift + arrowLength;
16012 }
16013
16014 posPx.text = posPx.tail + textShift;
16015
16016 // constrain pixel/paper referenced so the draggers are at least
16017 // partially visible
16018 var maxPx = fullLayout[(axLetter === 'x') ? 'width' : 'height'];
16019 if(axRef === 'paper') {
16020 posPx.head = Lib.constrain(posPx.head, 1, maxPx - 1);
16021 }
16022 if(tailRef === 'pixel') {
16023 var shiftPlus = -Math.max(posPx.tail - 3, posPx.text);
16024 var shiftMinus = Math.min(posPx.tail + 3, posPx.text) - maxPx;
16025 if(shiftPlus > 0) {
16026 posPx.tail += shiftPlus;
16027 posPx.text += shiftPlus;
16028 } else if(shiftMinus > 0) {
16029 posPx.tail -= shiftMinus;
16030 posPx.text -= shiftMinus;
16031 }
16032 }
16033
16034 posPx.tail += overallShift;
16035 posPx.head += overallShift;
16036 } else {
16037 // with no arrow, the text rotates and *then* we put the anchor
16038 // relative to the new bounding box
16039 textShift = annSize * shiftFraction(autoAlignFraction, anchor);
16040 textPadShift = textShift;
16041 posPx.text = basePx + textShift;
16042 }
16043
16044 posPx.text += overallShift;
16045 textShift += overallShift;
16046 textPadShift += overallShift;
16047
16048 // padplus/minus are used by autorange
16049 options['_' + axLetter + 'padplus'] = (annSize / 2) + textPadShift;
16050 options['_' + axLetter + 'padminus'] = (annSize / 2) - textPadShift;
16051
16052 // size/shift are used during dragging
16053 options['_' + axLetter + 'size'] = annSize;
16054 options['_' + axLetter + 'shift'] = textShift;
16055 }
16056
16057 if(annotationIsOffscreen) {
16058 annTextGroupInner.remove();
16059 return;
16060 }
16061
16062 var xShift = 0;
16063 var yShift = 0;
16064
16065 if(options.align !== 'left') {
16066 xShift = (annWidth - textWidth) * (options.align === 'center' ? 0.5 : 1);
16067 }
16068 if(options.valign !== 'top') {
16069 yShift = (annHeight - textHeight) * (options.valign === 'middle' ? 0.5 : 1);
16070 }
16071
16072 if(hasMathjax) {
16073 mathjaxGroup.select('svg').attr({
16074 x: borderfull + xShift - 1,
16075 y: borderfull + yShift
16076 })
16077 .call(Drawing.setClipUrl, isSizeConstrained ? annClipID : null, gd);
16078 } else {
16079 var texty = borderfull + yShift - anntextBB.top;
16080 var textx = borderfull + xShift - anntextBB.left;
16081
16082 annText.call(svgTextUtils.positionText, textx, texty)
16083 .call(Drawing.setClipUrl, isSizeConstrained ? annClipID : null, gd);
16084 }
16085
16086 annTextClip.select('rect').call(Drawing.setRect, borderfull, borderfull,
16087 annWidth, annHeight);
16088
16089 annTextBG.call(Drawing.setRect, borderwidth / 2, borderwidth / 2,
16090 outerWidth - borderwidth, outerHeight - borderwidth);
16091
16092 annTextGroupInner.call(Drawing.setTranslate,
16093 Math.round(annPosPx.x.text - outerWidth / 2),
16094 Math.round(annPosPx.y.text - outerHeight / 2));
16095
16096 /*
16097 * rotate text and background
16098 * we already calculated the text center position *as rotated*
16099 * because we needed that for autoranging anyway, so now whether
16100 * we have an arrow or not, we rotate about the text center.
16101 */
16102 annTextGroup.attr({transform: 'rotate(' + textangle + ',' +
16103 annPosPx.x.text + ',' + annPosPx.y.text + ')'});
16104
16105 /*
16106 * add the arrow
16107 * uses options[arrowwidth,arrowcolor,arrowhead] for styling
16108 * dx and dy are normally zero, but when you are dragging the textbox
16109 * while the head stays put, dx and dy are the pixel offsets
16110 */
16111 var drawArrow = function(dx, dy) {
16112 annGroup
16113 .selectAll('.annotation-arrow-g')
16114 .remove();
16115
16116 var headX = annPosPx.x.head;
16117 var headY = annPosPx.y.head;
16118 var tailX = annPosPx.x.tail + dx;
16119 var tailY = annPosPx.y.tail + dy;
16120 var textX = annPosPx.x.text + dx;
16121 var textY = annPosPx.y.text + dy;
16122
16123 // find the edge of the text box, where we'll start the arrow:
16124 // create transform matrix to rotate the text box corners
16125 var transform = Lib.rotationXYMatrix(textangle, textX, textY);
16126 var applyTransform = Lib.apply2DTransform(transform);
16127 var applyTransform2 = Lib.apply2DTransform2(transform);
16128
16129 // calculate and transform bounding box
16130 var width = +annTextBG.attr('width');
16131 var height = +annTextBG.attr('height');
16132 var xLeft = textX - 0.5 * width;
16133 var xRight = xLeft + width;
16134 var yTop = textY - 0.5 * height;
16135 var yBottom = yTop + height;
16136 var edges = [
16137 [xLeft, yTop, xLeft, yBottom],
16138 [xLeft, yBottom, xRight, yBottom],
16139 [xRight, yBottom, xRight, yTop],
16140 [xRight, yTop, xLeft, yTop]
16141 ].map(applyTransform2);
16142
16143 // Remove the line if it ends inside the box. Use ray
16144 // casting for rotated boxes: see which edges intersect a
16145 // line from the arrowhead to far away and reduce with xor
16146 // to get the parity of the number of intersections.
16147 if(edges.reduce(function(a, x) {
16148 return a ^
16149 !!Lib.segmentsIntersect(headX, headY, headX + 1e6, headY + 1e6,
16150 x[0], x[1], x[2], x[3]);
16151 }, false)) {
16152 // no line or arrow - so quit drawArrow now
16153 return;
16154 }
16155
16156 edges.forEach(function(x) {
16157 var p = Lib.segmentsIntersect(tailX, tailY, headX, headY,
16158 x[0], x[1], x[2], x[3]);
16159 if(p) {
16160 tailX = p.x;
16161 tailY = p.y;
16162 }
16163 });
16164
16165 var strokewidth = options.arrowwidth;
16166 var arrowColor = options.arrowcolor;
16167 var arrowSide = options.arrowside;
16168
16169 var arrowGroup = annGroup.append('g')
16170 .style({opacity: Color.opacity(arrowColor)})
16171 .classed('annotation-arrow-g', true);
16172
16173 var arrow = arrowGroup.append('path')
16174 .attr('d', 'M' + tailX + ',' + tailY + 'L' + headX + ',' + headY)
16175 .style('stroke-width', strokewidth + 'px')
16176 .call(Color.stroke, Color.rgb(arrowColor));
16177
16178 drawArrowHead(arrow, arrowSide, options);
16179
16180 // the arrow dragger is a small square right at the head, then a line to the tail,
16181 // all expanded by a stroke width of 6px plus the arrow line width
16182 if(edits.annotationPosition && arrow.node().parentNode && !subplotId) {
16183 var arrowDragHeadX = headX;
16184 var arrowDragHeadY = headY;
16185 if(options.standoff) {
16186 var arrowLength = Math.sqrt(Math.pow(headX - tailX, 2) + Math.pow(headY - tailY, 2));
16187 arrowDragHeadX += options.standoff * (tailX - headX) / arrowLength;
16188 arrowDragHeadY += options.standoff * (tailY - headY) / arrowLength;
16189 }
16190 var arrowDrag = arrowGroup.append('path')
16191 .classed('annotation-arrow', true)
16192 .classed('anndrag', true)
16193 .classed('cursor-move', true)
16194 .attr({
16195 d: 'M3,3H-3V-3H3ZM0,0L' + (tailX - arrowDragHeadX) + ',' + (tailY - arrowDragHeadY),
16196 transform: 'translate(' + arrowDragHeadX + ',' + arrowDragHeadY + ')'
16197 })
16198 .style('stroke-width', (strokewidth + 6) + 'px')
16199 .call(Color.stroke, 'rgba(0,0,0,0)')
16200 .call(Color.fill, 'rgba(0,0,0,0)');
16201
16202 var annx0, anny0;
16203
16204 // dragger for the arrow & head: translates the whole thing
16205 // (head/tail/text) all together
16206 dragElement.init({
16207 element: arrowDrag.node(),
16208 gd: gd,
16209 prepFn: function() {
16210 var pos = Drawing.getTranslate(annTextGroupInner);
16211
16212 annx0 = pos.x;
16213 anny0 = pos.y;
16214 if(xa && xa.autorange) {
16215 modifyBase(xa._name + '.autorange', true);
16216 }
16217 if(ya && ya.autorange) {
16218 modifyBase(ya._name + '.autorange', true);
16219 }
16220 },
16221 moveFn: function(dx, dy) {
16222 var annxy0 = applyTransform(annx0, anny0);
16223 var xcenter = annxy0[0] + dx;
16224 var ycenter = annxy0[1] + dy;
16225 annTextGroupInner.call(Drawing.setTranslate, xcenter, ycenter);
16226
16227 modifyItem('x', xa ?
16228 xa.p2r(xa.r2p(options.x) + dx) :
16229 (options.x + (dx / gs.w)));
16230 modifyItem('y', ya ?
16231 ya.p2r(ya.r2p(options.y) + dy) :
16232 (options.y - (dy / gs.h)));
16233
16234 if(options.axref === options.xref) {
16235 modifyItem('ax', xa.p2r(xa.r2p(options.ax) + dx));
16236 }
16237
16238 if(options.ayref === options.yref) {
16239 modifyItem('ay', ya.p2r(ya.r2p(options.ay) + dy));
16240 }
16241
16242 arrowGroup.attr('transform', 'translate(' + dx + ',' + dy + ')');
16243 annTextGroup.attr({
16244 transform: 'rotate(' + textangle + ',' +
16245 xcenter + ',' + ycenter + ')'
16246 });
16247 },
16248 doneFn: function() {
16249 Registry.call('_guiRelayout', gd, getUpdateObj());
16250 var notesBox = document.querySelector('.js-notes-box-panel');
16251 if(notesBox) notesBox.redraw(notesBox.selectedObj);
16252 }
16253 });
16254 }
16255 };
16256
16257 if(options.showarrow) drawArrow(0, 0);
16258
16259 // user dragging the annotation (text, not arrow)
16260 if(editTextPosition) {
16261 var baseTextTransform;
16262
16263 // dragger for the textbox: if there's an arrow, just drag the
16264 // textbox and tail, leave the head untouched
16265 dragElement.init({
16266 element: annTextGroupInner.node(),
16267 gd: gd,
16268 prepFn: function() {
16269 baseTextTransform = annTextGroup.attr('transform');
16270 },
16271 moveFn: function(dx, dy) {
16272 var csr = 'pointer';
16273 if(options.showarrow) {
16274 if(options.axref === options.xref) {
16275 modifyItem('ax', xa.p2r(xa.r2p(options.ax) + dx));
16276 } else {
16277 modifyItem('ax', options.ax + dx);
16278 }
16279
16280 if(options.ayref === options.yref) {
16281 modifyItem('ay', ya.p2r(ya.r2p(options.ay) + dy));
16282 } else {
16283 modifyItem('ay', options.ay + dy);
16284 }
16285
16286 drawArrow(dx, dy);
16287 } else if(!subplotId) {
16288 var xUpdate, yUpdate;
16289 if(xa) {
16290 xUpdate = xa.p2r(xa.r2p(options.x) + dx);
16291 } else {
16292 var widthFraction = options._xsize / gs.w;
16293 var xLeft = options.x + (options._xshift - options.xshift) / gs.w - widthFraction / 2;
16294
16295 xUpdate = dragElement.align(xLeft + dx / gs.w,
16296 widthFraction, 0, 1, options.xanchor);
16297 }
16298
16299 if(ya) {
16300 yUpdate = ya.p2r(ya.r2p(options.y) + dy);
16301 } else {
16302 var heightFraction = options._ysize / gs.h;
16303 var yBottom = options.y - (options._yshift + options.yshift) / gs.h - heightFraction / 2;
16304
16305 yUpdate = dragElement.align(yBottom - dy / gs.h,
16306 heightFraction, 0, 1, options.yanchor);
16307 }
16308 modifyItem('x', xUpdate);
16309 modifyItem('y', yUpdate);
16310 if(!xa || !ya) {
16311 csr = dragElement.getCursor(
16312 xa ? 0.5 : xUpdate,
16313 ya ? 0.5 : yUpdate,
16314 options.xanchor, options.yanchor
16315 );
16316 }
16317 } else return;
16318
16319 annTextGroup.attr({
16320 transform: 'translate(' + dx + ',' + dy + ')' + baseTextTransform
16321 });
16322
16323 setCursor(annTextGroupInner, csr);
16324 },
16325 clickFn: function(_, initialEvent) {
16326 if(options.captureevents) {
16327 gd.emit('plotly_clickannotation', makeEventData(initialEvent));
16328 }
16329 },
16330 doneFn: function() {
16331 setCursor(annTextGroupInner);
16332 Registry.call('_guiRelayout', gd, getUpdateObj());
16333 var notesBox = document.querySelector('.js-notes-box-panel');
16334 if(notesBox) notesBox.redraw(notesBox.selectedObj);
16335 }
16336 });
16337 }
16338 }
16339
16340 if(edits.annotationText) {
16341 annText.call(svgTextUtils.makeEditable, {delegate: annTextGroupInner, gd: gd})
16342 .call(textLayout)
16343 .on('edit', function(_text) {
16344 options.text = _text;
16345
16346 this.call(textLayout);
16347
16348 modifyItem('text', _text);
16349
16350 if(xa && xa.autorange) {
16351 modifyBase(xa._name + '.autorange', true);
16352 }
16353 if(ya && ya.autorange) {
16354 modifyBase(ya._name + '.autorange', true);
16355 }
16356
16357 Registry.call('_guiRelayout', gd, getUpdateObj());
16358 });
16359 } else annText.call(textLayout);
16360}
16361
16362},{"../../lib":178,"../../lib/setcursor":197,"../../lib/svg_text_utils":199,"../../plot_api/plot_template":212,"../../plots/cartesian/axes":222,"../../plots/plots":256,"../../registry":269,"../color":52,"../dragelement":71,"../drawing":74,"../fx":92,"./draw_arrow_head":44,"d3":16}],44:[function(_dereq_,module,exports){
16363/**
16364* Copyright 2012-2020, Plotly, Inc.
16365* All rights reserved.
16366*
16367* This source code is licensed under the MIT license found in the
16368* LICENSE file in the root directory of this source tree.
16369*/
16370
16371
16372'use strict';
16373
16374var d3 = _dereq_('d3');
16375
16376var Color = _dereq_('../color');
16377
16378var ARROWPATHS = _dereq_('./arrow_paths');
16379
16380/**
16381 * Add arrowhead(s) to a path or line element
16382 *
16383 * @param {d3.selection} el3: a d3-selected line or path element
16384 *
16385 * @param {string} ends: 'none', 'start', 'end', or 'start+end' for which ends get arrowheads
16386 *
16387 * @param {object} options: style information. Must have all the following:
16388 * @param {number} options.arrowhead: end head style - see ./arrow_paths
16389 * @param {number} options.startarrowhead: start head style - see ./arrow_paths
16390 * @param {number} options.arrowsize: relative size of the end head vs line width
16391 * @param {number} options.startarrowsize: relative size of the start head vs line width
16392 * @param {number} options.standoff: distance in px to move the end arrow point from its target
16393 * @param {number} options.startstandoff: distance in px to move the start arrow point from its target
16394 * @param {number} options.arrowwidth: width of the arrow line
16395 * @param {string} options.arrowcolor: color of the arrow line, for the head to match
16396 * Note that the opacity of this color is ignored, as it's assumed the container
16397 * of both the line and head has opacity applied to it so there isn't greater opacity
16398 * where they overlap.
16399 */
16400module.exports = function drawArrowHead(el3, ends, options) {
16401 var el = el3.node();
16402 var headStyle = ARROWPATHS[options.arrowhead || 0];
16403 var startHeadStyle = ARROWPATHS[options.startarrowhead || 0];
16404 var scale = (options.arrowwidth || 1) * (options.arrowsize || 1);
16405 var startScale = (options.arrowwidth || 1) * (options.startarrowsize || 1);
16406 var doStart = ends.indexOf('start') >= 0;
16407 var doEnd = ends.indexOf('end') >= 0;
16408 var backOff = headStyle.backoff * scale + options.standoff;
16409 var startBackOff = startHeadStyle.backoff * startScale + options.startstandoff;
16410
16411 var start, end, startRot, endRot;
16412
16413 if(el.nodeName === 'line') {
16414 start = {x: +el3.attr('x1'), y: +el3.attr('y1')};
16415 end = {x: +el3.attr('x2'), y: +el3.attr('y2')};
16416
16417 var dx = start.x - end.x;
16418 var dy = start.y - end.y;
16419
16420 startRot = Math.atan2(dy, dx);
16421 endRot = startRot + Math.PI;
16422 if(backOff && startBackOff) {
16423 if(backOff + startBackOff > Math.sqrt(dx * dx + dy * dy)) {
16424 hideLine();
16425 return;
16426 }
16427 }
16428
16429 if(backOff) {
16430 if(backOff * backOff > dx * dx + dy * dy) {
16431 hideLine();
16432 return;
16433 }
16434 var backOffX = backOff * Math.cos(startRot);
16435 var backOffY = backOff * Math.sin(startRot);
16436
16437 end.x += backOffX;
16438 end.y += backOffY;
16439 el3.attr({x2: end.x, y2: end.y});
16440 }
16441
16442 if(startBackOff) {
16443 if(startBackOff * startBackOff > dx * dx + dy * dy) {
16444 hideLine();
16445 return;
16446 }
16447 var startBackOffX = startBackOff * Math.cos(startRot);
16448 var startbackOffY = startBackOff * Math.sin(startRot);
16449
16450 start.x -= startBackOffX;
16451 start.y -= startbackOffY;
16452 el3.attr({x1: start.x, y1: start.y});
16453 }
16454 } else if(el.nodeName === 'path') {
16455 var pathlen = el.getTotalLength();
16456 // using dash to hide the backOff region of the path.
16457 // if we ever allow dash for the arrow we'll have to
16458 // do better than this hack... maybe just manually
16459 // combine the two
16460 var dashArray = '';
16461
16462 if(pathlen < backOff + startBackOff) {
16463 hideLine();
16464 return;
16465 }
16466
16467
16468 var start0 = el.getPointAtLength(0);
16469 var dstart = el.getPointAtLength(0.1);
16470
16471 startRot = Math.atan2(start0.y - dstart.y, start0.x - dstart.x);
16472 start = el.getPointAtLength(Math.min(startBackOff, pathlen));
16473
16474 dashArray = '0px,' + startBackOff + 'px,';
16475
16476 var end0 = el.getPointAtLength(pathlen);
16477 var dend = el.getPointAtLength(pathlen - 0.1);
16478
16479 endRot = Math.atan2(end0.y - dend.y, end0.x - dend.x);
16480 end = el.getPointAtLength(Math.max(0, pathlen - backOff));
16481
16482 var shortening = dashArray ? startBackOff + backOff : backOff;
16483 dashArray += (pathlen - shortening) + 'px,' + pathlen + 'px';
16484
16485 el3.style('stroke-dasharray', dashArray);
16486 }
16487
16488 function hideLine() { el3.style('stroke-dasharray', '0px,100px'); }
16489
16490 function drawhead(arrowHeadStyle, p, rot, arrowScale) {
16491 if(!arrowHeadStyle.path) return;
16492 if(arrowHeadStyle.noRotate) rot = 0;
16493
16494 d3.select(el.parentNode).append('path')
16495 .attr({
16496 'class': el3.attr('class'),
16497 d: arrowHeadStyle.path,
16498 transform:
16499 'translate(' + p.x + ',' + p.y + ')' +
16500 (rot ? 'rotate(' + (rot * 180 / Math.PI) + ')' : '') +
16501 'scale(' + arrowScale + ')'
16502 })
16503 .style({
16504 fill: Color.rgb(options.arrowcolor),
16505 'stroke-width': 0
16506 });
16507 }
16508
16509 if(doStart) drawhead(startHeadStyle, start, startRot, startScale);
16510 if(doEnd) drawhead(headStyle, end, endRot, scale);
16511};
16512
16513},{"../color":52,"./arrow_paths":36,"d3":16}],45:[function(_dereq_,module,exports){
16514/**
16515* Copyright 2012-2020, Plotly, Inc.
16516* All rights reserved.
16517*
16518* This source code is licensed under the MIT license found in the
16519* LICENSE file in the root directory of this source tree.
16520*/
16521
16522
16523'use strict';
16524
16525var drawModule = _dereq_('./draw');
16526var clickModule = _dereq_('./click');
16527
16528module.exports = {
16529 moduleType: 'component',
16530 name: 'annotations',
16531
16532 layoutAttributes: _dereq_('./attributes'),
16533 supplyLayoutDefaults: _dereq_('./defaults'),
16534 includeBasePlot: _dereq_('../../plots/cartesian/include_components')('annotations'),
16535
16536 calcAutorange: _dereq_('./calc_autorange'),
16537 draw: drawModule.draw,
16538 drawOne: drawModule.drawOne,
16539 drawRaw: drawModule.drawRaw,
16540
16541 hasClickToShow: clickModule.hasClickToShow,
16542 onClick: clickModule.onClick,
16543
16544 convertCoords: _dereq_('./convert_coords')
16545};
16546
16547},{"../../plots/cartesian/include_components":234,"./attributes":37,"./calc_autorange":38,"./click":39,"./convert_coords":41,"./defaults":42,"./draw":43}],46:[function(_dereq_,module,exports){
16548/**
16549* Copyright 2012-2020, Plotly, Inc.
16550* All rights reserved.
16551*
16552* This source code is licensed under the MIT license found in the
16553* LICENSE file in the root directory of this source tree.
16554*/
16555
16556
16557'use strict';
16558
16559var annAttrs = _dereq_('../annotations/attributes');
16560var overrideAll = _dereq_('../../plot_api/edit_types').overrideAll;
16561var templatedArray = _dereq_('../../plot_api/plot_template').templatedArray;
16562
16563module.exports = overrideAll(templatedArray('annotation', {
16564 visible: annAttrs.visible,
16565 x: {
16566 valType: 'any',
16567
16568
16569 },
16570 y: {
16571 valType: 'any',
16572
16573
16574 },
16575 z: {
16576 valType: 'any',
16577
16578
16579 },
16580 ax: {
16581 valType: 'number',
16582
16583
16584 },
16585 ay: {
16586 valType: 'number',
16587
16588
16589 },
16590
16591 xanchor: annAttrs.xanchor,
16592 xshift: annAttrs.xshift,
16593 yanchor: annAttrs.yanchor,
16594 yshift: annAttrs.yshift,
16595
16596 text: annAttrs.text,
16597 textangle: annAttrs.textangle,
16598 font: annAttrs.font,
16599 width: annAttrs.width,
16600 height: annAttrs.height,
16601 opacity: annAttrs.opacity,
16602 align: annAttrs.align,
16603 valign: annAttrs.valign,
16604 bgcolor: annAttrs.bgcolor,
16605 bordercolor: annAttrs.bordercolor,
16606 borderpad: annAttrs.borderpad,
16607 borderwidth: annAttrs.borderwidth,
16608 showarrow: annAttrs.showarrow,
16609 arrowcolor: annAttrs.arrowcolor,
16610 arrowhead: annAttrs.arrowhead,
16611 startarrowhead: annAttrs.startarrowhead,
16612 arrowside: annAttrs.arrowside,
16613 arrowsize: annAttrs.arrowsize,
16614 startarrowsize: annAttrs.startarrowsize,
16615 arrowwidth: annAttrs.arrowwidth,
16616 standoff: annAttrs.standoff,
16617 startstandoff: annAttrs.startstandoff,
16618 hovertext: annAttrs.hovertext,
16619 hoverlabel: annAttrs.hoverlabel,
16620 captureevents: annAttrs.captureevents,
16621
16622 // maybes later?
16623 // clicktoshow: annAttrs.clicktoshow,
16624 // xclick: annAttrs.xclick,
16625 // yclick: annAttrs.yclick,
16626
16627 // not needed!
16628 // axref: 'pixel'
16629 // ayref: 'pixel'
16630 // xref: 'x'
16631 // yref: 'y
16632 // zref: 'z'
16633}), 'calc', 'from-root');
16634
16635},{"../../plot_api/edit_types":205,"../../plot_api/plot_template":212,"../annotations/attributes":37}],47:[function(_dereq_,module,exports){
16636/**
16637* Copyright 2012-2020, Plotly, Inc.
16638* All rights reserved.
16639*
16640* This source code is licensed under the MIT license found in the
16641* LICENSE file in the root directory of this source tree.
16642*/
16643
16644'use strict';
16645
16646var Lib = _dereq_('../../lib');
16647var Axes = _dereq_('../../plots/cartesian/axes');
16648
16649module.exports = function convert(scene) {
16650 var fullSceneLayout = scene.fullSceneLayout;
16651 var anns = fullSceneLayout.annotations;
16652
16653 for(var i = 0; i < anns.length; i++) {
16654 mockAnnAxes(anns[i], scene);
16655 }
16656
16657 scene.fullLayout._infolayer
16658 .selectAll('.annotation-' + scene.id)
16659 .remove();
16660};
16661
16662function mockAnnAxes(ann, scene) {
16663 var fullSceneLayout = scene.fullSceneLayout;
16664 var domain = fullSceneLayout.domain;
16665 var size = scene.fullLayout._size;
16666
16667 var base = {
16668 // this gets fill in on render
16669 pdata: null,
16670
16671 // to get setConvert to not execute cleanly
16672 type: 'linear',
16673
16674 // don't try to update them on `editable: true`
16675 autorange: false,
16676
16677 // set infinite range so that annotation draw routine
16678 // does not try to remove 'outside-range' annotations,
16679 // this case is handled in the render loop
16680 range: [-Infinity, Infinity]
16681 };
16682
16683 ann._xa = {};
16684 Lib.extendFlat(ann._xa, base);
16685 Axes.setConvert(ann._xa);
16686 ann._xa._offset = size.l + domain.x[0] * size.w;
16687 ann._xa.l2p = function() {
16688 return 0.5 * (1 + ann._pdata[0] / ann._pdata[3]) * size.w * (domain.x[1] - domain.x[0]);
16689 };
16690
16691 ann._ya = {};
16692 Lib.extendFlat(ann._ya, base);
16693 Axes.setConvert(ann._ya);
16694 ann._ya._offset = size.t + (1 - domain.y[1]) * size.h;
16695 ann._ya.l2p = function() {
16696 return 0.5 * (1 - ann._pdata[1] / ann._pdata[3]) * size.h * (domain.y[1] - domain.y[0]);
16697 };
16698}
16699
16700},{"../../lib":178,"../../plots/cartesian/axes":222}],48:[function(_dereq_,module,exports){
16701/**
16702* Copyright 2012-2020, Plotly, Inc.
16703* All rights reserved.
16704*
16705* This source code is licensed under the MIT license found in the
16706* LICENSE file in the root directory of this source tree.
16707*/
16708
16709'use strict';
16710
16711var Lib = _dereq_('../../lib');
16712var Axes = _dereq_('../../plots/cartesian/axes');
16713var handleArrayContainerDefaults = _dereq_('../../plots/array_container_defaults');
16714var handleAnnotationCommonDefaults = _dereq_('../annotations/common_defaults');
16715var attributes = _dereq_('./attributes');
16716
16717module.exports = function handleDefaults(sceneLayoutIn, sceneLayoutOut, opts) {
16718 handleArrayContainerDefaults(sceneLayoutIn, sceneLayoutOut, {
16719 name: 'annotations',
16720 handleItemDefaults: handleAnnotationDefaults,
16721 fullLayout: opts.fullLayout
16722 });
16723};
16724
16725function handleAnnotationDefaults(annIn, annOut, sceneLayout, opts) {
16726 function coerce(attr, dflt) {
16727 return Lib.coerce(annIn, annOut, attributes, attr, dflt);
16728 }
16729
16730 function coercePosition(axLetter) {
16731 var axName = axLetter + 'axis';
16732
16733 // mock in such way that getFromId grabs correct 3D axis
16734 var gdMock = { _fullLayout: {} };
16735 gdMock._fullLayout[axName] = sceneLayout[axName];
16736
16737 return Axes.coercePosition(annOut, gdMock, coerce, axLetter, axLetter, 0.5);
16738 }
16739
16740
16741 var visible = coerce('visible');
16742 if(!visible) return;
16743
16744 handleAnnotationCommonDefaults(annIn, annOut, opts.fullLayout, coerce);
16745
16746 coercePosition('x');
16747 coercePosition('y');
16748 coercePosition('z');
16749
16750 // if you have one coordinate you should all three
16751 Lib.noneOrAll(annIn, annOut, ['x', 'y', 'z']);
16752
16753 // hard-set here for completeness
16754 annOut.xref = 'x';
16755 annOut.yref = 'y';
16756 annOut.zref = 'z';
16757
16758 coerce('xanchor');
16759 coerce('yanchor');
16760 coerce('xshift');
16761 coerce('yshift');
16762
16763 if(annOut.showarrow) {
16764 annOut.axref = 'pixel';
16765 annOut.ayref = 'pixel';
16766
16767 // TODO maybe default values should be bigger than the 2D case?
16768 coerce('ax', -10);
16769 coerce('ay', -30);
16770
16771 // if you have one part of arrow length you should have both
16772 Lib.noneOrAll(annIn, annOut, ['ax', 'ay']);
16773 }
16774}
16775
16776},{"../../lib":178,"../../plots/array_container_defaults":218,"../../plots/cartesian/axes":222,"../annotations/common_defaults":40,"./attributes":46}],49:[function(_dereq_,module,exports){
16777/**
16778* Copyright 2012-2020, Plotly, Inc.
16779* All rights reserved.
16780*
16781* This source code is licensed under the MIT license found in the
16782* LICENSE file in the root directory of this source tree.
16783*/
16784
16785'use strict';
16786
16787var drawRaw = _dereq_('../annotations/draw').drawRaw;
16788var project = _dereq_('../../plots/gl3d/project');
16789var axLetters = ['x', 'y', 'z'];
16790
16791module.exports = function draw(scene) {
16792 var fullSceneLayout = scene.fullSceneLayout;
16793 var dataScale = scene.dataScale;
16794 var anns = fullSceneLayout.annotations;
16795
16796 for(var i = 0; i < anns.length; i++) {
16797 var ann = anns[i];
16798 var annotationIsOffscreen = false;
16799
16800 for(var j = 0; j < 3; j++) {
16801 var axLetter = axLetters[j];
16802 var pos = ann[axLetter];
16803 var ax = fullSceneLayout[axLetter + 'axis'];
16804 var posFraction = ax.r2fraction(pos);
16805
16806 if(posFraction < 0 || posFraction > 1) {
16807 annotationIsOffscreen = true;
16808 break;
16809 }
16810 }
16811
16812 if(annotationIsOffscreen) {
16813 scene.fullLayout._infolayer
16814 .select('.annotation-' + scene.id + '[data-index="' + i + '"]')
16815 .remove();
16816 } else {
16817 ann._pdata = project(scene.glplot.cameraParams, [
16818 fullSceneLayout.xaxis.r2l(ann.x) * dataScale[0],
16819 fullSceneLayout.yaxis.r2l(ann.y) * dataScale[1],
16820 fullSceneLayout.zaxis.r2l(ann.z) * dataScale[2]
16821 ]);
16822
16823 drawRaw(scene.graphDiv, ann, i, scene.id, ann._xa, ann._ya);
16824 }
16825 }
16826};
16827
16828},{"../../plots/gl3d/project":253,"../annotations/draw":43}],50:[function(_dereq_,module,exports){
16829/**
16830* Copyright 2012-2020, Plotly, Inc.
16831* All rights reserved.
16832*
16833* This source code is licensed under the MIT license found in the
16834* LICENSE file in the root directory of this source tree.
16835*/
16836
16837'use strict';
16838
16839var Registry = _dereq_('../../registry');
16840var Lib = _dereq_('../../lib');
16841
16842module.exports = {
16843 moduleType: 'component',
16844 name: 'annotations3d',
16845
16846 schema: {
16847 subplots: {
16848 scene: {annotations: _dereq_('./attributes')}
16849 }
16850 },
16851
16852 layoutAttributes: _dereq_('./attributes'),
16853 handleDefaults: _dereq_('./defaults'),
16854 includeBasePlot: includeGL3D,
16855
16856 convert: _dereq_('./convert'),
16857 draw: _dereq_('./draw')
16858};
16859
16860function includeGL3D(layoutIn, layoutOut) {
16861 var GL3D = Registry.subplotsRegistry.gl3d;
16862 if(!GL3D) return;
16863
16864 var attrRegex = GL3D.attrRegex;
16865
16866 var keys = Object.keys(layoutIn);
16867 for(var i = 0; i < keys.length; i++) {
16868 var k = keys[i];
16869 if(attrRegex.test(k) && (layoutIn[k].annotations || []).length) {
16870 Lib.pushUnique(layoutOut._basePlotModules, GL3D);
16871 Lib.pushUnique(layoutOut._subplots.gl3d, k);
16872 }
16873 }
16874}
16875
16876},{"../../lib":178,"../../registry":269,"./attributes":46,"./convert":47,"./defaults":48,"./draw":49}],51:[function(_dereq_,module,exports){
16877/**
16878* Copyright 2012-2020, Plotly, Inc.
16879* All rights reserved.
16880*
16881* This source code is licensed under the MIT license found in the
16882* LICENSE file in the root directory of this source tree.
16883*/
16884
16885'use strict';
16886
16887
16888// IMPORTANT - default colors should be in hex for compatibility
16889exports.defaults = [
16890 '#1f77b4', // muted blue
16891 '#ff7f0e', // safety orange
16892 '#2ca02c', // cooked asparagus green
16893 '#d62728', // brick red
16894 '#9467bd', // muted purple
16895 '#8c564b', // chestnut brown
16896 '#e377c2', // raspberry yogurt pink
16897 '#7f7f7f', // middle gray
16898 '#bcbd22', // curry yellow-green
16899 '#17becf' // blue-teal
16900];
16901
16902exports.defaultLine = '#444';
16903
16904exports.lightLine = '#eee';
16905
16906exports.background = '#fff';
16907
16908exports.borderLine = '#BEC8D9';
16909
16910// with axis.color and Color.interp we aren't using lightLine
16911// itself anymore, instead interpolating between axis.color
16912// and the background color using tinycolor.mix. lightFraction
16913// gives back exactly lightLine if the other colors are defaults.
16914exports.lightFraction = 100 * (0xe - 0x4) / (0xf - 0x4);
16915
16916},{}],52:[function(_dereq_,module,exports){
16917/**
16918* Copyright 2012-2020, Plotly, Inc.
16919* All rights reserved.
16920*
16921* This source code is licensed under the MIT license found in the
16922* LICENSE file in the root directory of this source tree.
16923*/
16924
16925
16926'use strict';
16927
16928var tinycolor = _dereq_('tinycolor2');
16929var isNumeric = _dereq_('fast-isnumeric');
16930
16931var color = module.exports = {};
16932
16933var colorAttrs = _dereq_('./attributes');
16934color.defaults = colorAttrs.defaults;
16935var defaultLine = color.defaultLine = colorAttrs.defaultLine;
16936color.lightLine = colorAttrs.lightLine;
16937var background = color.background = colorAttrs.background;
16938
16939/*
16940 * tinyRGB: turn a tinycolor into an rgb string, but
16941 * unlike the built-in tinycolor.toRgbString this never includes alpha
16942 */
16943color.tinyRGB = function(tc) {
16944 var c = tc.toRgb();
16945 return 'rgb(' + Math.round(c.r) + ', ' +
16946 Math.round(c.g) + ', ' + Math.round(c.b) + ')';
16947};
16948
16949color.rgb = function(cstr) { return color.tinyRGB(tinycolor(cstr)); };
16950
16951color.opacity = function(cstr) { return cstr ? tinycolor(cstr).getAlpha() : 0; };
16952
16953color.addOpacity = function(cstr, op) {
16954 var c = tinycolor(cstr).toRgb();
16955 return 'rgba(' + Math.round(c.r) + ', ' +
16956 Math.round(c.g) + ', ' + Math.round(c.b) + ', ' + op + ')';
16957};
16958
16959// combine two colors into one apparent color
16960// if back has transparency or is missing,
16961// color.background is assumed behind it
16962color.combine = function(front, back) {
16963 var fc = tinycolor(front).toRgb();
16964 if(fc.a === 1) return tinycolor(front).toRgbString();
16965
16966 var bc = tinycolor(back || background).toRgb();
16967 var bcflat = bc.a === 1 ? bc : {
16968 r: 255 * (1 - bc.a) + bc.r * bc.a,
16969 g: 255 * (1 - bc.a) + bc.g * bc.a,
16970 b: 255 * (1 - bc.a) + bc.b * bc.a
16971 };
16972 var fcflat = {
16973 r: bcflat.r * (1 - fc.a) + fc.r * fc.a,
16974 g: bcflat.g * (1 - fc.a) + fc.g * fc.a,
16975 b: bcflat.b * (1 - fc.a) + fc.b * fc.a
16976 };
16977 return tinycolor(fcflat).toRgbString();
16978};
16979
16980/*
16981 * Create a color that contrasts with cstr.
16982 *
16983 * If cstr is a dark color, we lighten it; if it's light, we darken.
16984 *
16985 * If lightAmount / darkAmount are used, we adjust by these percentages,
16986 * otherwise we go all the way to white or black.
16987 */
16988color.contrast = function(cstr, lightAmount, darkAmount) {
16989 var tc = tinycolor(cstr);
16990
16991 if(tc.getAlpha() !== 1) tc = tinycolor(color.combine(cstr, background));
16992
16993 var newColor = tc.isDark() ?
16994 (lightAmount ? tc.lighten(lightAmount) : background) :
16995 (darkAmount ? tc.darken(darkAmount) : defaultLine);
16996
16997 return newColor.toString();
16998};
16999
17000color.stroke = function(s, c) {
17001 var tc = tinycolor(c);
17002 s.style({'stroke': color.tinyRGB(tc), 'stroke-opacity': tc.getAlpha()});
17003};
17004
17005color.fill = function(s, c) {
17006 var tc = tinycolor(c);
17007 s.style({
17008 'fill': color.tinyRGB(tc),
17009 'fill-opacity': tc.getAlpha()
17010 });
17011};
17012
17013// search container for colors with the deprecated rgb(fractions) format
17014// and convert them to rgb(0-255 values)
17015color.clean = function(container) {
17016 if(!container || typeof container !== 'object') return;
17017
17018 var keys = Object.keys(container);
17019 var i, j, key, val;
17020
17021 for(i = 0; i < keys.length; i++) {
17022 key = keys[i];
17023 val = container[key];
17024
17025 if(key.substr(key.length - 5) === 'color') {
17026 // only sanitize keys that end in "color" or "colorscale"
17027
17028 if(Array.isArray(val)) {
17029 for(j = 0; j < val.length; j++) val[j] = cleanOne(val[j]);
17030 } else container[key] = cleanOne(val);
17031 } else if(key.substr(key.length - 10) === 'colorscale' && Array.isArray(val)) {
17032 // colorscales have the format [[0, color1], [frac, color2], ... [1, colorN]]
17033
17034 for(j = 0; j < val.length; j++) {
17035 if(Array.isArray(val[j])) val[j][1] = cleanOne(val[j][1]);
17036 }
17037 } else if(Array.isArray(val)) {
17038 // recurse into arrays of objects, and plain objects
17039
17040 var el0 = val[0];
17041 if(!Array.isArray(el0) && el0 && typeof el0 === 'object') {
17042 for(j = 0; j < val.length; j++) color.clean(val[j]);
17043 }
17044 } else if(val && typeof val === 'object') color.clean(val);
17045 }
17046};
17047
17048function cleanOne(val) {
17049 if(isNumeric(val) || typeof val !== 'string') return val;
17050
17051 var valTrim = val.trim();
17052 if(valTrim.substr(0, 3) !== 'rgb') return val;
17053
17054 var match = valTrim.match(/^rgba?\s*\(([^()]*)\)$/);
17055 if(!match) return val;
17056
17057 var parts = match[1].trim().split(/\s*[\s,]\s*/);
17058 var rgba = valTrim.charAt(3) === 'a' && parts.length === 4;
17059 if(!rgba && parts.length !== 3) return val;
17060
17061 for(var i = 0; i < parts.length; i++) {
17062 if(!parts[i].length) return val;
17063 parts[i] = Number(parts[i]);
17064
17065 if(!(parts[i] >= 0)) {
17066 // all parts must be non-negative numbers
17067
17068 return val;
17069 }
17070
17071 if(i === 3) {
17072 // alpha>1 gets clipped to 1
17073
17074 if(parts[i] > 1) parts[i] = 1;
17075 } else if(parts[i] >= 1) {
17076 // r, g, b must be < 1 (ie 1 itself is not allowed)
17077
17078 return val;
17079 }
17080 }
17081
17082 var rgbStr = Math.round(parts[0] * 255) + ', ' +
17083 Math.round(parts[1] * 255) + ', ' +
17084 Math.round(parts[2] * 255);
17085
17086 if(rgba) return 'rgba(' + rgbStr + ', ' + parts[3] + ')';
17087 return 'rgb(' + rgbStr + ')';
17088}
17089
17090},{"./attributes":51,"fast-isnumeric":18,"tinycolor2":35}],53:[function(_dereq_,module,exports){
17091/**
17092* Copyright 2012-2020, Plotly, Inc.
17093* All rights reserved.
17094*
17095* This source code is licensed under the MIT license found in the
17096* LICENSE file in the root directory of this source tree.
17097*/
17098
17099'use strict';
17100
17101var axesAttrs = _dereq_('../../plots/cartesian/layout_attributes');
17102var fontAttrs = _dereq_('../../plots/font_attributes');
17103var extendFlat = _dereq_('../../lib/extend').extendFlat;
17104var overrideAll = _dereq_('../../plot_api/edit_types').overrideAll;
17105
17106
17107module.exports = overrideAll({
17108// TODO: only right is supported currently
17109// orient: {
17110// valType: 'enumerated',
17111//
17112// values: ['left', 'right', 'top', 'bottom'],
17113// dflt: 'right',
17114//
17115// },
17116 thicknessmode: {
17117 valType: 'enumerated',
17118 values: ['fraction', 'pixels'],
17119
17120 dflt: 'pixels',
17121
17122 },
17123 thickness: {
17124 valType: 'number',
17125
17126 min: 0,
17127 dflt: 30,
17128
17129 },
17130 lenmode: {
17131 valType: 'enumerated',
17132 values: ['fraction', 'pixels'],
17133
17134 dflt: 'fraction',
17135
17136 },
17137 len: {
17138 valType: 'number',
17139 min: 0,
17140 dflt: 1,
17141
17142
17143 },
17144 x: {
17145 valType: 'number',
17146 dflt: 1.02,
17147 min: -2,
17148 max: 3,
17149
17150
17151 },
17152 xanchor: {
17153 valType: 'enumerated',
17154 values: ['left', 'center', 'right'],
17155 dflt: 'left',
17156
17157
17158 },
17159 xpad: {
17160 valType: 'number',
17161
17162 min: 0,
17163 dflt: 10,
17164
17165 },
17166 y: {
17167 valType: 'number',
17168
17169 dflt: 0.5,
17170 min: -2,
17171 max: 3,
17172
17173 },
17174 yanchor: {
17175 valType: 'enumerated',
17176 values: ['top', 'middle', 'bottom'],
17177
17178 dflt: 'middle',
17179
17180 },
17181 ypad: {
17182 valType: 'number',
17183
17184 min: 0,
17185 dflt: 10,
17186
17187 },
17188 // a possible line around the bar itself
17189 outlinecolor: axesAttrs.linecolor,
17190 outlinewidth: axesAttrs.linewidth,
17191 // Should outlinewidth have {dflt: 0} ?
17192 // another possible line outside the padding and tick labels
17193 bordercolor: axesAttrs.linecolor,
17194 borderwidth: {
17195 valType: 'number',
17196
17197 min: 0,
17198 dflt: 0,
17199
17200 },
17201 bgcolor: {
17202 valType: 'color',
17203
17204 dflt: 'rgba(0,0,0,0)',
17205
17206 },
17207 // tick and title properties named and function exactly as in axes
17208 tickmode: axesAttrs.tickmode,
17209 nticks: axesAttrs.nticks,
17210 tick0: axesAttrs.tick0,
17211 dtick: axesAttrs.dtick,
17212 tickvals: axesAttrs.tickvals,
17213 ticktext: axesAttrs.ticktext,
17214 ticks: extendFlat({}, axesAttrs.ticks, {dflt: ''}),
17215 ticklen: axesAttrs.ticklen,
17216 tickwidth: axesAttrs.tickwidth,
17217 tickcolor: axesAttrs.tickcolor,
17218 showticklabels: axesAttrs.showticklabels,
17219 tickfont: fontAttrs({
17220
17221 }),
17222 tickangle: axesAttrs.tickangle,
17223 tickformat: axesAttrs.tickformat,
17224 tickformatstops: axesAttrs.tickformatstops,
17225 tickprefix: axesAttrs.tickprefix,
17226 showtickprefix: axesAttrs.showtickprefix,
17227 ticksuffix: axesAttrs.ticksuffix,
17228 showticksuffix: axesAttrs.showticksuffix,
17229 separatethousands: axesAttrs.separatethousands,
17230 exponentformat: axesAttrs.exponentformat,
17231 showexponent: axesAttrs.showexponent,
17232 title: {
17233 text: {
17234 valType: 'string',
17235
17236
17237 },
17238 font: fontAttrs({
17239
17240 }),
17241 side: {
17242 valType: 'enumerated',
17243 values: ['right', 'top', 'bottom'],
17244
17245 dflt: 'top',
17246
17247 }
17248 },
17249
17250 _deprecated: {
17251 title: {
17252 valType: 'string',
17253
17254
17255 },
17256 titlefont: fontAttrs({
17257
17258 }),
17259 titleside: {
17260 valType: 'enumerated',
17261 values: ['right', 'top', 'bottom'],
17262
17263 dflt: 'top',
17264
17265 }
17266 }
17267}, 'colorbars', 'from-root');
17268
17269},{"../../lib/extend":173,"../../plot_api/edit_types":205,"../../plots/cartesian/layout_attributes":236,"../../plots/font_attributes":250}],54:[function(_dereq_,module,exports){
17270/**
17271* Copyright 2012-2020, Plotly, Inc.
17272* All rights reserved.
17273*
17274* This source code is licensed under the MIT license found in the
17275* LICENSE file in the root directory of this source tree.
17276*/
17277
17278'use strict';
17279
17280module.exports = {
17281 cn: {
17282 colorbar: 'colorbar',
17283 cbbg: 'cbbg',
17284 cbfill: 'cbfill',
17285 cbfills: 'cbfills',
17286 cbline: 'cbline',
17287 cblines: 'cblines',
17288 cbaxis: 'cbaxis',
17289 cbtitleunshift: 'cbtitleunshift',
17290 cbtitle: 'cbtitle',
17291 cboutline: 'cboutline',
17292 crisp: 'crisp',
17293 jsPlaceholder: 'js-placeholder'
17294 }
17295};
17296
17297},{}],55:[function(_dereq_,module,exports){
17298/**
17299* Copyright 2012-2020, Plotly, Inc.
17300* All rights reserved.
17301*
17302* This source code is licensed under the MIT license found in the
17303* LICENSE file in the root directory of this source tree.
17304*/
17305
17306
17307'use strict';
17308
17309var Lib = _dereq_('../../lib');
17310var Template = _dereq_('../../plot_api/plot_template');
17311
17312var handleTickValueDefaults = _dereq_('../../plots/cartesian/tick_value_defaults');
17313var handleTickMarkDefaults = _dereq_('../../plots/cartesian/tick_mark_defaults');
17314var handleTickLabelDefaults = _dereq_('../../plots/cartesian/tick_label_defaults');
17315
17316var attributes = _dereq_('./attributes');
17317
17318module.exports = function colorbarDefaults(containerIn, containerOut, layout) {
17319 var colorbarOut = Template.newContainer(containerOut, 'colorbar');
17320 var colorbarIn = containerIn.colorbar || {};
17321
17322 function coerce(attr, dflt) {
17323 return Lib.coerce(colorbarIn, colorbarOut, attributes, attr, dflt);
17324 }
17325
17326 var thicknessmode = coerce('thicknessmode');
17327 coerce('thickness', (thicknessmode === 'fraction') ?
17328 30 / (layout.width - layout.margin.l - layout.margin.r) :
17329 30
17330 );
17331
17332 var lenmode = coerce('lenmode');
17333 coerce('len', (lenmode === 'fraction') ?
17334 1 :
17335 layout.height - layout.margin.t - layout.margin.b
17336 );
17337
17338 coerce('x');
17339 coerce('xanchor');
17340 coerce('xpad');
17341 coerce('y');
17342 coerce('yanchor');
17343 coerce('ypad');
17344 Lib.noneOrAll(colorbarIn, colorbarOut, ['x', 'y']);
17345
17346 coerce('outlinecolor');
17347 coerce('outlinewidth');
17348 coerce('bordercolor');
17349 coerce('borderwidth');
17350 coerce('bgcolor');
17351
17352 handleTickValueDefaults(colorbarIn, colorbarOut, coerce, 'linear');
17353
17354 var opts = {outerTicks: false, font: layout.font};
17355 handleTickLabelDefaults(colorbarIn, colorbarOut, coerce, 'linear', opts);
17356 handleTickMarkDefaults(colorbarIn, colorbarOut, coerce, 'linear', opts);
17357
17358 coerce('title.text', layout._dfltTitle.colorbar);
17359 Lib.coerceFont(coerce, 'title.font', layout.font);
17360 coerce('title.side');
17361};
17362
17363},{"../../lib":178,"../../plot_api/plot_template":212,"../../plots/cartesian/tick_label_defaults":243,"../../plots/cartesian/tick_mark_defaults":244,"../../plots/cartesian/tick_value_defaults":245,"./attributes":53}],56:[function(_dereq_,module,exports){
17364/**
17365* Copyright 2012-2020, Plotly, Inc.
17366* All rights reserved.
17367*
17368* This source code is licensed under the MIT license found in the
17369* LICENSE file in the root directory of this source tree.
17370*/
17371
17372'use strict';
17373
17374var d3 = _dereq_('d3');
17375var tinycolor = _dereq_('tinycolor2');
17376
17377var Plots = _dereq_('../../plots/plots');
17378var Registry = _dereq_('../../registry');
17379var Axes = _dereq_('../../plots/cartesian/axes');
17380var dragElement = _dereq_('../dragelement');
17381var Lib = _dereq_('../../lib');
17382var extendFlat = _dereq_('../../lib/extend').extendFlat;
17383var setCursor = _dereq_('../../lib/setcursor');
17384var Drawing = _dereq_('../drawing');
17385var Color = _dereq_('../color');
17386var Titles = _dereq_('../titles');
17387var svgTextUtils = _dereq_('../../lib/svg_text_utils');
17388var flipScale = _dereq_('../colorscale/helpers').flipScale;
17389
17390var handleAxisDefaults = _dereq_('../../plots/cartesian/axis_defaults');
17391var handleAxisPositionDefaults = _dereq_('../../plots/cartesian/position_defaults');
17392var axisLayoutAttrs = _dereq_('../../plots/cartesian/layout_attributes');
17393
17394var alignmentConstants = _dereq_('../../constants/alignment');
17395var LINE_SPACING = alignmentConstants.LINE_SPACING;
17396var FROM_TL = alignmentConstants.FROM_TL;
17397var FROM_BR = alignmentConstants.FROM_BR;
17398
17399var cn = _dereq_('./constants').cn;
17400
17401function draw(gd) {
17402 var fullLayout = gd._fullLayout;
17403
17404 var colorBars = fullLayout._infolayer
17405 .selectAll('g.' + cn.colorbar)
17406 .data(makeColorBarData(gd), function(opts) { return opts._id; });
17407
17408 colorBars.enter().append('g')
17409 .attr('class', function(opts) { return opts._id; })
17410 .classed(cn.colorbar, true);
17411
17412 colorBars.each(function(opts) {
17413 var g = d3.select(this);
17414
17415 Lib.ensureSingle(g, 'rect', cn.cbbg);
17416 Lib.ensureSingle(g, 'g', cn.cbfills);
17417 Lib.ensureSingle(g, 'g', cn.cblines);
17418 Lib.ensureSingle(g, 'g', cn.cbaxis, function(s) { s.classed(cn.crisp, true); });
17419 Lib.ensureSingle(g, 'g', cn.cbtitleunshift, function(s) { s.append('g').classed(cn.cbtitle, true); });
17420 Lib.ensureSingle(g, 'rect', cn.cboutline);
17421
17422 var done = drawColorBar(g, opts, gd);
17423 if(done && done.then) (gd._promises || []).push(done);
17424
17425 if(gd._context.edits.colorbarPosition) {
17426 makeEditable(g, opts, gd);
17427 }
17428 });
17429
17430 colorBars.exit()
17431 .each(function(opts) { Plots.autoMargin(gd, opts._id); })
17432 .remove();
17433
17434 colorBars.order();
17435}
17436
17437function makeColorBarData(gd) {
17438 var fullLayout = gd._fullLayout;
17439 var calcdata = gd.calcdata;
17440 var out = [];
17441
17442 // single out item
17443 var opts;
17444 // colorbar attr parent container
17445 var cont;
17446 // trace attr container
17447 var trace;
17448 // colorbar options
17449 var cbOpt;
17450
17451 function initOpts(opts) {
17452 return extendFlat(opts, {
17453 // fillcolor can be a d3 scale, domain is z values, range is colors
17454 // or leave it out for no fill,
17455 // or set to a string constant for single-color fill
17456 _fillcolor: null,
17457 // line.color has the same options as fillcolor
17458 _line: {color: null, width: null, dash: null},
17459 // levels of lines to draw.
17460 // note that this DOES NOT determine the extent of the bar
17461 // that's given by the domain of fillcolor
17462 // (or line.color if no fillcolor domain)
17463 _levels: {start: null, end: null, size: null},
17464 // separate fill levels (for example, heatmap coloring of a
17465 // contour map) if this is omitted, fillcolors will be
17466 // evaluated halfway between levels
17467 _filllevels: null,
17468 // for continuous colorscales: fill with a gradient instead of explicit levels
17469 // value should be the colorscale [[0, c0], [v1, c1], ..., [1, cEnd]]
17470 _fillgradient: null,
17471 // when using a gradient, we need the data range specified separately
17472 _zrange: null
17473 });
17474 }
17475
17476 function calcOpts() {
17477 if(typeof cbOpt.calc === 'function') {
17478 cbOpt.calc(gd, trace, opts);
17479 } else {
17480 opts._fillgradient = cont.reversescale ?
17481 flipScale(cont.colorscale) :
17482 cont.colorscale;
17483 opts._zrange = [cont[cbOpt.min], cont[cbOpt.max]];
17484 }
17485 }
17486
17487 for(var i = 0; i < calcdata.length; i++) {
17488 var cd = calcdata[i];
17489 trace = cd[0].trace;
17490 var moduleOpts = trace._module.colorbar;
17491
17492 if(trace.visible === true && moduleOpts) {
17493 var allowsMultiplotCbs = Array.isArray(moduleOpts);
17494 var cbOpts = allowsMultiplotCbs ? moduleOpts : [moduleOpts];
17495
17496 for(var j = 0; j < cbOpts.length; j++) {
17497 cbOpt = cbOpts[j];
17498 var contName = cbOpt.container;
17499 cont = contName ? trace[contName] : trace;
17500
17501 if(cont && cont.showscale) {
17502 opts = initOpts(cont.colorbar);
17503 opts._id = 'cb' + trace.uid + (allowsMultiplotCbs && contName ? '-' + contName : '');
17504 opts._traceIndex = trace.index;
17505 opts._propPrefix = (contName ? contName + '.' : '') + 'colorbar.';
17506 opts._meta = trace._meta;
17507 calcOpts();
17508 out.push(opts);
17509 }
17510 }
17511 }
17512 }
17513
17514 for(var k in fullLayout._colorAxes) {
17515 cont = fullLayout[k];
17516
17517 if(cont.showscale) {
17518 var colorAxOpts = fullLayout._colorAxes[k];
17519
17520 opts = initOpts(cont.colorbar);
17521 opts._id = 'cb' + k;
17522 opts._propPrefix = k + '.colorbar.';
17523 opts._meta = fullLayout._meta;
17524
17525 cbOpt = {min: 'cmin', max: 'cmax'};
17526 if(colorAxOpts[0] !== 'heatmap') {
17527 trace = colorAxOpts[1];
17528 cbOpt.calc = trace._module.colorbar.calc;
17529 }
17530
17531 calcOpts();
17532 out.push(opts);
17533 }
17534 }
17535
17536 return out;
17537}
17538
17539function drawColorBar(g, opts, gd) {
17540 var fullLayout = gd._fullLayout;
17541 var gs = fullLayout._size;
17542
17543 var fillColor = opts._fillcolor;
17544 var line = opts._line;
17545 var title = opts.title;
17546 var titleSide = title.side;
17547
17548 var zrange = opts._zrange ||
17549 d3.extent((typeof fillColor === 'function' ? fillColor : line.color).domain());
17550
17551 var lineColormap = typeof line.color === 'function' ?
17552 line.color :
17553 function() { return line.color; };
17554 var fillColormap = typeof fillColor === 'function' ?
17555 fillColor :
17556 function() { return fillColor; };
17557
17558 var levelsIn = opts._levels;
17559 var levelsOut = calcLevels(gd, opts, zrange);
17560 var fillLevels = levelsOut.fill;
17561 var lineLevels = levelsOut.line;
17562
17563 // we calculate pixel sizes based on the specified graph size,
17564 // not the actual (in case something pushed the margins around)
17565 // which is a little odd but avoids an odd iterative effect
17566 // when the colorbar itself is pushing the margins.
17567 // but then the fractional size is calculated based on the
17568 // actual graph size, so that the axes will size correctly.
17569 var thickPx = Math.round(opts.thickness * (opts.thicknessmode === 'fraction' ? gs.w : 1));
17570 var thickFrac = thickPx / gs.w;
17571 var lenPx = Math.round(opts.len * (opts.lenmode === 'fraction' ? gs.h : 1));
17572 var lenFrac = lenPx / gs.h;
17573 var xpadFrac = opts.xpad / gs.w;
17574 var yExtraPx = (opts.borderwidth + opts.outlinewidth) / 2;
17575 var ypadFrac = opts.ypad / gs.h;
17576
17577 // x positioning: do it initially just for left anchor,
17578 // then fix at the end (since we don't know the width yet)
17579 var xLeft = Math.round(opts.x * gs.w + opts.xpad);
17580 // for dragging... this is getting a little muddled...
17581 var xLeftFrac = opts.x - thickFrac * ({middle: 0.5, right: 1}[opts.xanchor] || 0);
17582
17583 // y positioning we can do correctly from the start
17584 var yBottomFrac = opts.y + lenFrac * (({top: -0.5, bottom: 0.5}[opts.yanchor] || 0) - 0.5);
17585 var yBottomPx = Math.round(gs.h * (1 - yBottomFrac));
17586 var yTopPx = yBottomPx - lenPx;
17587
17588 // stash a few things for makeEditable
17589 opts._lenFrac = lenFrac;
17590 opts._thickFrac = thickFrac;
17591 opts._xLeftFrac = xLeftFrac;
17592 opts._yBottomFrac = yBottomFrac;
17593
17594 // stash mocked axis for contour label formatting
17595 var ax = opts._axis = mockColorBarAxis(gd, opts, zrange);
17596
17597 // position can't go in through supplyDefaults
17598 // because that restricts it to [0,1]
17599 ax.position = opts.x + xpadFrac + thickFrac;
17600
17601 if(['top', 'bottom'].indexOf(titleSide) !== -1) {
17602 ax.title.side = titleSide;
17603 ax.titlex = opts.x + xpadFrac;
17604 ax.titley = yBottomFrac + (title.side === 'top' ? lenFrac - ypadFrac : ypadFrac);
17605 }
17606
17607 if(line.color && opts.tickmode === 'auto') {
17608 ax.tickmode = 'linear';
17609 ax.tick0 = levelsIn.start;
17610 var dtick = levelsIn.size;
17611 // expand if too many contours, so we don't get too many ticks
17612 var autoNtick = Lib.constrain((yBottomPx - yTopPx) / 50, 4, 15) + 1;
17613 var dtFactor = (zrange[1] - zrange[0]) / ((opts.nticks || autoNtick) * dtick);
17614 if(dtFactor > 1) {
17615 var dtexp = Math.pow(10, Math.floor(Math.log(dtFactor) / Math.LN10));
17616 dtick *= dtexp * Lib.roundUp(dtFactor / dtexp, [2, 5, 10]);
17617 // if the contours are at round multiples, reset tick0
17618 // so they're still at round multiples. Otherwise,
17619 // keep the first label on the first contour level
17620 if((Math.abs(levelsIn.start) / levelsIn.size + 1e-6) % 1 < 2e-6) {
17621 ax.tick0 = 0;
17622 }
17623 }
17624 ax.dtick = dtick;
17625 }
17626
17627 // set domain after init, because we may want to
17628 // allow it outside [0,1]
17629 ax.domain = [
17630 yBottomFrac + ypadFrac,
17631 yBottomFrac + lenFrac - ypadFrac
17632 ];
17633
17634 ax.setScale();
17635
17636 g.attr('transform', 'translate(' + Math.round(gs.l) + ',' + Math.round(gs.t) + ')');
17637
17638 var titleCont = g.select('.' + cn.cbtitleunshift)
17639 .attr('transform', 'translate(-' + Math.round(gs.l) + ',-' + Math.round(gs.t) + ')');
17640
17641 var axLayer = g.select('.' + cn.cbaxis);
17642 var titleEl;
17643 var titleHeight = 0;
17644
17645 function drawTitle(titleClass, titleOpts) {
17646 var dfltTitleOpts = {
17647 propContainer: ax,
17648 propName: opts._propPrefix + 'title',
17649 traceIndex: opts._traceIndex,
17650 _meta: opts._meta,
17651 placeholder: fullLayout._dfltTitle.colorbar,
17652 containerGroup: g.select('.' + cn.cbtitle)
17653 };
17654
17655 // this class-to-rotate thing with convertToTspans is
17656 // getting hackier and hackier... delete groups with the
17657 // wrong class (in case earlier the colorbar was drawn on
17658 // a different side, I think?)
17659 var otherClass = titleClass.charAt(0) === 'h' ?
17660 titleClass.substr(1) :
17661 'h' + titleClass;
17662 g.selectAll('.' + otherClass + ',.' + otherClass + '-math-group').remove();
17663
17664 Titles.draw(gd, titleClass, extendFlat(dfltTitleOpts, titleOpts || {}));
17665 }
17666
17667 function drawDummyTitle() {
17668 if(['top', 'bottom'].indexOf(titleSide) !== -1) {
17669 // draw the title so we know how much room it needs
17670 // when we squish the axis. This one only applies to
17671 // top or bottom titles, not right side.
17672 var x = gs.l + (opts.x + xpadFrac) * gs.w;
17673 var fontSize = ax.title.font.size;
17674 var y;
17675
17676 if(titleSide === 'top') {
17677 y = (1 - (yBottomFrac + lenFrac - ypadFrac)) * gs.h +
17678 gs.t + 3 + fontSize * 0.75;
17679 } else {
17680 y = (1 - (yBottomFrac + ypadFrac)) * gs.h +
17681 gs.t - 3 - fontSize * 0.25;
17682 }
17683 drawTitle(ax._id + 'title', {
17684 attributes: {x: x, y: y, 'text-anchor': 'start'}
17685 });
17686 }
17687 }
17688
17689 function drawCbTitle() {
17690 if(['top', 'bottom'].indexOf(titleSide) === -1) {
17691 var fontSize = ax.title.font.size;
17692 var y = ax._offset + ax._length / 2;
17693 var x = gs.l + (ax.position || 0) * gs.w + ((ax.side === 'right') ?
17694 10 + fontSize * ((ax.showticklabels ? 1 : 0.5)) :
17695 -10 - fontSize * ((ax.showticklabels ? 0.5 : 0)));
17696
17697 // the 'h' + is a hack to get around the fact that
17698 // convertToTspans rotates any 'y...' class by 90 degrees.
17699 // TODO: find a better way to control this.
17700 drawTitle('h' + ax._id + 'title', {
17701 avoid: {
17702 selection: d3.select(gd).selectAll('g.' + ax._id + 'tick'),
17703 side: titleSide,
17704 offsetLeft: gs.l,
17705 offsetTop: 0,
17706 maxShift: fullLayout.width
17707 },
17708 attributes: {x: x, y: y, 'text-anchor': 'middle'},
17709 transform: {rotate: '-90', offset: 0}
17710 });
17711 }
17712 }
17713
17714 function drawAxis() {
17715 if(['top', 'bottom'].indexOf(titleSide) !== -1) {
17716 // squish the axis top to make room for the title
17717 var titleGroup = g.select('.' + cn.cbtitle);
17718 var titleText = titleGroup.select('text');
17719 var titleTrans = [-opts.outlinewidth / 2, opts.outlinewidth / 2];
17720 var mathJaxNode = titleGroup
17721 .select('.h' + ax._id + 'title-math-group')
17722 .node();
17723 var lineSize = 15.6;
17724 if(titleText.node()) {
17725 lineSize = parseInt(titleText.node().style.fontSize, 10) * LINE_SPACING;
17726 }
17727 if(mathJaxNode) {
17728 titleHeight = Drawing.bBox(mathJaxNode).height;
17729 if(titleHeight > lineSize) {
17730 // not entirely sure how mathjax is doing
17731 // vertical alignment, but this seems to work.
17732 titleTrans[1] -= (titleHeight - lineSize) / 2;
17733 }
17734 } else if(titleText.node() && !titleText.classed(cn.jsPlaceholder)) {
17735 titleHeight = Drawing.bBox(titleText.node()).height;
17736 }
17737 if(titleHeight) {
17738 // buffer btwn colorbar and title
17739 // TODO: configurable
17740 titleHeight += 5;
17741
17742 if(titleSide === 'top') {
17743 ax.domain[1] -= titleHeight / gs.h;
17744 titleTrans[1] *= -1;
17745 } else {
17746 ax.domain[0] += titleHeight / gs.h;
17747 var nlines = svgTextUtils.lineCount(titleText);
17748 titleTrans[1] += (1 - nlines) * lineSize;
17749 }
17750
17751 titleGroup.attr('transform', 'translate(' + titleTrans + ')');
17752 ax.setScale();
17753 }
17754 }
17755
17756 g.selectAll('.' + cn.cbfills + ',.' + cn.cblines)
17757 .attr('transform', 'translate(0,' + Math.round(gs.h * (1 - ax.domain[1])) + ')');
17758
17759 axLayer.attr('transform', 'translate(0,' + Math.round(-gs.t) + ')');
17760
17761 var fills = g.select('.' + cn.cbfills)
17762 .selectAll('rect.' + cn.cbfill)
17763 .data(fillLevels);
17764 fills.enter().append('rect')
17765 .classed(cn.cbfill, true)
17766 .style('stroke', 'none');
17767 fills.exit().remove();
17768
17769 var zBounds = zrange
17770 .map(ax.c2p)
17771 .map(Math.round)
17772 .sort(function(a, b) { return a - b; });
17773
17774 fills.each(function(d, i) {
17775 var z = [
17776 (i === 0) ? zrange[0] : (fillLevels[i] + fillLevels[i - 1]) / 2,
17777 (i === fillLevels.length - 1) ? zrange[1] : (fillLevels[i] + fillLevels[i + 1]) / 2
17778 ]
17779 .map(ax.c2p)
17780 .map(Math.round);
17781
17782 // offset the side adjoining the next rectangle so they
17783 // overlap, to prevent antialiasing gaps
17784 z[1] = Lib.constrain(z[1] + (z[1] > z[0]) ? 1 : -1, zBounds[0], zBounds[1]);
17785
17786
17787 // Colorbar cannot currently support opacities so we
17788 // use an opaque fill even when alpha channels present
17789 var fillEl = d3.select(this).attr({
17790 x: xLeft,
17791 width: Math.max(thickPx, 2),
17792 y: d3.min(z),
17793 height: Math.max(d3.max(z) - d3.min(z), 2),
17794 });
17795
17796 if(opts._fillgradient) {
17797 Drawing.gradient(fillEl, gd, opts._id, 'vertical', opts._fillgradient, 'fill');
17798 } else {
17799 // tinycolor can't handle exponents and
17800 // at this scale, removing it makes no difference.
17801 var colorString = fillColormap(d).replace('e-', '');
17802 fillEl.attr('fill', tinycolor(colorString).toHexString());
17803 }
17804 });
17805
17806 var lines = g.select('.' + cn.cblines)
17807 .selectAll('path.' + cn.cbline)
17808 .data(line.color && line.width ? lineLevels : []);
17809 lines.enter().append('path')
17810 .classed(cn.cbline, true);
17811 lines.exit().remove();
17812 lines.each(function(d) {
17813 d3.select(this)
17814 .attr('d', 'M' + xLeft + ',' +
17815 (Math.round(ax.c2p(d)) + (line.width / 2) % 1) + 'h' + thickPx)
17816 .call(Drawing.lineGroupStyle, line.width, lineColormap(d), line.dash);
17817 });
17818
17819 // force full redraw of labels and ticks
17820 axLayer.selectAll('g.' + ax._id + 'tick,path').remove();
17821
17822 var shift = xLeft + thickPx +
17823 (opts.outlinewidth || 0) / 2 - (opts.ticks === 'outside' ? 1 : 0);
17824
17825 var vals = Axes.calcTicks(ax);
17826 var transFn = Axes.makeTransFn(ax);
17827 var tickSign = Axes.getTickSigns(ax)[2];
17828
17829 Axes.drawTicks(gd, ax, {
17830 vals: ax.ticks === 'inside' ? Axes.clipEnds(ax, vals) : vals,
17831 layer: axLayer,
17832 path: Axes.makeTickPath(ax, shift, tickSign),
17833 transFn: transFn
17834 });
17835
17836 return Axes.drawLabels(gd, ax, {
17837 vals: vals,
17838 layer: axLayer,
17839 transFn: transFn,
17840 labelFns: Axes.makeLabelFns(ax, shift)
17841 });
17842 }
17843
17844 // wait for the axis & title to finish rendering before
17845 // continuing positioning
17846 // TODO: why are we redrawing multiple times now with this?
17847 // I guess autoMargin doesn't like being post-promise?
17848 function positionCB() {
17849 var innerWidth = thickPx + opts.outlinewidth / 2 + Drawing.bBox(axLayer.node()).width;
17850 titleEl = titleCont.select('text');
17851
17852 if(titleEl.node() && !titleEl.classed(cn.jsPlaceholder)) {
17853 var mathJaxNode = titleCont.select('.h' + ax._id + 'title-math-group').node();
17854 var titleWidth;
17855 if(mathJaxNode && ['top', 'bottom'].indexOf(titleSide) !== -1) {
17856 titleWidth = Drawing.bBox(mathJaxNode).width;
17857 } else {
17858 // note: the formula below works for all title sides,
17859 // (except for top/bottom mathjax, above)
17860 // but the weird gs.l is because the titleunshift
17861 // transform gets removed by Drawing.bBox
17862 titleWidth = Drawing.bBox(titleCont.node()).right - xLeft - gs.l;
17863 }
17864 innerWidth = Math.max(innerWidth, titleWidth);
17865 }
17866
17867 var outerwidth = 2 * opts.xpad + innerWidth + opts.borderwidth + opts.outlinewidth / 2;
17868 var outerheight = yBottomPx - yTopPx;
17869
17870 g.select('.' + cn.cbbg).attr({
17871 x: xLeft - opts.xpad - (opts.borderwidth + opts.outlinewidth) / 2,
17872 y: yTopPx - yExtraPx,
17873 width: Math.max(outerwidth, 2),
17874 height: Math.max(outerheight + 2 * yExtraPx, 2)
17875 })
17876 .call(Color.fill, opts.bgcolor)
17877 .call(Color.stroke, opts.bordercolor)
17878 .style('stroke-width', opts.borderwidth);
17879
17880 g.selectAll('.' + cn.cboutline).attr({
17881 x: xLeft,
17882 y: yTopPx + opts.ypad + (titleSide === 'top' ? titleHeight : 0),
17883 width: Math.max(thickPx, 2),
17884 height: Math.max(outerheight - 2 * opts.ypad - titleHeight, 2)
17885 })
17886 .call(Color.stroke, opts.outlinecolor)
17887 .style({
17888 fill: 'none',
17889 'stroke-width': opts.outlinewidth
17890 });
17891
17892 // fix positioning for xanchor!='left'
17893 var xoffset = ({center: 0.5, right: 1}[opts.xanchor] || 0) * outerwidth;
17894 g.attr('transform', 'translate(' + (gs.l - xoffset) + ',' + gs.t + ')');
17895
17896 // auto margin adjustment
17897 var marginOpts = {};
17898 var tFrac = FROM_TL[opts.yanchor];
17899 var bFrac = FROM_BR[opts.yanchor];
17900 if(opts.lenmode === 'pixels') {
17901 marginOpts.y = opts.y;
17902 marginOpts.t = outerheight * tFrac;
17903 marginOpts.b = outerheight * bFrac;
17904 } else {
17905 marginOpts.t = marginOpts.b = 0;
17906 marginOpts.yt = opts.y + opts.len * tFrac;
17907 marginOpts.yb = opts.y - opts.len * bFrac;
17908 }
17909
17910 var lFrac = FROM_TL[opts.xanchor];
17911 var rFrac = FROM_BR[opts.xanchor];
17912 if(opts.thicknessmode === 'pixels') {
17913 marginOpts.x = opts.x;
17914 marginOpts.l = outerwidth * lFrac;
17915 marginOpts.r = outerwidth * rFrac;
17916 } else {
17917 var extraThickness = outerwidth - thickPx;
17918 marginOpts.l = extraThickness * lFrac;
17919 marginOpts.r = extraThickness * rFrac;
17920 marginOpts.xl = opts.x - opts.thickness * lFrac;
17921 marginOpts.xr = opts.x + opts.thickness * rFrac;
17922 }
17923
17924 Plots.autoMargin(gd, opts._id, marginOpts);
17925 }
17926
17927 return Lib.syncOrAsync([
17928 Plots.previousPromises,
17929 drawDummyTitle,
17930 drawAxis,
17931 drawCbTitle,
17932 Plots.previousPromises,
17933 positionCB
17934 ], gd);
17935}
17936
17937function makeEditable(g, opts, gd) {
17938 var fullLayout = gd._fullLayout;
17939 var gs = fullLayout._size;
17940 var t0, xf, yf;
17941
17942 dragElement.init({
17943 element: g.node(),
17944 gd: gd,
17945 prepFn: function() {
17946 t0 = g.attr('transform');
17947 setCursor(g);
17948 },
17949 moveFn: function(dx, dy) {
17950 g.attr('transform', t0 + ' ' + 'translate(' + dx + ',' + dy + ')');
17951
17952 xf = dragElement.align(opts._xLeftFrac + (dx / gs.w), opts._thickFrac,
17953 0, 1, opts.xanchor);
17954 yf = dragElement.align(opts._yBottomFrac - (dy / gs.h), opts._lenFrac,
17955 0, 1, opts.yanchor);
17956
17957 var csr = dragElement.getCursor(xf, yf, opts.xanchor, opts.yanchor);
17958 setCursor(g, csr);
17959 },
17960 doneFn: function() {
17961 setCursor(g);
17962
17963 if(xf !== undefined && yf !== undefined) {
17964 var update = {};
17965 update[opts._propPrefix + 'x'] = xf;
17966 update[opts._propPrefix + 'y'] = yf;
17967 if(opts._traceIndex !== undefined) {
17968 Registry.call('_guiRestyle', gd, update, opts._traceIndex);
17969 } else {
17970 Registry.call('_guiRelayout', gd, update);
17971 }
17972 }
17973 }
17974 });
17975}
17976
17977function calcLevels(gd, opts, zrange) {
17978 var levelsIn = opts._levels;
17979 var lineLevels = [];
17980 var fillLevels = [];
17981 var l;
17982 var i;
17983
17984 var l0 = levelsIn.end + levelsIn.size / 100;
17985 var ls = levelsIn.size;
17986 var zr0 = (1.001 * zrange[0] - 0.001 * zrange[1]);
17987 var zr1 = (1.001 * zrange[1] - 0.001 * zrange[0]);
17988
17989 for(i = 0; i < 1e5; i++) {
17990 l = levelsIn.start + i * ls;
17991 if(ls > 0 ? (l >= l0) : (l <= l0)) break;
17992 if(l > zr0 && l < zr1) lineLevels.push(l);
17993 }
17994
17995 if(opts._fillgradient) {
17996 fillLevels = [0];
17997 } else if(typeof opts._fillcolor === 'function') {
17998 var fillLevelsIn = opts._filllevels;
17999
18000 if(fillLevelsIn) {
18001 l0 = fillLevelsIn.end + fillLevelsIn.size / 100;
18002 ls = fillLevelsIn.size;
18003 for(i = 0; i < 1e5; i++) {
18004 l = fillLevelsIn.start + i * ls;
18005 if(ls > 0 ? (l >= l0) : (l <= l0)) break;
18006 if(l > zrange[0] && l < zrange[1]) fillLevels.push(l);
18007 }
18008 } else {
18009 fillLevels = lineLevels.map(function(v) {
18010 return v - levelsIn.size / 2;
18011 });
18012 fillLevels.push(fillLevels[fillLevels.length - 1] + levelsIn.size);
18013 }
18014 } else if(opts._fillcolor && typeof opts._fillcolor === 'string') {
18015 // doesn't matter what this value is, with a single value
18016 // we'll make a single fill rect covering the whole bar
18017 fillLevels = [0];
18018 }
18019
18020 if(levelsIn.size < 0) {
18021 lineLevels.reverse();
18022 fillLevels.reverse();
18023 }
18024
18025 return {line: lineLevels, fill: fillLevels};
18026}
18027
18028function mockColorBarAxis(gd, opts, zrange) {
18029 var fullLayout = gd._fullLayout;
18030
18031 var cbAxisIn = {
18032 type: 'linear',
18033 range: zrange,
18034 tickmode: opts.tickmode,
18035 nticks: opts.nticks,
18036 tick0: opts.tick0,
18037 dtick: opts.dtick,
18038 tickvals: opts.tickvals,
18039 ticktext: opts.ticktext,
18040 ticks: opts.ticks,
18041 ticklen: opts.ticklen,
18042 tickwidth: opts.tickwidth,
18043 tickcolor: opts.tickcolor,
18044 showticklabels: opts.showticklabels,
18045 tickfont: opts.tickfont,
18046 tickangle: opts.tickangle,
18047 tickformat: opts.tickformat,
18048 exponentformat: opts.exponentformat,
18049 separatethousands: opts.separatethousands,
18050 showexponent: opts.showexponent,
18051 showtickprefix: opts.showtickprefix,
18052 tickprefix: opts.tickprefix,
18053 showticksuffix: opts.showticksuffix,
18054 ticksuffix: opts.ticksuffix,
18055 title: opts.title,
18056 showline: true,
18057 anchor: 'free',
18058 side: 'right',
18059 position: 1
18060 };
18061
18062 var cbAxisOut = {
18063 type: 'linear',
18064 _id: 'y' + opts._id
18065 };
18066
18067 var axisOptions = {
18068 letter: 'y',
18069 font: fullLayout.font,
18070 noHover: true,
18071 noTickson: true,
18072 calendar: fullLayout.calendar // not really necessary (yet?)
18073 };
18074
18075 function coerce(attr, dflt) {
18076 return Lib.coerce(cbAxisIn, cbAxisOut, axisLayoutAttrs, attr, dflt);
18077 }
18078
18079 handleAxisDefaults(cbAxisIn, cbAxisOut, coerce, axisOptions, fullLayout);
18080 handleAxisPositionDefaults(cbAxisIn, cbAxisOut, coerce, axisOptions);
18081
18082 return cbAxisOut;
18083}
18084
18085module.exports = {
18086 draw: draw
18087};
18088
18089},{"../../constants/alignment":154,"../../lib":178,"../../lib/extend":173,"../../lib/setcursor":197,"../../lib/svg_text_utils":199,"../../plots/cartesian/axes":222,"../../plots/cartesian/axis_defaults":224,"../../plots/cartesian/layout_attributes":236,"../../plots/cartesian/position_defaults":239,"../../plots/plots":256,"../../registry":269,"../color":52,"../colorscale/helpers":63,"../dragelement":71,"../drawing":74,"../titles":147,"./constants":54,"d3":16,"tinycolor2":35}],57:[function(_dereq_,module,exports){
18090/**
18091* Copyright 2012-2020, Plotly, Inc.
18092* All rights reserved.
18093*
18094* This source code is licensed under the MIT license found in the
18095* LICENSE file in the root directory of this source tree.
18096*/
18097
18098
18099'use strict';
18100
18101var Lib = _dereq_('../../lib');
18102
18103
18104module.exports = function hasColorbar(container) {
18105 return Lib.isPlainObject(container.colorbar);
18106};
18107
18108},{"../../lib":178}],58:[function(_dereq_,module,exports){
18109/**
18110* Copyright 2012-2020, Plotly, Inc.
18111* All rights reserved.
18112*
18113* This source code is licensed under the MIT license found in the
18114* LICENSE file in the root directory of this source tree.
18115*/
18116
18117'use strict';
18118
18119module.exports = {
18120 moduleType: 'component',
18121 name: 'colorbar',
18122
18123 attributes: _dereq_('./attributes'),
18124 supplyDefaults: _dereq_('./defaults'),
18125
18126 draw: _dereq_('./draw').draw,
18127 hasColorbar: _dereq_('./has_colorbar')
18128};
18129
18130},{"./attributes":53,"./defaults":55,"./draw":56,"./has_colorbar":57}],59:[function(_dereq_,module,exports){
18131/**
18132* Copyright 2012-2020, Plotly, Inc.
18133* All rights reserved.
18134*
18135* This source code is licensed under the MIT license found in the
18136* LICENSE file in the root directory of this source tree.
18137*/
18138
18139'use strict';
18140
18141var colorbarAttrs = _dereq_('../colorbar/attributes');
18142var counterRegex = _dereq_('../../lib/regex').counter;
18143
18144var palettes = _dereq_('./scales.js').scales;
18145var paletteStr = Object.keys(palettes);
18146
18147function code(s) {
18148 return '`' + s + '`';
18149}
18150
18151/**
18152 * Make colorscale attribute declarations for
18153 *
18154 * - colorscale,
18155 * - (c|z)auto, (c|z)min, (c|z)max,
18156 * - autocolorscale, reversescale,
18157 * - showscale (optionally)
18158 * - color (optionally)
18159 *
18160 * @param {string} context (dflt: '', i.e. from trace root):
18161 * the container this is in ('', *marker*, *marker.line* etc)
18162 *
18163 * @param {object} opts:
18164 * - cLetter {string} (dflt: 'c'):
18165 * leading letter for 'min', 'max and 'auto' attribute (either 'z' or 'c')
18166 *
18167 * - colorAttr {string} (dflt: 'z' if `cLetter: 'z'`, 'color' if `cLetter: 'c'`):
18168 * (for descriptions) sets the name of the color attribute that maps to the colorscale.
18169 *
18170 * N.B. if `colorAttr: 'color'`, we include the `color` declaration here.
18171 *
18172 * - onlyIfNumerical {string} (dflt: false' if `cLetter: 'z'`, true if `cLetter: 'c'`):
18173 * (for descriptions) set to true if colorscale attribute only
18174 *
18175 * - colorscaleDflt {string}:
18176 * overrides the colorscale dflt
18177 *
18178 * - autoColorDflt {boolean} (dflt true):
18179 * normally autocolorscale.dflt is `true`, but pass `false` to override
18180 *
18181 * - noScale {boolean} (dflt: true if `context: 'marker.line'`, false otherwise):
18182 * set to `false` to not include showscale attribute (e.g. for 'marker.line')
18183 *
18184 * - showScaleDflt {boolean} (dflt: true if `cLetter: 'z'`, false otherwise)
18185 *
18186 * - editTypeOverride {boolean} (dflt: ''):
18187 * most of these attributes already require a recalc, but the ones that do not
18188 * have editType *style* or *plot* unless you override (presumably with *calc*)
18189 *
18190 * - anim {boolean) (dflt: undefined): is 'color' animatable?
18191 *
18192 * @return {object}
18193 */
18194module.exports = function colorScaleAttrs(context, opts) {
18195 context = context || '';
18196 opts = opts || {};
18197
18198 var cLetter = opts.cLetter || 'c';
18199 var onlyIfNumerical = ('onlyIfNumerical' in opts) ? opts.onlyIfNumerical : Boolean(context);
18200 var noScale = ('noScale' in opts) ? opts.noScale : context === 'marker.line';
18201 var showScaleDflt = ('showScaleDflt' in opts) ? opts.showScaleDflt : cLetter === 'z';
18202 var colorscaleDflt = typeof opts.colorscaleDflt === 'string' ? palettes[opts.colorscaleDflt] : null;
18203 var editTypeOverride = opts.editTypeOverride || '';
18204 var contextHead = context ? (context + '.') : '';
18205
18206 var colorAttr, colorAttrFull;
18207
18208 if('colorAttr' in opts) {
18209 colorAttr = opts.colorAttr;
18210 colorAttrFull = opts.colorAttr;
18211 } else {
18212 colorAttr = {z: 'z', c: 'color'}[cLetter];
18213 colorAttrFull = 'in ' + code(contextHead + colorAttr);
18214 }
18215
18216 var effectDesc = onlyIfNumerical ?
18217 ' Has an effect only if ' + colorAttrFull + 'is set to a numerical array.' :
18218 '';
18219
18220 var auto = cLetter + 'auto';
18221 var min = cLetter + 'min';
18222 var max = cLetter + 'max';
18223 var mid = cLetter + 'mid';
18224 var autoFull = code(contextHead + auto);
18225 var minFull = code(contextHead + min);
18226 var maxFull = code(contextHead + max);
18227 var minmaxFull = minFull + ' and ' + maxFull;
18228 var autoImpliedEdits = {};
18229 autoImpliedEdits[min] = autoImpliedEdits[max] = undefined;
18230 var minmaxImpliedEdits = {};
18231 minmaxImpliedEdits[auto] = false;
18232
18233 var attrs = {};
18234
18235 if(colorAttr === 'color') {
18236 attrs.color = {
18237 valType: 'color',
18238 arrayOk: true,
18239
18240 editType: editTypeOverride || 'style',
18241
18242 };
18243
18244 if(opts.anim) {
18245 attrs.color.anim = true;
18246 }
18247 }
18248
18249 attrs[auto] = {
18250 valType: 'boolean',
18251
18252 dflt: true,
18253 editType: 'calc',
18254 impliedEdits: autoImpliedEdits,
18255
18256 };
18257
18258 attrs[min] = {
18259 valType: 'number',
18260
18261 dflt: null,
18262 editType: editTypeOverride || 'plot',
18263 impliedEdits: minmaxImpliedEdits,
18264
18265 };
18266
18267 attrs[max] = {
18268 valType: 'number',
18269
18270 dflt: null,
18271 editType: editTypeOverride || 'plot',
18272 impliedEdits: minmaxImpliedEdits,
18273
18274 };
18275
18276 attrs[mid] = {
18277 valType: 'number',
18278
18279 dflt: null,
18280 editType: 'calc',
18281 impliedEdits: autoImpliedEdits,
18282
18283 };
18284
18285 attrs.colorscale = {
18286 valType: 'colorscale',
18287
18288 editType: 'calc',
18289 dflt: colorscaleDflt,
18290 impliedEdits: {autocolorscale: false},
18291
18292 };
18293
18294 attrs.autocolorscale = {
18295 valType: 'boolean',
18296
18297 // gets overrode in 'heatmap' & 'surface' for backwards comp.
18298 dflt: opts.autoColorDflt === false ? false : true,
18299 editType: 'calc',
18300 impliedEdits: {colorscale: undefined},
18301
18302 };
18303
18304 attrs.reversescale = {
18305 valType: 'boolean',
18306
18307 dflt: false,
18308 editType: 'plot',
18309
18310 };
18311
18312 if(!noScale) {
18313 attrs.showscale = {
18314 valType: 'boolean',
18315
18316 dflt: showScaleDflt,
18317 editType: 'calc',
18318
18319 };
18320
18321 attrs.colorbar = colorbarAttrs;
18322 }
18323
18324 if(!opts.noColorAxis) {
18325 attrs.coloraxis = {
18326 valType: 'subplotid',
18327
18328 regex: counterRegex('coloraxis'),
18329 dflt: null,
18330 editType: 'calc',
18331
18332 };
18333 }
18334
18335 return attrs;
18336};
18337
18338},{"../../lib/regex":193,"../colorbar/attributes":53,"./scales.js":67}],60:[function(_dereq_,module,exports){
18339/**
18340* Copyright 2012-2020, Plotly, Inc.
18341* All rights reserved.
18342*
18343* This source code is licensed under the MIT license found in the
18344* LICENSE file in the root directory of this source tree.
18345*/
18346
18347'use strict';
18348
18349var isNumeric = _dereq_('fast-isnumeric');
18350
18351var Lib = _dereq_('../../lib');
18352var extractOpts = _dereq_('./helpers').extractOpts;
18353
18354module.exports = function calc(gd, trace, opts) {
18355 var fullLayout = gd._fullLayout;
18356 var vals = opts.vals;
18357 var containerStr = opts.containerStr;
18358
18359 var container = containerStr ?
18360 Lib.nestedProperty(trace, containerStr).get() :
18361 trace;
18362
18363 var cOpts = extractOpts(container);
18364 var auto = cOpts.auto !== false;
18365 var min = cOpts.min;
18366 var max = cOpts.max;
18367 var mid = cOpts.mid;
18368
18369 var minVal = function() { return Lib.aggNums(Math.min, null, vals); };
18370 var maxVal = function() { return Lib.aggNums(Math.max, null, vals); };
18371
18372 if(min === undefined) {
18373 min = minVal();
18374 } else if(auto) {
18375 if(container._colorAx && isNumeric(min)) {
18376 min = Math.min(min, minVal());
18377 } else {
18378 min = minVal();
18379 }
18380 }
18381
18382 if(max === undefined) {
18383 max = maxVal();
18384 } else if(auto) {
18385 if(container._colorAx && isNumeric(max)) {
18386 max = Math.max(max, maxVal());
18387 } else {
18388 max = maxVal();
18389 }
18390 }
18391
18392 if(auto && mid !== undefined) {
18393 if(max - mid > mid - min) {
18394 min = mid - (max - mid);
18395 } else if(max - mid < mid - min) {
18396 max = mid + (mid - min);
18397 }
18398 }
18399
18400 if(min === max) {
18401 min -= 0.5;
18402 max += 0.5;
18403 }
18404
18405 cOpts._sync('min', min);
18406 cOpts._sync('max', max);
18407
18408 if(cOpts.autocolorscale) {
18409 var scl;
18410 if(min * max < 0) scl = fullLayout.colorscale.diverging;
18411 else if(min >= 0) scl = fullLayout.colorscale.sequential;
18412 else scl = fullLayout.colorscale.sequentialminus;
18413 cOpts._sync('colorscale', scl);
18414 }
18415};
18416
18417},{"../../lib":178,"./helpers":63,"fast-isnumeric":18}],61:[function(_dereq_,module,exports){
18418/**
18419* Copyright 2012-2020, Plotly, Inc.
18420* All rights reserved.
18421*
18422* This source code is licensed under the MIT license found in the
18423* LICENSE file in the root directory of this source tree.
18424*/
18425
18426'use strict';
18427
18428var Lib = _dereq_('../../lib');
18429var hasColorscale = _dereq_('./helpers').hasColorscale;
18430var extractOpts = _dereq_('./helpers').extractOpts;
18431
18432module.exports = function crossTraceDefaults(fullData, fullLayout) {
18433 function replace(cont, k) {
18434 var val = cont['_' + k];
18435 if(val !== undefined) {
18436 cont[k] = val;
18437 }
18438 }
18439
18440 function relinkColorAttrs(outerCont, cbOpt) {
18441 var cont = cbOpt.container ?
18442 Lib.nestedProperty(outerCont, cbOpt.container).get() :
18443 outerCont;
18444
18445 if(cont) {
18446 if(cont.coloraxis) {
18447 // stash ref to color axis
18448 cont._colorAx = fullLayout[cont.coloraxis];
18449 } else {
18450 var cOpts = extractOpts(cont);
18451 var isAuto = cOpts.auto;
18452
18453 if(isAuto || cOpts.min === undefined) {
18454 replace(cont, cbOpt.min);
18455 }
18456 if(isAuto || cOpts.max === undefined) {
18457 replace(cont, cbOpt.max);
18458 }
18459 if(cOpts.autocolorscale) {
18460 replace(cont, 'colorscale');
18461 }
18462 }
18463 }
18464 }
18465
18466 for(var i = 0; i < fullData.length; i++) {
18467 var trace = fullData[i];
18468 var cbOpts = trace._module.colorbar;
18469
18470 if(cbOpts) {
18471 if(Array.isArray(cbOpts)) {
18472 for(var j = 0; j < cbOpts.length; j++) {
18473 relinkColorAttrs(trace, cbOpts[j]);
18474 }
18475 } else {
18476 relinkColorAttrs(trace, cbOpts);
18477 }
18478 }
18479
18480 if(hasColorscale(trace, 'marker.line')) {
18481 relinkColorAttrs(trace, {
18482 container: 'marker.line',
18483 min: 'cmin',
18484 max: 'cmax'
18485 });
18486 }
18487 }
18488
18489 for(var k in fullLayout._colorAxes) {
18490 relinkColorAttrs(fullLayout[k], {min: 'cmin', max: 'cmax'});
18491 }
18492};
18493
18494},{"../../lib":178,"./helpers":63}],62:[function(_dereq_,module,exports){
18495/**
18496* Copyright 2012-2020, Plotly, Inc.
18497* All rights reserved.
18498*
18499* This source code is licensed under the MIT license found in the
18500* LICENSE file in the root directory of this source tree.
18501*/
18502
18503'use strict';
18504
18505var isNumeric = _dereq_('fast-isnumeric');
18506
18507var Lib = _dereq_('../../lib');
18508var hasColorbar = _dereq_('../colorbar/has_colorbar');
18509var colorbarDefaults = _dereq_('../colorbar/defaults');
18510
18511var isValidScale = _dereq_('./scales').isValid;
18512var traceIs = _dereq_('../../registry').traceIs;
18513
18514function npMaybe(parentCont, prefix) {
18515 var containerStr = prefix.slice(0, prefix.length - 1);
18516 return prefix ?
18517 Lib.nestedProperty(parentCont, containerStr).get() || {} :
18518 parentCont;
18519}
18520
18521/**
18522 * Colorscale / colorbar default handler
18523 *
18524 * @param {object} parentContIn : user (input) parent container (e.g. trace or layout coloraxis object)
18525 * @param {object} parentContOut : full parent container
18526 * @param {object} layout : (full) layout object
18527 * @param {fn} coerce : Lib.coerce wrapper
18528 * @param {object} opts :
18529 * - prefix {string} : attr string prefix to colorscale container from parent root
18530 * - cLetter {string} : 'c or 'z' color letter
18531 */
18532module.exports = function colorScaleDefaults(parentContIn, parentContOut, layout, coerce, opts) {
18533 var prefix = opts.prefix;
18534 var cLetter = opts.cLetter;
18535 var inTrace = '_module' in parentContOut;
18536 var containerIn = npMaybe(parentContIn, prefix);
18537 var containerOut = npMaybe(parentContOut, prefix);
18538 var template = npMaybe(parentContOut._template || {}, prefix) || {};
18539
18540 // colorScaleDefaults wrapper called if-ever we need to reset the colorscale
18541 // attributes for containers that were linked to invalid color axes
18542 var thisFn = function() {
18543 delete parentContIn.coloraxis;
18544 delete parentContOut.coloraxis;
18545 return colorScaleDefaults(parentContIn, parentContOut, layout, coerce, opts);
18546 };
18547
18548 if(inTrace) {
18549 var colorAxes = layout._colorAxes || {};
18550 var colorAx = coerce(prefix + 'coloraxis');
18551
18552 if(colorAx) {
18553 var colorbarVisuals = (
18554 traceIs(parentContOut, 'contour') &&
18555 Lib.nestedProperty(parentContOut, 'contours.coloring').get()
18556 ) || 'heatmap';
18557
18558 var stash = colorAxes[colorAx];
18559
18560 if(stash) {
18561 stash[2].push(thisFn);
18562
18563 if(stash[0] !== colorbarVisuals) {
18564 stash[0] = false;
18565 Lib.warn([
18566 'Ignoring coloraxis:', colorAx, 'setting',
18567 'as it is linked to incompatible colorscales.'
18568 ].join(' '));
18569 }
18570 } else {
18571 // stash:
18572 // - colorbar visual 'type'
18573 // - colorbar options to help in Colorbar.draw
18574 // - list of colorScaleDefaults wrapper functions
18575 colorAxes[colorAx] = [colorbarVisuals, parentContOut, [thisFn]];
18576 }
18577 return;
18578 }
18579 }
18580
18581 var minIn = containerIn[cLetter + 'min'];
18582 var maxIn = containerIn[cLetter + 'max'];
18583 var validMinMax = isNumeric(minIn) && isNumeric(maxIn) && (minIn < maxIn);
18584 var auto = coerce(prefix + cLetter + 'auto', !validMinMax);
18585
18586 if(auto) {
18587 coerce(prefix + cLetter + 'mid');
18588 } else {
18589 coerce(prefix + cLetter + 'min');
18590 coerce(prefix + cLetter + 'max');
18591 }
18592
18593 // handles both the trace case (autocolorscale is false by default) and
18594 // the marker and marker.line case (autocolorscale is true by default)
18595 var sclIn = containerIn.colorscale;
18596 var sclTemplate = template.colorscale;
18597 var autoColorscaleDflt;
18598 if(sclIn !== undefined) autoColorscaleDflt = !isValidScale(sclIn);
18599 if(sclTemplate !== undefined) autoColorscaleDflt = !isValidScale(sclTemplate);
18600 coerce(prefix + 'autocolorscale', autoColorscaleDflt);
18601
18602 coerce(prefix + 'colorscale');
18603 coerce(prefix + 'reversescale');
18604
18605 if(prefix !== 'marker.line.') {
18606 // handles both the trace case where the dflt is listed in attributes and
18607 // the marker case where the dflt is determined by hasColorbar
18608 var showScaleDflt;
18609 if(prefix && inTrace) showScaleDflt = hasColorbar(containerIn);
18610
18611 var showScale = coerce(prefix + 'showscale', showScaleDflt);
18612 if(showScale) {
18613 if(prefix && template) containerOut._template = template;
18614 colorbarDefaults(containerIn, containerOut, layout);
18615 }
18616 }
18617};
18618
18619},{"../../lib":178,"../../registry":269,"../colorbar/defaults":55,"../colorbar/has_colorbar":57,"./scales":67,"fast-isnumeric":18}],63:[function(_dereq_,module,exports){
18620/**
18621* Copyright 2012-2020, Plotly, Inc.
18622* All rights reserved.
18623*
18624* This source code is licensed under the MIT license found in the
18625* LICENSE file in the root directory of this source tree.
18626*/
18627
18628'use strict';
18629
18630var d3 = _dereq_('d3');
18631var tinycolor = _dereq_('tinycolor2');
18632var isNumeric = _dereq_('fast-isnumeric');
18633
18634var Lib = _dereq_('../../lib');
18635var Color = _dereq_('../color');
18636
18637var isValidScale = _dereq_('./scales').isValid;
18638
18639function hasColorscale(trace, containerStr, colorKey) {
18640 var container = containerStr ?
18641 Lib.nestedProperty(trace, containerStr).get() || {} :
18642 trace;
18643 var color = container[colorKey || 'color'];
18644
18645 var isArrayWithOneNumber = false;
18646 if(Lib.isArrayOrTypedArray(color)) {
18647 for(var i = 0; i < color.length; i++) {
18648 if(isNumeric(color[i])) {
18649 isArrayWithOneNumber = true;
18650 break;
18651 }
18652 }
18653 }
18654
18655 return (
18656 Lib.isPlainObject(container) && (
18657 isArrayWithOneNumber ||
18658 container.showscale === true ||
18659 (isNumeric(container.cmin) && isNumeric(container.cmax)) ||
18660 isValidScale(container.colorscale) ||
18661 Lib.isPlainObject(container.colorbar)
18662 )
18663 );
18664}
18665
18666var constantAttrs = ['showscale', 'autocolorscale', 'colorscale', 'reversescale', 'colorbar'];
18667var letterAttrs = ['min', 'max', 'mid', 'auto'];
18668
18669/**
18670 * Extract 'c' / 'z', trace / color axis colorscale options
18671 *
18672 * Note that it would be nice to replace all z* with c* equivalents in v2
18673 *
18674 * @param {object} cont : attribute container
18675 * @return {object}:
18676 * - min: cmin or zmin
18677 * - max: cmax or zmax
18678 * - mid: cmid or zmid
18679 * - auto: cauto or zauto
18680 * - *scale: *scale attrs
18681 * - colorbar: colorbar
18682 * - _sync: function syncing attr and underscore dual (useful when calc'ing min/max)
18683 */
18684function extractOpts(cont) {
18685 var colorAx = cont._colorAx;
18686 var cont2 = colorAx ? colorAx : cont;
18687 var out = {};
18688 var cLetter;
18689 var i, k;
18690
18691 for(i = 0; i < constantAttrs.length; i++) {
18692 k = constantAttrs[i];
18693 out[k] = cont2[k];
18694 }
18695
18696 if(colorAx) {
18697 cLetter = 'c';
18698 for(i = 0; i < letterAttrs.length; i++) {
18699 k = letterAttrs[i];
18700 out[k] = cont2['c' + k];
18701 }
18702 } else {
18703 var k2;
18704 for(i = 0; i < letterAttrs.length; i++) {
18705 k = letterAttrs[i];
18706 k2 = 'c' + k;
18707 if(k2 in cont2) {
18708 out[k] = cont2[k2];
18709 continue;
18710 }
18711 k2 = 'z' + k;
18712 if(k2 in cont2) {
18713 out[k] = cont2[k2];
18714 }
18715 }
18716 cLetter = k2.charAt(0);
18717 }
18718
18719 out._sync = function(k, v) {
18720 var k2 = letterAttrs.indexOf(k) !== -1 ? cLetter + k : k;
18721 cont2[k2] = cont2['_' + k2] = v;
18722 };
18723
18724 return out;
18725}
18726
18727/**
18728 * Extract colorscale into numeric domain and color range.
18729 *
18730 * @param {object} cont colorscale container (e.g. trace, marker)
18731 * - colorscale {array of arrays}
18732 * - cmin/zmin {number}
18733 * - cmax/zmax {number}
18734 * - reversescale {boolean}
18735 *
18736 * @return {object}
18737 * - domain {array}
18738 * - range {array}
18739 */
18740function extractScale(cont) {
18741 var cOpts = extractOpts(cont);
18742 var cmin = cOpts.min;
18743 var cmax = cOpts.max;
18744
18745 var scl = cOpts.reversescale ?
18746 flipScale(cOpts.colorscale) :
18747 cOpts.colorscale;
18748
18749 var N = scl.length;
18750 var domain = new Array(N);
18751 var range = new Array(N);
18752
18753 for(var i = 0; i < N; i++) {
18754 var si = scl[i];
18755 domain[i] = cmin + si[0] * (cmax - cmin);
18756 range[i] = si[1];
18757 }
18758
18759 return {domain: domain, range: range};
18760}
18761
18762function flipScale(scl) {
18763 var N = scl.length;
18764 var sclNew = new Array(N);
18765
18766 for(var i = N - 1, j = 0; i >= 0; i--, j++) {
18767 var si = scl[i];
18768 sclNew[j] = [1 - si[0], si[1]];
18769 }
18770 return sclNew;
18771}
18772
18773/**
18774 * General colorscale function generator.
18775 *
18776 * @param {object} specs output of Colorscale.extractScale or precomputed domain, range.
18777 * - domain {array}
18778 * - range {array}
18779 *
18780 * @param {object} opts
18781 * - noNumericCheck {boolean} if true, scale func bypasses numeric checks
18782 * - returnArray {boolean} if true, scale func return 4-item array instead of color strings
18783 *
18784 * @return {function}
18785 */
18786function makeColorScaleFunc(specs, opts) {
18787 opts = opts || {};
18788
18789 var domain = specs.domain;
18790 var range = specs.range;
18791 var N = range.length;
18792 var _range = new Array(N);
18793
18794 for(var i = 0; i < N; i++) {
18795 var rgba = tinycolor(range[i]).toRgb();
18796 _range[i] = [rgba.r, rgba.g, rgba.b, rgba.a];
18797 }
18798
18799 var _sclFunc = d3.scale.linear()
18800 .domain(domain)
18801 .range(_range)
18802 .clamp(true);
18803
18804 var noNumericCheck = opts.noNumericCheck;
18805 var returnArray = opts.returnArray;
18806 var sclFunc;
18807
18808 if(noNumericCheck && returnArray) {
18809 sclFunc = _sclFunc;
18810 } else if(noNumericCheck) {
18811 sclFunc = function(v) {
18812 return colorArray2rbga(_sclFunc(v));
18813 };
18814 } else if(returnArray) {
18815 sclFunc = function(v) {
18816 if(isNumeric(v)) return _sclFunc(v);
18817 else if(tinycolor(v).isValid()) return v;
18818 else return Color.defaultLine;
18819 };
18820 } else {
18821 sclFunc = function(v) {
18822 if(isNumeric(v)) return colorArray2rbga(_sclFunc(v));
18823 else if(tinycolor(v).isValid()) return v;
18824 else return Color.defaultLine;
18825 };
18826 }
18827
18828 // colorbar draw looks into the d3 scale closure for domain and range
18829 sclFunc.domain = _sclFunc.domain;
18830 sclFunc.range = function() { return range; };
18831
18832 return sclFunc;
18833}
18834
18835function makeColorScaleFuncFromTrace(trace, opts) {
18836 return makeColorScaleFunc(extractScale(trace), opts);
18837}
18838
18839function colorArray2rbga(colorArray) {
18840 var colorObj = {
18841 r: colorArray[0],
18842 g: colorArray[1],
18843 b: colorArray[2],
18844 a: colorArray[3]
18845 };
18846
18847 return tinycolor(colorObj).toRgbString();
18848}
18849
18850module.exports = {
18851 hasColorscale: hasColorscale,
18852 extractOpts: extractOpts,
18853 extractScale: extractScale,
18854 flipScale: flipScale,
18855 makeColorScaleFunc: makeColorScaleFunc,
18856 makeColorScaleFuncFromTrace: makeColorScaleFuncFromTrace
18857};
18858
18859},{"../../lib":178,"../color":52,"./scales":67,"d3":16,"fast-isnumeric":18,"tinycolor2":35}],64:[function(_dereq_,module,exports){
18860/**
18861* Copyright 2012-2020, Plotly, Inc.
18862* All rights reserved.
18863*
18864* This source code is licensed under the MIT license found in the
18865* LICENSE file in the root directory of this source tree.
18866*/
18867
18868'use strict';
18869
18870var scales = _dereq_('./scales');
18871var helpers = _dereq_('./helpers');
18872
18873module.exports = {
18874 moduleType: 'component',
18875 name: 'colorscale',
18876
18877 attributes: _dereq_('./attributes'),
18878 layoutAttributes: _dereq_('./layout_attributes'),
18879
18880 supplyLayoutDefaults: _dereq_('./layout_defaults'),
18881 handleDefaults: _dereq_('./defaults'),
18882 crossTraceDefaults: _dereq_('./cross_trace_defaults'),
18883
18884 calc: _dereq_('./calc'),
18885
18886 // ./scales.js is required in lib/coerce.js ;
18887 // it needs to be a seperate module to avoid circular a dependency
18888 scales: scales.scales,
18889 defaultScale: scales.defaultScale,
18890 getScale: scales.get,
18891 isValidScale: scales.isValid,
18892
18893 hasColorscale: helpers.hasColorscale,
18894 extractOpts: helpers.extractOpts,
18895 extractScale: helpers.extractScale,
18896 flipScale: helpers.flipScale,
18897 makeColorScaleFunc: helpers.makeColorScaleFunc,
18898 makeColorScaleFuncFromTrace: helpers.makeColorScaleFuncFromTrace
18899};
18900
18901},{"./attributes":59,"./calc":60,"./cross_trace_defaults":61,"./defaults":62,"./helpers":63,"./layout_attributes":65,"./layout_defaults":66,"./scales":67}],65:[function(_dereq_,module,exports){
18902/**
18903* Copyright 2012-2020, Plotly, Inc.
18904* All rights reserved.
18905*
18906* This source code is licensed under the MIT license found in the
18907* LICENSE file in the root directory of this source tree.
18908*/
18909
18910'use strict';
18911
18912var extendFlat = _dereq_('../../lib/extend').extendFlat;
18913
18914var colorScaleAttrs = _dereq_('./attributes');
18915var scales = _dereq_('./scales').scales;
18916
18917var msg = 'Note that `autocolorscale` must be true for this attribute to work.';
18918
18919module.exports = {
18920 editType: 'calc',
18921
18922 colorscale: {
18923 editType: 'calc',
18924
18925 sequential: {
18926 valType: 'colorscale',
18927 dflt: scales.Reds,
18928
18929 editType: 'calc',
18930
18931 },
18932 sequentialminus: {
18933 valType: 'colorscale',
18934 dflt: scales.Blues,
18935
18936 editType: 'calc',
18937
18938 },
18939 diverging: {
18940 valType: 'colorscale',
18941 dflt: scales.RdBu,
18942
18943 editType: 'calc',
18944
18945 }
18946 },
18947
18948 coloraxis: extendFlat({
18949 // not really a 'subplot' attribute container,
18950 // but this is the flag we use to denote attributes that
18951 // support yaxis, yaxis2, yaxis3, ... counters
18952 _isSubplotObj: true,
18953 editType: 'calc',
18954
18955 }, colorScaleAttrs('', {
18956 colorAttr: 'corresponding trace color array(s)',
18957 noColorAxis: true,
18958 showScaleDflt: true
18959 }))
18960};
18961
18962},{"../../lib/extend":173,"./attributes":59,"./scales":67}],66:[function(_dereq_,module,exports){
18963/**
18964* Copyright 2012-2020, Plotly, Inc.
18965* All rights reserved.
18966*
18967* This source code is licensed under the MIT license found in the
18968* LICENSE file in the root directory of this source tree.
18969*/
18970
18971'use strict';
18972
18973var Lib = _dereq_('../../lib');
18974var Template = _dereq_('../../plot_api/plot_template');
18975
18976var colorScaleAttrs = _dereq_('./layout_attributes');
18977var colorScaleDefaults = _dereq_('./defaults');
18978
18979module.exports = function supplyLayoutDefaults(layoutIn, layoutOut) {
18980 function coerce(attr, dflt) {
18981 return Lib.coerce(layoutIn, layoutOut, colorScaleAttrs, attr, dflt);
18982 }
18983
18984 coerce('colorscale.sequential');
18985 coerce('colorscale.sequentialminus');
18986 coerce('colorscale.diverging');
18987
18988 var colorAxes = layoutOut._colorAxes;
18989 var colorAxIn, colorAxOut;
18990
18991 function coerceAx(attr, dflt) {
18992 return Lib.coerce(colorAxIn, colorAxOut, colorScaleAttrs.coloraxis, attr, dflt);
18993 }
18994
18995 for(var k in colorAxes) {
18996 var stash = colorAxes[k];
18997
18998 if(stash[0]) {
18999 colorAxIn = layoutIn[k] || {};
19000 colorAxOut = Template.newContainer(layoutOut, k, 'coloraxis');
19001 colorAxOut._name = k;
19002 colorScaleDefaults(colorAxIn, colorAxOut, layoutOut, coerceAx, {prefix: '', cLetter: 'c'});
19003 } else {
19004 // re-coerce colorscale attributes w/o coloraxis
19005 for(var i = 0; i < stash[2].length; i++) {
19006 stash[2][i]();
19007 }
19008 delete layoutOut._colorAxes[k];
19009 }
19010 }
19011};
19012
19013},{"../../lib":178,"../../plot_api/plot_template":212,"./defaults":62,"./layout_attributes":65}],67:[function(_dereq_,module,exports){
19014/**
19015* Copyright 2012-2020, Plotly, Inc.
19016* All rights reserved.
19017*
19018* This source code is licensed under the MIT license found in the
19019* LICENSE file in the root directory of this source tree.
19020*/
19021
19022'use strict';
19023
19024var tinycolor = _dereq_('tinycolor2');
19025
19026var scales = {
19027 'Greys': [
19028 [0, 'rgb(0,0,0)'], [1, 'rgb(255,255,255)']
19029 ],
19030
19031 'YlGnBu': [
19032 [0, 'rgb(8,29,88)'], [0.125, 'rgb(37,52,148)'],
19033 [0.25, 'rgb(34,94,168)'], [0.375, 'rgb(29,145,192)'],
19034 [0.5, 'rgb(65,182,196)'], [0.625, 'rgb(127,205,187)'],
19035 [0.75, 'rgb(199,233,180)'], [0.875, 'rgb(237,248,217)'],
19036 [1, 'rgb(255,255,217)']
19037 ],
19038
19039 'Greens': [
19040 [0, 'rgb(0,68,27)'], [0.125, 'rgb(0,109,44)'],
19041 [0.25, 'rgb(35,139,69)'], [0.375, 'rgb(65,171,93)'],
19042 [0.5, 'rgb(116,196,118)'], [0.625, 'rgb(161,217,155)'],
19043 [0.75, 'rgb(199,233,192)'], [0.875, 'rgb(229,245,224)'],
19044 [1, 'rgb(247,252,245)']
19045 ],
19046
19047 'YlOrRd': [
19048 [0, 'rgb(128,0,38)'], [0.125, 'rgb(189,0,38)'],
19049 [0.25, 'rgb(227,26,28)'], [0.375, 'rgb(252,78,42)'],
19050 [0.5, 'rgb(253,141,60)'], [0.625, 'rgb(254,178,76)'],
19051 [0.75, 'rgb(254,217,118)'], [0.875, 'rgb(255,237,160)'],
19052 [1, 'rgb(255,255,204)']
19053 ],
19054
19055 'Bluered': [
19056 [0, 'rgb(0,0,255)'], [1, 'rgb(255,0,0)']
19057 ],
19058
19059 // modified RdBu based on
19060 // http://www.kennethmoreland.com/color-maps/
19061 'RdBu': [
19062 [0, 'rgb(5,10,172)'], [0.35, 'rgb(106,137,247)'],
19063 [0.5, 'rgb(190,190,190)'], [0.6, 'rgb(220,170,132)'],
19064 [0.7, 'rgb(230,145,90)'], [1, 'rgb(178,10,28)']
19065 ],
19066
19067 // Scale for non-negative numeric values
19068 'Reds': [
19069 [0, 'rgb(220,220,220)'], [0.2, 'rgb(245,195,157)'],
19070 [0.4, 'rgb(245,160,105)'], [1, 'rgb(178,10,28)']
19071 ],
19072
19073 // Scale for non-positive numeric values
19074 'Blues': [
19075 [0, 'rgb(5,10,172)'], [0.35, 'rgb(40,60,190)'],
19076 [0.5, 'rgb(70,100,245)'], [0.6, 'rgb(90,120,245)'],
19077 [0.7, 'rgb(106,137,247)'], [1, 'rgb(220,220,220)']
19078 ],
19079
19080 'Picnic': [
19081 [0, 'rgb(0,0,255)'], [0.1, 'rgb(51,153,255)'],
19082 [0.2, 'rgb(102,204,255)'], [0.3, 'rgb(153,204,255)'],
19083 [0.4, 'rgb(204,204,255)'], [0.5, 'rgb(255,255,255)'],
19084 [0.6, 'rgb(255,204,255)'], [0.7, 'rgb(255,153,255)'],
19085 [0.8, 'rgb(255,102,204)'], [0.9, 'rgb(255,102,102)'],
19086 [1, 'rgb(255,0,0)']
19087 ],
19088
19089 'Rainbow': [
19090 [0, 'rgb(150,0,90)'], [0.125, 'rgb(0,0,200)'],
19091 [0.25, 'rgb(0,25,255)'], [0.375, 'rgb(0,152,255)'],
19092 [0.5, 'rgb(44,255,150)'], [0.625, 'rgb(151,255,0)'],
19093 [0.75, 'rgb(255,234,0)'], [0.875, 'rgb(255,111,0)'],
19094 [1, 'rgb(255,0,0)']
19095 ],
19096
19097 'Portland': [
19098 [0, 'rgb(12,51,131)'], [0.25, 'rgb(10,136,186)'],
19099 [0.5, 'rgb(242,211,56)'], [0.75, 'rgb(242,143,56)'],
19100 [1, 'rgb(217,30,30)']
19101 ],
19102
19103 'Jet': [
19104 [0, 'rgb(0,0,131)'], [0.125, 'rgb(0,60,170)'],
19105 [0.375, 'rgb(5,255,255)'], [0.625, 'rgb(255,255,0)'],
19106 [0.875, 'rgb(250,0,0)'], [1, 'rgb(128,0,0)']
19107 ],
19108
19109 'Hot': [
19110 [0, 'rgb(0,0,0)'], [0.3, 'rgb(230,0,0)'],
19111 [0.6, 'rgb(255,210,0)'], [1, 'rgb(255,255,255)']
19112 ],
19113
19114 'Blackbody': [
19115 [0, 'rgb(0,0,0)'], [0.2, 'rgb(230,0,0)'],
19116 [0.4, 'rgb(230,210,0)'], [0.7, 'rgb(255,255,255)'],
19117 [1, 'rgb(160,200,255)']
19118 ],
19119
19120 'Earth': [
19121 [0, 'rgb(0,0,130)'], [0.1, 'rgb(0,180,180)'],
19122 [0.2, 'rgb(40,210,40)'], [0.4, 'rgb(230,230,50)'],
19123 [0.6, 'rgb(120,70,20)'], [1, 'rgb(255,255,255)']
19124 ],
19125
19126 'Electric': [
19127 [0, 'rgb(0,0,0)'], [0.15, 'rgb(30,0,100)'],
19128 [0.4, 'rgb(120,0,100)'], [0.6, 'rgb(160,90,0)'],
19129 [0.8, 'rgb(230,200,0)'], [1, 'rgb(255,250,220)']
19130 ],
19131
19132 'Viridis': [
19133 [0, '#440154'], [0.06274509803921569, '#48186a'],
19134 [0.12549019607843137, '#472d7b'], [0.18823529411764706, '#424086'],
19135 [0.25098039215686274, '#3b528b'], [0.3137254901960784, '#33638d'],
19136 [0.3764705882352941, '#2c728e'], [0.4392156862745098, '#26828e'],
19137 [0.5019607843137255, '#21918c'], [0.5647058823529412, '#1fa088'],
19138 [0.6274509803921569, '#28ae80'], [0.6901960784313725, '#3fbc73'],
19139 [0.7529411764705882, '#5ec962'], [0.8156862745098039, '#84d44b'],
19140 [0.8784313725490196, '#addc30'], [0.9411764705882353, '#d8e219'],
19141 [1, '#fde725']
19142 ],
19143
19144 'Cividis': [
19145 [0.000000, 'rgb(0,32,76)'], [0.058824, 'rgb(0,42,102)'],
19146 [0.117647, 'rgb(0,52,110)'], [0.176471, 'rgb(39,63,108)'],
19147 [0.235294, 'rgb(60,74,107)'], [0.294118, 'rgb(76,85,107)'],
19148 [0.352941, 'rgb(91,95,109)'], [0.411765, 'rgb(104,106,112)'],
19149 [0.470588, 'rgb(117,117,117)'], [0.529412, 'rgb(131,129,120)'],
19150 [0.588235, 'rgb(146,140,120)'], [0.647059, 'rgb(161,152,118)'],
19151 [0.705882, 'rgb(176,165,114)'], [0.764706, 'rgb(192,177,109)'],
19152 [0.823529, 'rgb(209,191,102)'], [0.882353, 'rgb(225,204,92)'],
19153 [0.941176, 'rgb(243,219,79)'], [1.000000, 'rgb(255,233,69)']
19154 ]
19155};
19156
19157var defaultScale = scales.RdBu;
19158
19159function getScale(scl, dflt) {
19160 if(!dflt) dflt = defaultScale;
19161 if(!scl) return dflt;
19162
19163 function parseScale() {
19164 try {
19165 scl = scales[scl] || JSON.parse(scl);
19166 } catch(e) {
19167 scl = dflt;
19168 }
19169 }
19170
19171 if(typeof scl === 'string') {
19172 parseScale();
19173 // occasionally scl is double-JSON encoded...
19174 if(typeof scl === 'string') parseScale();
19175 }
19176
19177 if(!isValidScaleArray(scl)) return dflt;
19178 return scl;
19179}
19180
19181
19182function isValidScaleArray(scl) {
19183 var highestVal = 0;
19184
19185 if(!Array.isArray(scl) || scl.length < 2) return false;
19186
19187 if(!scl[0] || !scl[scl.length - 1]) return false;
19188
19189 if(+scl[0][0] !== 0 || +scl[scl.length - 1][0] !== 1) return false;
19190
19191 for(var i = 0; i < scl.length; i++) {
19192 var si = scl[i];
19193
19194 if(si.length !== 2 || +si[0] < highestVal || !tinycolor(si[1]).isValid()) {
19195 return false;
19196 }
19197
19198 highestVal = +si[0];
19199 }
19200
19201 return true;
19202}
19203
19204function isValidScale(scl) {
19205 if(scales[scl] !== undefined) return true;
19206 else return isValidScaleArray(scl);
19207}
19208
19209module.exports = {
19210 scales: scales,
19211 defaultScale: defaultScale,
19212
19213 get: getScale,
19214 isValid: isValidScale
19215};
19216
19217},{"tinycolor2":35}],68:[function(_dereq_,module,exports){
19218/**
19219* Copyright 2012-2020, Plotly, Inc.
19220* All rights reserved.
19221*
19222* This source code is licensed under the MIT license found in the
19223* LICENSE file in the root directory of this source tree.
19224*/
19225
19226
19227'use strict';
19228
19229
19230// for automatic alignment on dragging, <1/3 means left align,
19231// >2/3 means right, and between is center. Pick the right fraction
19232// based on where you are, and return the fraction corresponding to
19233// that position on the object
19234module.exports = function align(v, dv, v0, v1, anchor) {
19235 var vmin = (v - v0) / (v1 - v0);
19236 var vmax = vmin + dv / (v1 - v0);
19237 var vc = (vmin + vmax) / 2;
19238
19239 // explicitly specified anchor
19240 if(anchor === 'left' || anchor === 'bottom') return vmin;
19241 if(anchor === 'center' || anchor === 'middle') return vc;
19242 if(anchor === 'right' || anchor === 'top') return vmax;
19243
19244 // automatic based on position
19245 if(vmin < (2 / 3) - vc) return vmin;
19246 if(vmax > (4 / 3) - vc) return vmax;
19247 return vc;
19248};
19249
19250},{}],69:[function(_dereq_,module,exports){
19251/**
19252* Copyright 2012-2020, Plotly, Inc.
19253* All rights reserved.
19254*
19255* This source code is licensed under the MIT license found in the
19256* LICENSE file in the root directory of this source tree.
19257*/
19258
19259
19260'use strict';
19261
19262var Lib = _dereq_('../../lib');
19263
19264
19265// set cursors pointing toward the closest corner/side,
19266// to indicate alignment
19267// x and y are 0-1, fractions of the plot area
19268var cursorset = [
19269 ['sw-resize', 's-resize', 'se-resize'],
19270 ['w-resize', 'move', 'e-resize'],
19271 ['nw-resize', 'n-resize', 'ne-resize']
19272];
19273
19274module.exports = function getCursor(x, y, xanchor, yanchor) {
19275 if(xanchor === 'left') x = 0;
19276 else if(xanchor === 'center') x = 1;
19277 else if(xanchor === 'right') x = 2;
19278 else x = Lib.constrain(Math.floor(x * 3), 0, 2);
19279
19280 if(yanchor === 'bottom') y = 0;
19281 else if(yanchor === 'middle') y = 1;
19282 else if(yanchor === 'top') y = 2;
19283 else y = Lib.constrain(Math.floor(y * 3), 0, 2);
19284
19285 return cursorset[y][x];
19286};
19287
19288},{"../../lib":178}],70:[function(_dereq_,module,exports){
19289/**
19290* Copyright 2012-2020, Plotly, Inc.
19291* All rights reserved.
19292*
19293* This source code is licensed under the MIT license found in the
19294* LICENSE file in the root directory of this source tree.
19295*/
19296
19297'use strict';
19298
19299exports.selectMode = function(dragmode) {
19300 return (
19301 dragmode === 'lasso' ||
19302 dragmode === 'select'
19303 );
19304};
19305
19306exports.drawMode = function(dragmode) {
19307 return (
19308 dragmode === 'drawclosedpath' ||
19309 dragmode === 'drawopenpath' ||
19310 dragmode === 'drawline' ||
19311 dragmode === 'drawrect' ||
19312 dragmode === 'drawcircle'
19313 );
19314};
19315
19316exports.openMode = function(dragmode) {
19317 return (
19318 dragmode === 'drawline' ||
19319 dragmode === 'drawopenpath'
19320 );
19321};
19322
19323exports.rectMode = function(dragmode) {
19324 return (
19325 dragmode === 'select' ||
19326 dragmode === 'drawline' ||
19327 dragmode === 'drawrect' ||
19328 dragmode === 'drawcircle'
19329 );
19330};
19331
19332exports.freeMode = function(dragmode) {
19333 return (
19334 dragmode === 'lasso' ||
19335 dragmode === 'drawclosedpath' ||
19336 dragmode === 'drawopenpath'
19337 );
19338};
19339
19340exports.selectingOrDrawing = function(dragmode) {
19341 return (
19342 exports.freeMode(dragmode) ||
19343 exports.rectMode(dragmode)
19344 );
19345};
19346
19347},{}],71:[function(_dereq_,module,exports){
19348/**
19349* Copyright 2012-2020, Plotly, Inc.
19350* All rights reserved.
19351*
19352* This source code is licensed under the MIT license found in the
19353* LICENSE file in the root directory of this source tree.
19354*/
19355
19356'use strict';
19357
19358var mouseOffset = _dereq_('mouse-event-offset');
19359var hasHover = _dereq_('has-hover');
19360var supportsPassive = _dereq_('has-passive-events');
19361
19362var removeElement = _dereq_('../../lib').removeElement;
19363var constants = _dereq_('../../plots/cartesian/constants');
19364
19365var dragElement = module.exports = {};
19366
19367dragElement.align = _dereq_('./align');
19368dragElement.getCursor = _dereq_('./cursor');
19369
19370var unhover = _dereq_('./unhover');
19371dragElement.unhover = unhover.wrapped;
19372dragElement.unhoverRaw = unhover.raw;
19373
19374/**
19375 * Abstracts click & drag interactions
19376 *
19377 * During the interaction, a "coverSlip" element - a transparent
19378 * div covering the whole page - is created, which has two key effects:
19379 * - Lets you drag beyond the boundaries of the plot itself without
19380 * dropping (but if you drag all the way out of the browser window the
19381 * interaction will end)
19382 * - Freezes the cursor: whatever mouse cursor the drag element had when the
19383 * interaction started gets copied to the coverSlip for use until mouseup
19384 *
19385 * If the user executes a drag bigger than MINDRAG, callbacks will fire as:
19386 * prepFn, moveFn (1 or more times), doneFn
19387 * If the user does not drag enough, prepFn and clickFn will fire.
19388 *
19389 * Note: If you cancel contextmenu, clickFn will fire even with a right click
19390 * (unlike native events) so you'll get a `plotly_click` event. Cancel context eg:
19391 * gd.addEventListener('contextmenu', function(e) { e.preventDefault(); });
19392 * TODO: we should probably turn this into a `config` parameter, so we can fix it
19393 * such that if you *don't* cancel contextmenu, we can prevent partial drags, which
19394 * put you in a weird state.
19395 *
19396 * If the user clicks multiple times quickly, clickFn will fire each time
19397 * but numClicks will increase to help you recognize doubleclicks.
19398 *
19399 * @param {object} options with keys:
19400 * element (required) the DOM element to drag
19401 * prepFn (optional) function(event, startX, startY)
19402 * executed on mousedown
19403 * startX and startY are the clientX and clientY pixel position
19404 * of the mousedown event
19405 * moveFn (optional) function(dx, dy)
19406 * executed on move, ONLY after we've exceeded MINDRAG
19407 * (we keep executing moveFn if you move back to where you started)
19408 * dx and dy are the net pixel offset of the drag,
19409 * dragged is true/false, has the mouse moved enough to
19410 * constitute a drag
19411 * doneFn (optional) function(e)
19412 * executed on mouseup, ONLY if we exceeded MINDRAG (so you can be
19413 * sure that moveFn has been called at least once)
19414 * numClicks is how many clicks we've registered within
19415 * a doubleclick time
19416 * e is the original mouseup event
19417 * clickFn (optional) function(numClicks, e)
19418 * executed on mouseup if we have NOT exceeded MINDRAG (ie moveFn
19419 * has not been called at all)
19420 * numClicks is how many clicks we've registered within
19421 * a doubleclick time
19422 * e is the original mousedown event
19423 * clampFn (optional, function(dx, dy) return [dx2, dy2])
19424 * Provide custom clamping function for small displacements.
19425 * By default, clamping is done using `minDrag` to x and y displacements
19426 * independently.
19427 */
19428dragElement.init = function init(options) {
19429 var gd = options.gd;
19430 var numClicks = 1;
19431 var doubleClickDelay = gd._context.doubleClickDelay;
19432 var element = options.element;
19433
19434 var startX,
19435 startY,
19436 newMouseDownTime,
19437 cursor,
19438 dragCover,
19439 initialEvent,
19440 initialTarget,
19441 rightClick;
19442
19443 if(!gd._mouseDownTime) gd._mouseDownTime = 0;
19444
19445 element.style.pointerEvents = 'all';
19446
19447 element.onmousedown = onStart;
19448
19449 if(!supportsPassive) {
19450 element.ontouchstart = onStart;
19451 } else {
19452 if(element._ontouchstart) {
19453 element.removeEventListener('touchstart', element._ontouchstart);
19454 }
19455 element._ontouchstart = onStart;
19456 element.addEventListener('touchstart', onStart, {passive: false});
19457 }
19458
19459 function _clampFn(dx, dy, minDrag) {
19460 if(Math.abs(dx) < minDrag) dx = 0;
19461 if(Math.abs(dy) < minDrag) dy = 0;
19462 return [dx, dy];
19463 }
19464
19465 var clampFn = options.clampFn || _clampFn;
19466
19467 function onStart(e) {
19468 // make dragging and dragged into properties of gd
19469 // so that others can look at and modify them
19470 gd._dragged = false;
19471 gd._dragging = true;
19472 var offset = pointerOffset(e);
19473 startX = offset[0];
19474 startY = offset[1];
19475 initialTarget = e.target;
19476 initialEvent = e;
19477 rightClick = e.buttons === 2 || e.ctrlKey;
19478
19479 // fix Fx.hover for touch events
19480 if(typeof e.clientX === 'undefined' && typeof e.clientY === 'undefined') {
19481 e.clientX = startX;
19482 e.clientY = startY;
19483 }
19484
19485 newMouseDownTime = (new Date()).getTime();
19486 if(newMouseDownTime - gd._mouseDownTime < doubleClickDelay) {
19487 // in a click train
19488 numClicks += 1;
19489 } else {
19490 // new click train
19491 numClicks = 1;
19492 gd._mouseDownTime = newMouseDownTime;
19493 }
19494
19495 if(options.prepFn) options.prepFn(e, startX, startY);
19496
19497 if(hasHover && !rightClick) {
19498 dragCover = coverSlip();
19499 dragCover.style.cursor = window.getComputedStyle(element).cursor;
19500 } else if(!hasHover) {
19501 // document acts as a dragcover for mobile, bc we can't create dragcover dynamically
19502 dragCover = document;
19503 cursor = window.getComputedStyle(document.documentElement).cursor;
19504 document.documentElement.style.cursor = window.getComputedStyle(element).cursor;
19505 }
19506
19507 document.addEventListener('mouseup', onDone);
19508 document.addEventListener('touchend', onDone);
19509
19510 if(options.dragmode !== false) {
19511 e.preventDefault();
19512 document.addEventListener('mousemove', onMove);
19513 document.addEventListener('touchmove', onMove, {passive: false});
19514 }
19515
19516 return;
19517 }
19518
19519 function onMove(e) {
19520 e.preventDefault();
19521
19522 var offset = pointerOffset(e);
19523 var minDrag = options.minDrag || constants.MINDRAG;
19524 var dxdy = clampFn(offset[0] - startX, offset[1] - startY, minDrag);
19525 var dx = dxdy[0];
19526 var dy = dxdy[1];
19527
19528 if(dx || dy) {
19529 gd._dragged = true;
19530 dragElement.unhover(gd);
19531 }
19532
19533 if(gd._dragged && options.moveFn && !rightClick) {
19534 gd._dragdata = {
19535 element: element,
19536 dx: dx,
19537 dy: dy
19538 };
19539 options.moveFn(dx, dy);
19540 }
19541
19542 return;
19543 }
19544
19545 function onDone(e) {
19546 delete gd._dragdata;
19547
19548 if(options.dragmode !== false) {
19549 e.preventDefault();
19550 document.removeEventListener('mousemove', onMove);
19551 document.removeEventListener('touchmove', onMove);
19552 }
19553
19554 document.removeEventListener('mouseup', onDone);
19555 document.removeEventListener('touchend', onDone);
19556
19557 if(hasHover) {
19558 removeElement(dragCover);
19559 } else if(cursor) {
19560 dragCover.documentElement.style.cursor = cursor;
19561 cursor = null;
19562 }
19563
19564 if(!gd._dragging) {
19565 gd._dragged = false;
19566 return;
19567 }
19568 gd._dragging = false;
19569
19570 // don't count as a dblClick unless the mouseUp is also within
19571 // the dblclick delay
19572 if((new Date()).getTime() - gd._mouseDownTime > doubleClickDelay) {
19573 numClicks = Math.max(numClicks - 1, 1);
19574 }
19575
19576 if(gd._dragged) {
19577 if(options.doneFn) options.doneFn();
19578 } else {
19579 if(options.clickFn) options.clickFn(numClicks, initialEvent);
19580
19581 // If we haven't dragged, this should be a click. But because of the
19582 // coverSlip changing the element, the natural system might not generate one,
19583 // so we need to make our own. But right clicks don't normally generate
19584 // click events, only contextmenu events, which happen on mousedown.
19585 if(!rightClick) {
19586 var e2;
19587
19588 try {
19589 e2 = new MouseEvent('click', e);
19590 } catch(err) {
19591 var offset = pointerOffset(e);
19592 e2 = document.createEvent('MouseEvents');
19593 e2.initMouseEvent('click',
19594 e.bubbles, e.cancelable,
19595 e.view, e.detail,
19596 e.screenX, e.screenY,
19597 offset[0], offset[1],
19598 e.ctrlKey, e.altKey, e.shiftKey, e.metaKey,
19599 e.button, e.relatedTarget);
19600 }
19601
19602 initialTarget.dispatchEvent(e2);
19603 }
19604 }
19605
19606 gd._dragging = false;
19607 gd._dragged = false;
19608 return;
19609 }
19610};
19611
19612function coverSlip() {
19613 var cover = document.createElement('div');
19614
19615 cover.className = 'dragcover';
19616 var cStyle = cover.style;
19617 cStyle.position = 'fixed';
19618 cStyle.left = 0;
19619 cStyle.right = 0;
19620 cStyle.top = 0;
19621 cStyle.bottom = 0;
19622 cStyle.zIndex = 999999999;
19623 cStyle.background = 'none';
19624
19625 document.body.appendChild(cover);
19626
19627 return cover;
19628}
19629
19630dragElement.coverSlip = coverSlip;
19631
19632function pointerOffset(e) {
19633 return mouseOffset(
19634 e.changedTouches ? e.changedTouches[0] : e,
19635 document.body
19636 );
19637}
19638
19639},{"../../lib":178,"../../plots/cartesian/constants":228,"./align":68,"./cursor":69,"./unhover":72,"has-hover":20,"has-passive-events":21,"mouse-event-offset":24}],72:[function(_dereq_,module,exports){
19640/**
19641* Copyright 2012-2020, Plotly, Inc.
19642* All rights reserved.
19643*
19644* This source code is licensed under the MIT license found in the
19645* LICENSE file in the root directory of this source tree.
19646*/
19647
19648'use strict';
19649
19650var Events = _dereq_('../../lib/events');
19651var throttle = _dereq_('../../lib/throttle');
19652var getGraphDiv = _dereq_('../../lib/dom').getGraphDiv;
19653
19654var hoverConstants = _dereq_('../fx/constants');
19655
19656var unhover = module.exports = {};
19657
19658unhover.wrapped = function(gd, evt, subplot) {
19659 gd = getGraphDiv(gd);
19660
19661 // Important, clear any queued hovers
19662 if(gd._fullLayout) {
19663 throttle.clear(gd._fullLayout._uid + hoverConstants.HOVERID);
19664 }
19665
19666 unhover.raw(gd, evt, subplot);
19667};
19668
19669
19670// remove hover effects on mouse out, and emit unhover event
19671unhover.raw = function raw(gd, evt) {
19672 var fullLayout = gd._fullLayout;
19673 var oldhoverdata = gd._hoverdata;
19674
19675 if(!evt) evt = {};
19676 if(evt.target &&
19677 Events.triggerHandler(gd, 'plotly_beforehover', evt) === false) {
19678 return;
19679 }
19680
19681 fullLayout._hoverlayer.selectAll('g').remove();
19682 fullLayout._hoverlayer.selectAll('line').remove();
19683 fullLayout._hoverlayer.selectAll('circle').remove();
19684 gd._hoverdata = undefined;
19685
19686 if(evt.target && oldhoverdata) {
19687 gd.emit('plotly_unhover', {
19688 event: evt,
19689 points: oldhoverdata
19690 });
19691 }
19692};
19693
19694},{"../../lib/dom":171,"../../lib/events":172,"../../lib/throttle":200,"../fx/constants":86}],73:[function(_dereq_,module,exports){
19695/**
19696* Copyright 2012-2020, Plotly, Inc.
19697* All rights reserved.
19698*
19699* This source code is licensed under the MIT license found in the
19700* LICENSE file in the root directory of this source tree.
19701*/
19702
19703
19704'use strict';
19705
19706exports.dash = {
19707 valType: 'string',
19708 // string type usually doesn't take values... this one should really be
19709 // a special type or at least a special coercion function, from the GUI
19710 // you only get these values but elsewhere the user can supply a list of
19711 // dash lengths in px, and it will be honored
19712 values: ['solid', 'dot', 'dash', 'longdash', 'dashdot', 'longdashdot'],
19713 dflt: 'solid',
19714
19715 editType: 'style',
19716
19717};
19718
19719},{}],74:[function(_dereq_,module,exports){
19720/**
19721* Copyright 2012-2020, Plotly, Inc.
19722* All rights reserved.
19723*
19724* This source code is licensed under the MIT license found in the
19725* LICENSE file in the root directory of this source tree.
19726*/
19727
19728
19729'use strict';
19730
19731var d3 = _dereq_('d3');
19732var isNumeric = _dereq_('fast-isnumeric');
19733var tinycolor = _dereq_('tinycolor2');
19734
19735var Registry = _dereq_('../../registry');
19736var Color = _dereq_('../color');
19737var Colorscale = _dereq_('../colorscale');
19738var Lib = _dereq_('../../lib');
19739var svgTextUtils = _dereq_('../../lib/svg_text_utils');
19740
19741var xmlnsNamespaces = _dereq_('../../constants/xmlns_namespaces');
19742var alignment = _dereq_('../../constants/alignment');
19743var LINE_SPACING = alignment.LINE_SPACING;
19744var DESELECTDIM = _dereq_('../../constants/interactions').DESELECTDIM;
19745
19746var subTypes = _dereq_('../../traces/scatter/subtypes');
19747var makeBubbleSizeFn = _dereq_('../../traces/scatter/make_bubble_size_func');
19748var appendArrayPointValue = _dereq_('../../components/fx/helpers').appendArrayPointValue;
19749
19750var drawing = module.exports = {};
19751
19752// -----------------------------------------------------
19753// styling functions for plot elements
19754// -----------------------------------------------------
19755
19756drawing.font = function(s, family, size, color) {
19757 // also allow the form font(s, {family, size, color})
19758 if(Lib.isPlainObject(family)) {
19759 color = family.color;
19760 size = family.size;
19761 family = family.family;
19762 }
19763 if(family) s.style('font-family', family);
19764 if(size + 1) s.style('font-size', size + 'px');
19765 if(color) s.call(Color.fill, color);
19766};
19767
19768/*
19769 * Positioning helpers
19770 * Note: do not use `setPosition` with <text> nodes modified by
19771 * `svgTextUtils.convertToTspans`. Use `svgTextUtils.positionText`
19772 * instead, so that <tspan.line> elements get updated to match.
19773 */
19774drawing.setPosition = function(s, x, y) { s.attr('x', x).attr('y', y); };
19775drawing.setSize = function(s, w, h) { s.attr('width', w).attr('height', h); };
19776drawing.setRect = function(s, x, y, w, h) {
19777 s.call(drawing.setPosition, x, y).call(drawing.setSize, w, h);
19778};
19779
19780/** Translate node
19781 *
19782 * @param {object} d : calcdata point item
19783 * @param {sel} sel : d3 selction of node to translate
19784 * @param {object} xa : corresponding full xaxis object
19785 * @param {object} ya : corresponding full yaxis object
19786 *
19787 * @return {boolean} :
19788 * true if selection got translated
19789 * false if selection could not get translated
19790 */
19791drawing.translatePoint = function(d, sel, xa, ya) {
19792 var x = xa.c2p(d.x);
19793 var y = ya.c2p(d.y);
19794
19795 if(isNumeric(x) && isNumeric(y) && sel.node()) {
19796 // for multiline text this works better
19797 if(sel.node().nodeName === 'text') {
19798 sel.attr('x', x).attr('y', y);
19799 } else {
19800 sel.attr('transform', 'translate(' + x + ',' + y + ')');
19801 }
19802 } else {
19803 return false;
19804 }
19805
19806 return true;
19807};
19808
19809drawing.translatePoints = function(s, xa, ya) {
19810 s.each(function(d) {
19811 var sel = d3.select(this);
19812 drawing.translatePoint(d, sel, xa, ya);
19813 });
19814};
19815
19816drawing.hideOutsideRangePoint = function(d, sel, xa, ya, xcalendar, ycalendar) {
19817 sel.attr(
19818 'display',
19819 (xa.isPtWithinRange(d, xcalendar) && ya.isPtWithinRange(d, ycalendar)) ? null : 'none'
19820 );
19821};
19822
19823drawing.hideOutsideRangePoints = function(traceGroups, subplot) {
19824 if(!subplot._hasClipOnAxisFalse) return;
19825
19826 var xa = subplot.xaxis;
19827 var ya = subplot.yaxis;
19828
19829 traceGroups.each(function(d) {
19830 var trace = d[0].trace;
19831 var xcalendar = trace.xcalendar;
19832 var ycalendar = trace.ycalendar;
19833 var selector = Registry.traceIs(trace, 'bar-like') ? '.bartext' : '.point,.textpoint';
19834
19835 traceGroups.selectAll(selector).each(function(d) {
19836 drawing.hideOutsideRangePoint(d, d3.select(this), xa, ya, xcalendar, ycalendar);
19837 });
19838 });
19839};
19840
19841drawing.crispRound = function(gd, lineWidth, dflt) {
19842 // for lines that disable antialiasing we want to
19843 // make sure the width is an integer, and at least 1 if it's nonzero
19844
19845 if(!lineWidth || !isNumeric(lineWidth)) return dflt || 0;
19846
19847 // but not for static plots - these don't get antialiased anyway.
19848 if(gd._context.staticPlot) return lineWidth;
19849
19850 if(lineWidth < 1) return 1;
19851 return Math.round(lineWidth);
19852};
19853
19854drawing.singleLineStyle = function(d, s, lw, lc, ld) {
19855 s.style('fill', 'none');
19856 var line = (((d || [])[0] || {}).trace || {}).line || {};
19857 var lw1 = lw || line.width || 0;
19858 var dash = ld || line.dash || '';
19859
19860 Color.stroke(s, lc || line.color);
19861 drawing.dashLine(s, dash, lw1);
19862};
19863
19864drawing.lineGroupStyle = function(s, lw, lc, ld) {
19865 s.style('fill', 'none')
19866 .each(function(d) {
19867 var line = (((d || [])[0] || {}).trace || {}).line || {};
19868 var lw1 = lw || line.width || 0;
19869 var dash = ld || line.dash || '';
19870
19871 d3.select(this)
19872 .call(Color.stroke, lc || line.color)
19873 .call(drawing.dashLine, dash, lw1);
19874 });
19875};
19876
19877drawing.dashLine = function(s, dash, lineWidth) {
19878 lineWidth = +lineWidth || 0;
19879
19880 dash = drawing.dashStyle(dash, lineWidth);
19881
19882 s.style({
19883 'stroke-dasharray': dash,
19884 'stroke-width': lineWidth + 'px'
19885 });
19886};
19887
19888drawing.dashStyle = function(dash, lineWidth) {
19889 lineWidth = +lineWidth || 1;
19890 var dlw = Math.max(lineWidth, 3);
19891
19892 if(dash === 'solid') dash = '';
19893 else if(dash === 'dot') dash = dlw + 'px,' + dlw + 'px';
19894 else if(dash === 'dash') dash = (3 * dlw) + 'px,' + (3 * dlw) + 'px';
19895 else if(dash === 'longdash') dash = (5 * dlw) + 'px,' + (5 * dlw) + 'px';
19896 else if(dash === 'dashdot') {
19897 dash = (3 * dlw) + 'px,' + dlw + 'px,' + dlw + 'px,' + dlw + 'px';
19898 } else if(dash === 'longdashdot') {
19899 dash = (5 * dlw) + 'px,' + (2 * dlw) + 'px,' + dlw + 'px,' + (2 * dlw) + 'px';
19900 }
19901 // otherwise user wrote the dasharray themselves - leave it be
19902
19903 return dash;
19904};
19905
19906// Same as fillGroupStyle, except in this case the selection may be a transition
19907drawing.singleFillStyle = function(sel) {
19908 var node = d3.select(sel.node());
19909 var data = node.data();
19910 var fillcolor = (((data[0] || [])[0] || {}).trace || {}).fillcolor;
19911 if(fillcolor) {
19912 sel.call(Color.fill, fillcolor);
19913 }
19914};
19915
19916drawing.fillGroupStyle = function(s) {
19917 s.style('stroke-width', 0)
19918 .each(function(d) {
19919 var shape = d3.select(this);
19920 // N.B. 'd' won't be a calcdata item when
19921 // fill !== 'none' on a segment-less and marker-less trace
19922 if(d[0].trace) {
19923 shape.call(Color.fill, d[0].trace.fillcolor);
19924 }
19925 });
19926};
19927
19928var SYMBOLDEFS = _dereq_('./symbol_defs');
19929
19930drawing.symbolNames = [];
19931drawing.symbolFuncs = [];
19932drawing.symbolNeedLines = {};
19933drawing.symbolNoDot = {};
19934drawing.symbolNoFill = {};
19935drawing.symbolList = [];
19936
19937Object.keys(SYMBOLDEFS).forEach(function(k) {
19938 var symDef = SYMBOLDEFS[k];
19939 var n = symDef.n;
19940 drawing.symbolList.push(
19941 n,
19942 k,
19943 n + 100,
19944 k + '-open'
19945 );
19946 drawing.symbolNames[n] = k;
19947 drawing.symbolFuncs[n] = symDef.f;
19948
19949 if(symDef.needLine) {
19950 drawing.symbolNeedLines[n] = true;
19951 }
19952 if(symDef.noDot) {
19953 drawing.symbolNoDot[n] = true;
19954 } else {
19955 drawing.symbolList.push(
19956 n + 200,
19957 k + '-dot',
19958 n + 300,
19959 k + '-open-dot'
19960 );
19961 }
19962 if(symDef.noFill) {
19963 drawing.symbolNoFill[n] = true;
19964 }
19965});
19966
19967var MAXSYMBOL = drawing.symbolNames.length;
19968// add a dot in the middle of the symbol
19969var DOTPATH = 'M0,0.5L0.5,0L0,-0.5L-0.5,0Z';
19970
19971drawing.symbolNumber = function(v) {
19972 if(typeof v === 'string') {
19973 var vbase = 0;
19974 if(v.indexOf('-open') > 0) {
19975 vbase = 100;
19976 v = v.replace('-open', '');
19977 }
19978 if(v.indexOf('-dot') > 0) {
19979 vbase += 200;
19980 v = v.replace('-dot', '');
19981 }
19982 v = drawing.symbolNames.indexOf(v);
19983 if(v >= 0) { v += vbase; }
19984 }
19985
19986 return (v % 100 >= MAXSYMBOL || v >= 400) ?
19987 0 : Math.floor(Math.max(v, 0));
19988};
19989
19990function makePointPath(symbolNumber, r) {
19991 var base = symbolNumber % 100;
19992 return drawing.symbolFuncs[base](r) + (symbolNumber >= 200 ? DOTPATH : '');
19993}
19994
19995var HORZGRADIENT = {x1: 1, x2: 0, y1: 0, y2: 0};
19996var VERTGRADIENT = {x1: 0, x2: 0, y1: 1, y2: 0};
19997var stopFormatter = d3.format('~.1f');
19998var gradientInfo = {
19999 radial: {node: 'radialGradient'},
20000 radialreversed: {node: 'radialGradient', reversed: true},
20001 horizontal: {node: 'linearGradient', attrs: HORZGRADIENT},
20002 horizontalreversed: {node: 'linearGradient', attrs: HORZGRADIENT, reversed: true},
20003 vertical: {node: 'linearGradient', attrs: VERTGRADIENT},
20004 verticalreversed: {node: 'linearGradient', attrs: VERTGRADIENT, reversed: true}
20005};
20006
20007/**
20008 * gradient: create and apply a gradient fill
20009 *
20010 * @param {object} sel: d3 selection to apply this gradient to
20011 * You can use `selection.call(Drawing.gradient, ...)`
20012 * @param {DOM element} gd: the graph div `sel` is part of
20013 * @param {string} gradientID: a unique (within this plot) identifier
20014 * for this gradient, so that we don't create unnecessary definitions
20015 * @param {string} type: 'radial', 'horizontal', or 'vertical', optionally with
20016 * 'reversed' at the end. Normally radial goes center to edge,
20017 * horizontal goes right to left, and vertical goes bottom to top
20018 * @param {array} colorscale: as in attribute values, [[fraction, color], ...]
20019 * @param {string} prop: the property to apply to, 'fill' or 'stroke'
20020 */
20021drawing.gradient = function(sel, gd, gradientID, type, colorscale, prop) {
20022 var len = colorscale.length;
20023 var info = gradientInfo[type];
20024 var colorStops = new Array(len);
20025 for(var i = 0; i < len; i++) {
20026 if(info.reversed) {
20027 colorStops[len - 1 - i] = [stopFormatter((1 - colorscale[i][0]) * 100), colorscale[i][1]];
20028 } else {
20029 colorStops[i] = [stopFormatter(colorscale[i][0] * 100), colorscale[i][1]];
20030 }
20031 }
20032
20033 var fullLayout = gd._fullLayout;
20034 var fullID = 'g' + fullLayout._uid + '-' + gradientID;
20035
20036 var gradient = fullLayout._defs.select('.gradients')
20037 .selectAll('#' + fullID)
20038 .data([type + colorStops.join(';')], Lib.identity);
20039
20040 gradient.exit().remove();
20041
20042 gradient.enter()
20043 .append(info.node)
20044 .each(function() {
20045 var el = d3.select(this);
20046 if(info.attrs) el.attr(info.attrs);
20047
20048 el.attr('id', fullID);
20049
20050 var stops = el.selectAll('stop')
20051 .data(colorStops);
20052 stops.exit().remove();
20053 stops.enter().append('stop');
20054
20055 stops.each(function(d) {
20056 var tc = tinycolor(d[1]);
20057 d3.select(this).attr({
20058 offset: d[0] + '%',
20059 'stop-color': Color.tinyRGB(tc),
20060 'stop-opacity': tc.getAlpha()
20061 });
20062 });
20063 });
20064
20065 sel.style(prop, getFullUrl(fullID, gd))
20066 .style(prop + '-opacity', null);
20067
20068 var className2query = function(s) {
20069 return '.' + s.attr('class').replace(/\s/g, '.');
20070 };
20071 var k = className2query(d3.select(sel.node().parentNode)) +
20072 '>' + className2query(sel);
20073 fullLayout._gradientUrlQueryParts[k] = 1;
20074};
20075
20076/*
20077 * Make the gradients container and clear out any previous gradients.
20078 * We never collect all the gradients we need in one place,
20079 * so we can't ever remove gradients that have stopped being useful,
20080 * except all at once before a full redraw.
20081 * The upside of this is arbitrary points can share gradient defs
20082 */
20083drawing.initGradients = function(gd) {
20084 var fullLayout = gd._fullLayout;
20085
20086 var gradientsGroup = Lib.ensureSingle(fullLayout._defs, 'g', 'gradients');
20087 gradientsGroup.selectAll('linearGradient,radialGradient').remove();
20088
20089 // initialize stash of query parts filled in Drawing.gradient,
20090 // used to fix URL strings during image exports
20091 fullLayout._gradientUrlQueryParts = {};
20092};
20093
20094
20095drawing.pointStyle = function(s, trace, gd) {
20096 if(!s.size()) return;
20097
20098 var fns = drawing.makePointStyleFns(trace);
20099
20100 s.each(function(d) {
20101 drawing.singlePointStyle(d, d3.select(this), trace, fns, gd);
20102 });
20103};
20104
20105drawing.singlePointStyle = function(d, sel, trace, fns, gd) {
20106 var marker = trace.marker;
20107 var markerLine = marker.line;
20108
20109 sel.style('opacity',
20110 fns.selectedOpacityFn ? fns.selectedOpacityFn(d) :
20111 (d.mo === undefined ? marker.opacity : d.mo)
20112 );
20113
20114 if(fns.ms2mrc) {
20115 var r;
20116
20117 // handle multi-trace graph edit case
20118 if(d.ms === 'various' || marker.size === 'various') {
20119 r = 3;
20120 } else {
20121 r = fns.ms2mrc(d.ms);
20122 }
20123
20124 // store the calculated size so hover can use it
20125 d.mrc = r;
20126
20127 if(fns.selectedSizeFn) {
20128 r = d.mrc = fns.selectedSizeFn(d);
20129 }
20130
20131 // turn the symbol into a sanitized number
20132 var x = drawing.symbolNumber(d.mx || marker.symbol) || 0;
20133
20134 // save if this marker is open
20135 // because that impacts how to handle colors
20136 d.om = x % 200 >= 100;
20137
20138 sel.attr('d', makePointPath(x, r));
20139 }
20140
20141 var perPointGradient = false;
20142 var fillColor, lineColor, lineWidth;
20143
20144 // 'so' is suspected outliers, for box plots
20145 if(d.so) {
20146 lineWidth = markerLine.outlierwidth;
20147 lineColor = markerLine.outliercolor;
20148 fillColor = marker.outliercolor;
20149 } else {
20150 var markerLineWidth = (markerLine || {}).width;
20151
20152 lineWidth = (
20153 d.mlw + 1 ||
20154 markerLineWidth + 1 ||
20155 // TODO: we need the latter for legends... can we get rid of it?
20156 (d.trace ? (d.trace.marker.line || {}).width : 0) + 1
20157 ) - 1 || 0;
20158
20159 if('mlc' in d) lineColor = d.mlcc = fns.lineScale(d.mlc);
20160 // weird case: array wasn't long enough to apply to every point
20161 else if(Lib.isArrayOrTypedArray(markerLine.color)) lineColor = Color.defaultLine;
20162 else lineColor = markerLine.color;
20163
20164 if(Lib.isArrayOrTypedArray(marker.color)) {
20165 fillColor = Color.defaultLine;
20166 perPointGradient = true;
20167 }
20168
20169 if('mc' in d) {
20170 fillColor = d.mcc = fns.markerScale(d.mc);
20171 } else {
20172 fillColor = marker.color || 'rgba(0,0,0,0)';
20173 }
20174
20175 if(fns.selectedColorFn) {
20176 fillColor = fns.selectedColorFn(d);
20177 }
20178 }
20179
20180 if(d.om) {
20181 // open markers can't have zero linewidth, default to 1px,
20182 // and use fill color as stroke color
20183 sel.call(Color.stroke, fillColor)
20184 .style({
20185 'stroke-width': (lineWidth || 1) + 'px',
20186 fill: 'none'
20187 });
20188 } else {
20189 sel.style('stroke-width', (d.isBlank ? 0 : lineWidth) + 'px');
20190
20191 var markerGradient = marker.gradient;
20192
20193 var gradientType = d.mgt;
20194 if(gradientType) perPointGradient = true;
20195 else gradientType = markerGradient && markerGradient.type;
20196
20197 // for legend - arrays will propagate through here, but we don't need
20198 // to treat it as per-point.
20199 if(Array.isArray(gradientType)) {
20200 gradientType = gradientType[0];
20201 if(!gradientInfo[gradientType]) gradientType = 0;
20202 }
20203
20204 if(gradientType && gradientType !== 'none') {
20205 var gradientColor = d.mgc;
20206 if(gradientColor) perPointGradient = true;
20207 else gradientColor = markerGradient.color;
20208
20209 var gradientID = trace.uid;
20210 if(perPointGradient) gradientID += '-' + d.i;
20211
20212 drawing.gradient(sel, gd, gradientID, gradientType,
20213 [[0, gradientColor], [1, fillColor]], 'fill');
20214 } else {
20215 Color.fill(sel, fillColor);
20216 }
20217
20218 if(lineWidth) {
20219 Color.stroke(sel, lineColor);
20220 }
20221 }
20222};
20223
20224drawing.makePointStyleFns = function(trace) {
20225 var out = {};
20226 var marker = trace.marker;
20227
20228 // allow array marker and marker line colors to be
20229 // scaled by given max and min to colorscales
20230 out.markerScale = drawing.tryColorscale(marker, '');
20231 out.lineScale = drawing.tryColorscale(marker, 'line');
20232
20233 if(Registry.traceIs(trace, 'symbols')) {
20234 out.ms2mrc = subTypes.isBubble(trace) ?
20235 makeBubbleSizeFn(trace) :
20236 function() { return (marker.size || 6) / 2; };
20237 }
20238
20239 if(trace.selectedpoints) {
20240 Lib.extendFlat(out, drawing.makeSelectedPointStyleFns(trace));
20241 }
20242
20243 return out;
20244};
20245
20246drawing.makeSelectedPointStyleFns = function(trace) {
20247 var out = {};
20248
20249 var selectedAttrs = trace.selected || {};
20250 var unselectedAttrs = trace.unselected || {};
20251
20252 var marker = trace.marker || {};
20253 var selectedMarker = selectedAttrs.marker || {};
20254 var unselectedMarker = unselectedAttrs.marker || {};
20255
20256 var mo = marker.opacity;
20257 var smo = selectedMarker.opacity;
20258 var usmo = unselectedMarker.opacity;
20259 var smoIsDefined = smo !== undefined;
20260 var usmoIsDefined = usmo !== undefined;
20261
20262 if(Lib.isArrayOrTypedArray(mo) || smoIsDefined || usmoIsDefined) {
20263 out.selectedOpacityFn = function(d) {
20264 var base = d.mo === undefined ? marker.opacity : d.mo;
20265
20266 if(d.selected) {
20267 return smoIsDefined ? smo : base;
20268 } else {
20269 return usmoIsDefined ? usmo : DESELECTDIM * base;
20270 }
20271 };
20272 }
20273
20274 var mc = marker.color;
20275 var smc = selectedMarker.color;
20276 var usmc = unselectedMarker.color;
20277
20278 if(smc || usmc) {
20279 out.selectedColorFn = function(d) {
20280 var base = d.mcc || mc;
20281
20282 if(d.selected) {
20283 return smc || base;
20284 } else {
20285 return usmc || base;
20286 }
20287 };
20288 }
20289
20290 var ms = marker.size;
20291 var sms = selectedMarker.size;
20292 var usms = unselectedMarker.size;
20293 var smsIsDefined = sms !== undefined;
20294 var usmsIsDefined = usms !== undefined;
20295
20296 if(Registry.traceIs(trace, 'symbols') && (smsIsDefined || usmsIsDefined)) {
20297 out.selectedSizeFn = function(d) {
20298 var base = d.mrc || ms / 2;
20299
20300 if(d.selected) {
20301 return smsIsDefined ? sms / 2 : base;
20302 } else {
20303 return usmsIsDefined ? usms / 2 : base;
20304 }
20305 };
20306 }
20307
20308 return out;
20309};
20310
20311drawing.makeSelectedTextStyleFns = function(trace) {
20312 var out = {};
20313
20314 var selectedAttrs = trace.selected || {};
20315 var unselectedAttrs = trace.unselected || {};
20316
20317 var textFont = trace.textfont || {};
20318 var selectedTextFont = selectedAttrs.textfont || {};
20319 var unselectedTextFont = unselectedAttrs.textfont || {};
20320
20321 var tc = textFont.color;
20322 var stc = selectedTextFont.color;
20323 var utc = unselectedTextFont.color;
20324
20325 out.selectedTextColorFn = function(d) {
20326 var base = d.tc || tc;
20327
20328 if(d.selected) {
20329 return stc || base;
20330 } else {
20331 if(utc) return utc;
20332 else return stc ? base : Color.addOpacity(base, DESELECTDIM);
20333 }
20334 };
20335
20336 return out;
20337};
20338
20339drawing.selectedPointStyle = function(s, trace) {
20340 if(!s.size() || !trace.selectedpoints) return;
20341
20342 var fns = drawing.makeSelectedPointStyleFns(trace);
20343 var marker = trace.marker || {};
20344 var seq = [];
20345
20346 if(fns.selectedOpacityFn) {
20347 seq.push(function(pt, d) {
20348 pt.style('opacity', fns.selectedOpacityFn(d));
20349 });
20350 }
20351
20352 if(fns.selectedColorFn) {
20353 seq.push(function(pt, d) {
20354 Color.fill(pt, fns.selectedColorFn(d));
20355 });
20356 }
20357
20358 if(fns.selectedSizeFn) {
20359 seq.push(function(pt, d) {
20360 var mx = d.mx || marker.symbol || 0;
20361 var mrc2 = fns.selectedSizeFn(d);
20362
20363 pt.attr('d', makePointPath(drawing.symbolNumber(mx), mrc2));
20364
20365 // save for Drawing.selectedTextStyle
20366 d.mrc2 = mrc2;
20367 });
20368 }
20369
20370 if(seq.length) {
20371 s.each(function(d) {
20372 var pt = d3.select(this);
20373 for(var i = 0; i < seq.length; i++) {
20374 seq[i](pt, d);
20375 }
20376 });
20377 }
20378};
20379
20380drawing.tryColorscale = function(marker, prefix) {
20381 var cont = prefix ? Lib.nestedProperty(marker, prefix).get() : marker;
20382
20383 if(cont) {
20384 var colorArray = cont.color;
20385 if((cont.colorscale || cont._colorAx) && Lib.isArrayOrTypedArray(colorArray)) {
20386 return Colorscale.makeColorScaleFuncFromTrace(cont);
20387 }
20388 }
20389 return Lib.identity;
20390};
20391
20392var TEXTOFFSETSIGN = {
20393 start: 1, end: -1, middle: 0, bottom: 1, top: -1
20394};
20395
20396function textPointPosition(s, textPosition, fontSize, markerRadius) {
20397 var group = d3.select(s.node().parentNode);
20398
20399 var v = textPosition.indexOf('top') !== -1 ?
20400 'top' :
20401 textPosition.indexOf('bottom') !== -1 ? 'bottom' : 'middle';
20402 var h = textPosition.indexOf('left') !== -1 ?
20403 'end' :
20404 textPosition.indexOf('right') !== -1 ? 'start' : 'middle';
20405
20406 // if markers are shown, offset a little more than
20407 // the nominal marker size
20408 // ie 2/1.6 * nominal, bcs some markers are a bit bigger
20409 var r = markerRadius ? markerRadius / 0.8 + 1 : 0;
20410
20411 var numLines = (svgTextUtils.lineCount(s) - 1) * LINE_SPACING + 1;
20412 var dx = TEXTOFFSETSIGN[h] * r;
20413 var dy = fontSize * 0.75 + TEXTOFFSETSIGN[v] * r +
20414 (TEXTOFFSETSIGN[v] - 1) * numLines * fontSize / 2;
20415
20416 // fix the overall text group position
20417 s.attr('text-anchor', h);
20418 group.attr('transform', 'translate(' + dx + ',' + dy + ')');
20419}
20420
20421function extracTextFontSize(d, trace) {
20422 var fontSize = d.ts || trace.textfont.size;
20423 return (isNumeric(fontSize) && fontSize > 0) ? fontSize : 0;
20424}
20425
20426// draw text at points
20427drawing.textPointStyle = function(s, trace, gd) {
20428 if(!s.size()) return;
20429
20430 var selectedTextColorFn;
20431 if(trace.selectedpoints) {
20432 var fns = drawing.makeSelectedTextStyleFns(trace);
20433 selectedTextColorFn = fns.selectedTextColorFn;
20434 }
20435
20436 var texttemplate = trace.texttemplate;
20437 var fullLayout = gd._fullLayout;
20438
20439 s.each(function(d) {
20440 var p = d3.select(this);
20441
20442 var text = texttemplate ?
20443 Lib.extractOption(d, trace, 'txt', 'texttemplate') :
20444 Lib.extractOption(d, trace, 'tx', 'text');
20445
20446 if(!text && text !== 0) {
20447 p.remove();
20448 return;
20449 }
20450
20451 if(texttemplate) {
20452 var labels = trace._module.formatLabels ? trace._module.formatLabels(d, trace, fullLayout) : {};
20453 var pointValues = {};
20454 appendArrayPointValue(pointValues, trace, d.i);
20455 var meta = trace._meta || {};
20456 text = Lib.texttemplateString(text, labels, fullLayout._d3locale, pointValues, d, meta);
20457 }
20458
20459 var pos = d.tp || trace.textposition;
20460 var fontSize = extracTextFontSize(d, trace);
20461 var fontColor = selectedTextColorFn ?
20462 selectedTextColorFn(d) :
20463 (d.tc || trace.textfont.color);
20464
20465 p.call(drawing.font,
20466 d.tf || trace.textfont.family,
20467 fontSize,
20468 fontColor)
20469 .text(text)
20470 .call(svgTextUtils.convertToTspans, gd)
20471 .call(textPointPosition, pos, fontSize, d.mrc);
20472 });
20473};
20474
20475drawing.selectedTextStyle = function(s, trace) {
20476 if(!s.size() || !trace.selectedpoints) return;
20477
20478 var fns = drawing.makeSelectedTextStyleFns(trace);
20479
20480 s.each(function(d) {
20481 var tx = d3.select(this);
20482 var tc = fns.selectedTextColorFn(d);
20483 var tp = d.tp || trace.textposition;
20484 var fontSize = extracTextFontSize(d, trace);
20485
20486 Color.fill(tx, tc);
20487 textPointPosition(tx, tp, fontSize, d.mrc2 || d.mrc);
20488 });
20489};
20490
20491// generalized Catmull-Rom splines, per
20492// http://www.cemyuksel.com/research/catmullrom_param/catmullrom.pdf
20493var CatmullRomExp = 0.5;
20494drawing.smoothopen = function(pts, smoothness) {
20495 if(pts.length < 3) { return 'M' + pts.join('L');}
20496 var path = 'M' + pts[0];
20497 var tangents = [];
20498 var i;
20499 for(i = 1; i < pts.length - 1; i++) {
20500 tangents.push(makeTangent(pts[i - 1], pts[i], pts[i + 1], smoothness));
20501 }
20502 path += 'Q' + tangents[0][0] + ' ' + pts[1];
20503 for(i = 2; i < pts.length - 1; i++) {
20504 path += 'C' + tangents[i - 2][1] + ' ' + tangents[i - 1][0] + ' ' + pts[i];
20505 }
20506 path += 'Q' + tangents[pts.length - 3][1] + ' ' + pts[pts.length - 1];
20507 return path;
20508};
20509
20510drawing.smoothclosed = function(pts, smoothness) {
20511 if(pts.length < 3) { return 'M' + pts.join('L') + 'Z'; }
20512 var path = 'M' + pts[0];
20513 var pLast = pts.length - 1;
20514 var tangents = [makeTangent(pts[pLast], pts[0], pts[1], smoothness)];
20515 var i;
20516 for(i = 1; i < pLast; i++) {
20517 tangents.push(makeTangent(pts[i - 1], pts[i], pts[i + 1], smoothness));
20518 }
20519 tangents.push(
20520 makeTangent(pts[pLast - 1], pts[pLast], pts[0], smoothness)
20521 );
20522
20523 for(i = 1; i <= pLast; i++) {
20524 path += 'C' + tangents[i - 1][1] + ' ' + tangents[i][0] + ' ' + pts[i];
20525 }
20526 path += 'C' + tangents[pLast][1] + ' ' + tangents[0][0] + ' ' + pts[0] + 'Z';
20527 return path;
20528};
20529
20530function makeTangent(prevpt, thispt, nextpt, smoothness) {
20531 var d1x = prevpt[0] - thispt[0];
20532 var d1y = prevpt[1] - thispt[1];
20533 var d2x = nextpt[0] - thispt[0];
20534 var d2y = nextpt[1] - thispt[1];
20535 var d1a = Math.pow(d1x * d1x + d1y * d1y, CatmullRomExp / 2);
20536 var d2a = Math.pow(d2x * d2x + d2y * d2y, CatmullRomExp / 2);
20537 var numx = (d2a * d2a * d1x - d1a * d1a * d2x) * smoothness;
20538 var numy = (d2a * d2a * d1y - d1a * d1a * d2y) * smoothness;
20539 var denom1 = 3 * d2a * (d1a + d2a);
20540 var denom2 = 3 * d1a * (d1a + d2a);
20541 return [
20542 [
20543 d3.round(thispt[0] + (denom1 && numx / denom1), 2),
20544 d3.round(thispt[1] + (denom1 && numy / denom1), 2)
20545 ], [
20546 d3.round(thispt[0] - (denom2 && numx / denom2), 2),
20547 d3.round(thispt[1] - (denom2 && numy / denom2), 2)
20548 ]
20549 ];
20550}
20551
20552// step paths - returns a generator function for paths
20553// with the given step shape
20554var STEPPATH = {
20555 hv: function(p0, p1) {
20556 return 'H' + d3.round(p1[0], 2) + 'V' + d3.round(p1[1], 2);
20557 },
20558 vh: function(p0, p1) {
20559 return 'V' + d3.round(p1[1], 2) + 'H' + d3.round(p1[0], 2);
20560 },
20561 hvh: function(p0, p1) {
20562 return 'H' + d3.round((p0[0] + p1[0]) / 2, 2) + 'V' +
20563 d3.round(p1[1], 2) + 'H' + d3.round(p1[0], 2);
20564 },
20565 vhv: function(p0, p1) {
20566 return 'V' + d3.round((p0[1] + p1[1]) / 2, 2) + 'H' +
20567 d3.round(p1[0], 2) + 'V' + d3.round(p1[1], 2);
20568 }
20569};
20570var STEPLINEAR = function(p0, p1) {
20571 return 'L' + d3.round(p1[0], 2) + ',' + d3.round(p1[1], 2);
20572};
20573drawing.steps = function(shape) {
20574 var onestep = STEPPATH[shape] || STEPLINEAR;
20575 return function(pts) {
20576 var path = 'M' + d3.round(pts[0][0], 2) + ',' + d3.round(pts[0][1], 2);
20577 for(var i = 1; i < pts.length; i++) {
20578 path += onestep(pts[i - 1], pts[i]);
20579 }
20580 return path;
20581 };
20582};
20583
20584// off-screen svg render testing element, shared by the whole page
20585// uses the id 'js-plotly-tester' and stores it in drawing.tester
20586drawing.makeTester = function() {
20587 var tester = Lib.ensureSingleById(d3.select('body'), 'svg', 'js-plotly-tester', function(s) {
20588 s.attr(xmlnsNamespaces.svgAttrs)
20589 .style({
20590 position: 'absolute',
20591 left: '-10000px',
20592 top: '-10000px',
20593 width: '9000px',
20594 height: '9000px',
20595 'z-index': '1'
20596 });
20597 });
20598
20599 // browsers differ on how they describe the bounding rect of
20600 // the svg if its contents spill over... so make a 1x1px
20601 // reference point we can measure off of.
20602 var testref = Lib.ensureSingle(tester, 'path', 'js-reference-point', function(s) {
20603 s.attr('d', 'M0,0H1V1H0Z')
20604 .style({
20605 'stroke-width': 0,
20606 fill: 'black'
20607 });
20608 });
20609
20610 drawing.tester = tester;
20611 drawing.testref = testref;
20612};
20613
20614/*
20615 * use our offscreen tester to get a clientRect for an element,
20616 * in a reference frame where it isn't translated (or transformed) and
20617 * its anchor point is at (0,0)
20618 * always returns a copy of the bbox, so the caller can modify it safely
20619 *
20620 * @param {SVGElement} node: the element to measure. If possible this should be
20621 * a <text> or MathJax <g> element that's already passed through
20622 * `convertToTspans` because in that case we can cache the results, but it's
20623 * possible to pass in any svg element.
20624 *
20625 * @param {boolean} inTester: is this element already in `drawing.tester`?
20626 * If you are measuring a dummy element, rather than one you really intend
20627 * to use on the plot, making it in `drawing.tester` in the first place
20628 * allows us to test faster because it cuts out cloning and appending it.
20629 *
20630 * @param {string} hash: for internal use only, if we already know the cache key
20631 * for this element beforehand.
20632 *
20633 * @return {object}: a plain object containing the width, height, left, right,
20634 * top, and bottom of `node`
20635 */
20636drawing.savedBBoxes = {};
20637var savedBBoxesCount = 0;
20638var maxSavedBBoxes = 10000;
20639
20640drawing.bBox = function(node, inTester, hash) {
20641 /*
20642 * Cache elements we've already measured so we don't have to
20643 * remeasure the same thing many times
20644 * We have a few bBox callers though who pass a node larger than
20645 * a <text> or a MathJax <g>, such as an axis group containing many labels.
20646 * These will not generate a hash (unless we figure out an appropriate
20647 * hash key for them) and thus we will not hash them.
20648 */
20649 if(!hash) hash = nodeHash(node);
20650 var out;
20651 if(hash) {
20652 out = drawing.savedBBoxes[hash];
20653 if(out) return Lib.extendFlat({}, out);
20654 } else if(node.childNodes.length === 1) {
20655 /*
20656 * If we have only one child element, which is itself hashable, make
20657 * a new hash from this element plus its x,y,transform
20658 * These bounding boxes *include* x,y,transform - mostly for use by
20659 * callers trying to avoid overlaps (ie titles)
20660 */
20661 var innerNode = node.childNodes[0];
20662
20663 hash = nodeHash(innerNode);
20664 if(hash) {
20665 var x = +innerNode.getAttribute('x') || 0;
20666 var y = +innerNode.getAttribute('y') || 0;
20667 var transform = innerNode.getAttribute('transform');
20668
20669 if(!transform) {
20670 // in this case, just varying x and y, don't bother caching
20671 // the final bBox because the alteration is quick.
20672 var innerBB = drawing.bBox(innerNode, false, hash);
20673 if(x) {
20674 innerBB.left += x;
20675 innerBB.right += x;
20676 }
20677 if(y) {
20678 innerBB.top += y;
20679 innerBB.bottom += y;
20680 }
20681 return innerBB;
20682 }
20683 /*
20684 * else we have a transform - rather than make a complicated
20685 * (and error-prone and probably slow) transform parser/calculator,
20686 * just continue on calculating the boundingClientRect of the group
20687 * and use the new composite hash to cache it.
20688 * That said, `innerNode.transform.baseVal` is an array of
20689 * `SVGTransform` objects, that *do* seem to have a nice matrix
20690 * multiplication interface that we could use to avoid making
20691 * another getBoundingClientRect call...
20692 */
20693 hash += '~' + x + '~' + y + '~' + transform;
20694
20695 out = drawing.savedBBoxes[hash];
20696 if(out) return Lib.extendFlat({}, out);
20697 }
20698 }
20699 var testNode, tester;
20700 if(inTester) {
20701 testNode = node;
20702 } else {
20703 tester = drawing.tester.node();
20704
20705 // copy the node to test into the tester
20706 testNode = node.cloneNode(true);
20707 tester.appendChild(testNode);
20708 }
20709
20710 // standardize its position (and newline tspans if any)
20711 d3.select(testNode)
20712 .attr('transform', null)
20713 .call(svgTextUtils.positionText, 0, 0);
20714
20715 var testRect = testNode.getBoundingClientRect();
20716 var refRect = drawing.testref
20717 .node()
20718 .getBoundingClientRect();
20719
20720 if(!inTester) tester.removeChild(testNode);
20721
20722 var bb = {
20723 height: testRect.height,
20724 width: testRect.width,
20725 left: testRect.left - refRect.left,
20726 top: testRect.top - refRect.top,
20727 right: testRect.right - refRect.left,
20728 bottom: testRect.bottom - refRect.top
20729 };
20730
20731 // make sure we don't have too many saved boxes,
20732 // or a long session could overload on memory
20733 // by saving boxes for long-gone elements
20734 if(savedBBoxesCount >= maxSavedBBoxes) {
20735 drawing.savedBBoxes = {};
20736 savedBBoxesCount = 0;
20737 }
20738
20739 // cache this bbox
20740 if(hash) drawing.savedBBoxes[hash] = bb;
20741 savedBBoxesCount++;
20742
20743 return Lib.extendFlat({}, bb);
20744};
20745
20746// capture everything about a node (at least in our usage) that
20747// impacts its bounding box, given that bBox clears x, y, and transform
20748function nodeHash(node) {
20749 var inputText = node.getAttribute('data-unformatted');
20750 if(inputText === null) return;
20751 return inputText +
20752 node.getAttribute('data-math') +
20753 node.getAttribute('text-anchor') +
20754 node.getAttribute('style');
20755}
20756
20757/**
20758 * Set clipPath URL in a way that work for all situations.
20759 *
20760 * In details, graphs on pages with <base> HTML tags need to prepend
20761 * the clip path ids with the page's base url EXCEPT during toImage exports.
20762 *
20763 * @param {d3 selection} s : node to add clip-path attribute
20764 * @param {string} localId : local clip-path (w/o base url) id
20765 * @param {DOM element || object} gd
20766 * - context._baseUrl {string}
20767 * - context._exportedPlot {boolean}
20768 */
20769drawing.setClipUrl = function(s, localId, gd) {
20770 s.attr('clip-path', getFullUrl(localId, gd));
20771};
20772
20773function getFullUrl(localId, gd) {
20774 if(!localId) return null;
20775
20776 var context = gd._context;
20777 var baseUrl = context._exportedPlot ? '' : (context._baseUrl || '');
20778 return 'url(\'' + baseUrl + '#' + localId + '\')';
20779}
20780
20781drawing.getTranslate = function(element) {
20782 // Note the separator [^\d] between x and y in this regex
20783 // We generally use ',' but IE will convert it to ' '
20784 var re = /.*\btranslate\((-?\d*\.?\d*)[^-\d]*(-?\d*\.?\d*)[^\d].*/;
20785 var getter = element.attr ? 'attr' : 'getAttribute';
20786 var transform = element[getter]('transform') || '';
20787
20788 var translate = transform.replace(re, function(match, p1, p2) {
20789 return [p1, p2].join(' ');
20790 })
20791 .split(' ');
20792
20793 return {
20794 x: +translate[0] || 0,
20795 y: +translate[1] || 0
20796 };
20797};
20798
20799drawing.setTranslate = function(element, x, y) {
20800 var re = /(\btranslate\(.*?\);?)/;
20801 var getter = element.attr ? 'attr' : 'getAttribute';
20802 var setter = element.attr ? 'attr' : 'setAttribute';
20803 var transform = element[getter]('transform') || '';
20804
20805 x = x || 0;
20806 y = y || 0;
20807
20808 transform = transform.replace(re, '').trim();
20809 transform += ' translate(' + x + ', ' + y + ')';
20810 transform = transform.trim();
20811
20812 element[setter]('transform', transform);
20813
20814 return transform;
20815};
20816
20817drawing.getScale = function(element) {
20818 var re = /.*\bscale\((\d*\.?\d*)[^\d]*(\d*\.?\d*)[^\d].*/;
20819 var getter = element.attr ? 'attr' : 'getAttribute';
20820 var transform = element[getter]('transform') || '';
20821
20822 var translate = transform.replace(re, function(match, p1, p2) {
20823 return [p1, p2].join(' ');
20824 })
20825 .split(' ');
20826
20827 return {
20828 x: +translate[0] || 1,
20829 y: +translate[1] || 1
20830 };
20831};
20832
20833drawing.setScale = function(element, x, y) {
20834 var re = /(\bscale\(.*?\);?)/;
20835 var getter = element.attr ? 'attr' : 'getAttribute';
20836 var setter = element.attr ? 'attr' : 'setAttribute';
20837 var transform = element[getter]('transform') || '';
20838
20839 x = x || 1;
20840 y = y || 1;
20841
20842 transform = transform.replace(re, '').trim();
20843 transform += ' scale(' + x + ', ' + y + ')';
20844 transform = transform.trim();
20845
20846 element[setter]('transform', transform);
20847
20848 return transform;
20849};
20850
20851var SCALE_RE = /\s*sc.*/;
20852
20853drawing.setPointGroupScale = function(selection, xScale, yScale) {
20854 xScale = xScale || 1;
20855 yScale = yScale || 1;
20856
20857 if(!selection) return;
20858
20859 // The same scale transform for every point:
20860 var scale = (xScale === 1 && yScale === 1) ?
20861 '' :
20862 ' scale(' + xScale + ',' + yScale + ')';
20863
20864 selection.each(function() {
20865 var t = (this.getAttribute('transform') || '').replace(SCALE_RE, '');
20866 t += scale;
20867 t = t.trim();
20868 this.setAttribute('transform', t);
20869 });
20870};
20871
20872var TEXT_POINT_LAST_TRANSLATION_RE = /translate\([^)]*\)\s*$/;
20873
20874drawing.setTextPointsScale = function(selection, xScale, yScale) {
20875 if(!selection) return;
20876
20877 selection.each(function() {
20878 var transforms;
20879 var el = d3.select(this);
20880 var text = el.select('text');
20881
20882 if(!text.node()) return;
20883
20884 var x = parseFloat(text.attr('x') || 0);
20885 var y = parseFloat(text.attr('y') || 0);
20886
20887 var existingTransform = (el.attr('transform') || '').match(TEXT_POINT_LAST_TRANSLATION_RE);
20888
20889 if(xScale === 1 && yScale === 1) {
20890 transforms = [];
20891 } else {
20892 transforms = [
20893 'translate(' + x + ',' + y + ')',
20894 'scale(' + xScale + ',' + yScale + ')',
20895 'translate(' + (-x) + ',' + (-y) + ')',
20896 ];
20897 }
20898
20899 if(existingTransform) {
20900 transforms.push(existingTransform);
20901 }
20902
20903 el.attr('transform', transforms.join(' '));
20904 });
20905};
20906
20907},{"../../components/fx/helpers":88,"../../constants/alignment":154,"../../constants/interactions":157,"../../constants/xmlns_namespaces":159,"../../lib":178,"../../lib/svg_text_utils":199,"../../registry":269,"../../traces/scatter/make_bubble_size_func":406,"../../traces/scatter/subtypes":413,"../color":52,"../colorscale":64,"./symbol_defs":75,"d3":16,"fast-isnumeric":18,"tinycolor2":35}],75:[function(_dereq_,module,exports){
20908/**
20909* Copyright 2012-2020, Plotly, Inc.
20910* All rights reserved.
20911*
20912* This source code is licensed under the MIT license found in the
20913* LICENSE file in the root directory of this source tree.
20914*/
20915
20916
20917'use strict';
20918
20919var d3 = _dereq_('d3');
20920
20921/** Marker symbol definitions
20922 * users can specify markers either by number or name
20923 * add 100 (or '-open') and you get an open marker
20924 * open markers have no fill and use line color as the stroke color
20925 * add 200 (or '-dot') and you get a dot in the middle
20926 * add both and you get both
20927 */
20928
20929module.exports = {
20930 circle: {
20931 n: 0,
20932 f: function(r) {
20933 var rs = d3.round(r, 2);
20934 return 'M' + rs + ',0A' + rs + ',' + rs + ' 0 1,1 0,-' + rs +
20935 'A' + rs + ',' + rs + ' 0 0,1 ' + rs + ',0Z';
20936 }
20937 },
20938 square: {
20939 n: 1,
20940 f: function(r) {
20941 var rs = d3.round(r, 2);
20942 return 'M' + rs + ',' + rs + 'H-' + rs + 'V-' + rs + 'H' + rs + 'Z';
20943 }
20944 },
20945 diamond: {
20946 n: 2,
20947 f: function(r) {
20948 var rd = d3.round(r * 1.3, 2);
20949 return 'M' + rd + ',0L0,' + rd + 'L-' + rd + ',0L0,-' + rd + 'Z';
20950 }
20951 },
20952 cross: {
20953 n: 3,
20954 f: function(r) {
20955 var rc = d3.round(r * 0.4, 2);
20956 var rc2 = d3.round(r * 1.2, 2);
20957 return 'M' + rc2 + ',' + rc + 'H' + rc + 'V' + rc2 + 'H-' + rc +
20958 'V' + rc + 'H-' + rc2 + 'V-' + rc + 'H-' + rc + 'V-' + rc2 +
20959 'H' + rc + 'V-' + rc + 'H' + rc2 + 'Z';
20960 }
20961 },
20962 x: {
20963 n: 4,
20964 f: function(r) {
20965 var rx = d3.round(r * 0.8 / Math.sqrt(2), 2);
20966 var ne = 'l' + rx + ',' + rx;
20967 var se = 'l' + rx + ',-' + rx;
20968 var sw = 'l-' + rx + ',-' + rx;
20969 var nw = 'l-' + rx + ',' + rx;
20970 return 'M0,' + rx + ne + se + sw + se + sw + nw + sw + nw + ne + nw + ne + 'Z';
20971 }
20972 },
20973 'triangle-up': {
20974 n: 5,
20975 f: function(r) {
20976 var rt = d3.round(r * 2 / Math.sqrt(3), 2);
20977 var r2 = d3.round(r / 2, 2);
20978 var rs = d3.round(r, 2);
20979 return 'M-' + rt + ',' + r2 + 'H' + rt + 'L0,-' + rs + 'Z';
20980 }
20981 },
20982 'triangle-down': {
20983 n: 6,
20984 f: function(r) {
20985 var rt = d3.round(r * 2 / Math.sqrt(3), 2);
20986 var r2 = d3.round(r / 2, 2);
20987 var rs = d3.round(r, 2);
20988 return 'M-' + rt + ',-' + r2 + 'H' + rt + 'L0,' + rs + 'Z';
20989 }
20990 },
20991 'triangle-left': {
20992 n: 7,
20993 f: function(r) {
20994 var rt = d3.round(r * 2 / Math.sqrt(3), 2);
20995 var r2 = d3.round(r / 2, 2);
20996 var rs = d3.round(r, 2);
20997 return 'M' + r2 + ',-' + rt + 'V' + rt + 'L-' + rs + ',0Z';
20998 }
20999 },
21000 'triangle-right': {
21001 n: 8,
21002 f: function(r) {
21003 var rt = d3.round(r * 2 / Math.sqrt(3), 2);
21004 var r2 = d3.round(r / 2, 2);
21005 var rs = d3.round(r, 2);
21006 return 'M-' + r2 + ',-' + rt + 'V' + rt + 'L' + rs + ',0Z';
21007 }
21008 },
21009 'triangle-ne': {
21010 n: 9,
21011 f: function(r) {
21012 var r1 = d3.round(r * 0.6, 2);
21013 var r2 = d3.round(r * 1.2, 2);
21014 return 'M-' + r2 + ',-' + r1 + 'H' + r1 + 'V' + r2 + 'Z';
21015 }
21016 },
21017 'triangle-se': {
21018 n: 10,
21019 f: function(r) {
21020 var r1 = d3.round(r * 0.6, 2);
21021 var r2 = d3.round(r * 1.2, 2);
21022 return 'M' + r1 + ',-' + r2 + 'V' + r1 + 'H-' + r2 + 'Z';
21023 }
21024 },
21025 'triangle-sw': {
21026 n: 11,
21027 f: function(r) {
21028 var r1 = d3.round(r * 0.6, 2);
21029 var r2 = d3.round(r * 1.2, 2);
21030 return 'M' + r2 + ',' + r1 + 'H-' + r1 + 'V-' + r2 + 'Z';
21031 }
21032 },
21033 'triangle-nw': {
21034 n: 12,
21035 f: function(r) {
21036 var r1 = d3.round(r * 0.6, 2);
21037 var r2 = d3.round(r * 1.2, 2);
21038 return 'M-' + r1 + ',' + r2 + 'V-' + r1 + 'H' + r2 + 'Z';
21039 }
21040 },
21041 pentagon: {
21042 n: 13,
21043 f: function(r) {
21044 var x1 = d3.round(r * 0.951, 2);
21045 var x2 = d3.round(r * 0.588, 2);
21046 var y0 = d3.round(-r, 2);
21047 var y1 = d3.round(r * -0.309, 2);
21048 var y2 = d3.round(r * 0.809, 2);
21049 return 'M' + x1 + ',' + y1 + 'L' + x2 + ',' + y2 + 'H-' + x2 +
21050 'L-' + x1 + ',' + y1 + 'L0,' + y0 + 'Z';
21051 }
21052 },
21053 hexagon: {
21054 n: 14,
21055 f: function(r) {
21056 var y0 = d3.round(r, 2);
21057 var y1 = d3.round(r / 2, 2);
21058 var x = d3.round(r * Math.sqrt(3) / 2, 2);
21059 return 'M' + x + ',-' + y1 + 'V' + y1 + 'L0,' + y0 +
21060 'L-' + x + ',' + y1 + 'V-' + y1 + 'L0,-' + y0 + 'Z';
21061 }
21062 },
21063 hexagon2: {
21064 n: 15,
21065 f: function(r) {
21066 var x0 = d3.round(r, 2);
21067 var x1 = d3.round(r / 2, 2);
21068 var y = d3.round(r * Math.sqrt(3) / 2, 2);
21069 return 'M-' + x1 + ',' + y + 'H' + x1 + 'L' + x0 +
21070 ',0L' + x1 + ',-' + y + 'H-' + x1 + 'L-' + x0 + ',0Z';
21071 }
21072 },
21073 octagon: {
21074 n: 16,
21075 f: function(r) {
21076 var a = d3.round(r * 0.924, 2);
21077 var b = d3.round(r * 0.383, 2);
21078 return 'M-' + b + ',-' + a + 'H' + b + 'L' + a + ',-' + b + 'V' + b +
21079 'L' + b + ',' + a + 'H-' + b + 'L-' + a + ',' + b + 'V-' + b + 'Z';
21080 }
21081 },
21082 star: {
21083 n: 17,
21084 f: function(r) {
21085 var rs = r * 1.4;
21086 var x1 = d3.round(rs * 0.225, 2);
21087 var x2 = d3.round(rs * 0.951, 2);
21088 var x3 = d3.round(rs * 0.363, 2);
21089 var x4 = d3.round(rs * 0.588, 2);
21090 var y0 = d3.round(-rs, 2);
21091 var y1 = d3.round(rs * -0.309, 2);
21092 var y3 = d3.round(rs * 0.118, 2);
21093 var y4 = d3.round(rs * 0.809, 2);
21094 var y5 = d3.round(rs * 0.382, 2);
21095 return 'M' + x1 + ',' + y1 + 'H' + x2 + 'L' + x3 + ',' + y3 +
21096 'L' + x4 + ',' + y4 + 'L0,' + y5 + 'L-' + x4 + ',' + y4 +
21097 'L-' + x3 + ',' + y3 + 'L-' + x2 + ',' + y1 + 'H-' + x1 +
21098 'L0,' + y0 + 'Z';
21099 }
21100 },
21101 hexagram: {
21102 n: 18,
21103 f: function(r) {
21104 var y = d3.round(r * 0.66, 2);
21105 var x1 = d3.round(r * 0.38, 2);
21106 var x2 = d3.round(r * 0.76, 2);
21107 return 'M-' + x2 + ',0l-' + x1 + ',-' + y + 'h' + x2 +
21108 'l' + x1 + ',-' + y + 'l' + x1 + ',' + y + 'h' + x2 +
21109 'l-' + x1 + ',' + y + 'l' + x1 + ',' + y + 'h-' + x2 +
21110 'l-' + x1 + ',' + y + 'l-' + x1 + ',-' + y + 'h-' + x2 + 'Z';
21111 }
21112 },
21113 'star-triangle-up': {
21114 n: 19,
21115 f: function(r) {
21116 var x = d3.round(r * Math.sqrt(3) * 0.8, 2);
21117 var y1 = d3.round(r * 0.8, 2);
21118 var y2 = d3.round(r * 1.6, 2);
21119 var rc = d3.round(r * 4, 2);
21120 var aPart = 'A ' + rc + ',' + rc + ' 0 0 1 ';
21121 return 'M-' + x + ',' + y1 + aPart + x + ',' + y1 +
21122 aPart + '0,-' + y2 + aPart + '-' + x + ',' + y1 + 'Z';
21123 }
21124 },
21125 'star-triangle-down': {
21126 n: 20,
21127 f: function(r) {
21128 var x = d3.round(r * Math.sqrt(3) * 0.8, 2);
21129 var y1 = d3.round(r * 0.8, 2);
21130 var y2 = d3.round(r * 1.6, 2);
21131 var rc = d3.round(r * 4, 2);
21132 var aPart = 'A ' + rc + ',' + rc + ' 0 0 1 ';
21133 return 'M' + x + ',-' + y1 + aPart + '-' + x + ',-' + y1 +
21134 aPart + '0,' + y2 + aPart + x + ',-' + y1 + 'Z';
21135 }
21136 },
21137 'star-square': {
21138 n: 21,
21139 f: function(r) {
21140 var rp = d3.round(r * 1.1, 2);
21141 var rc = d3.round(r * 2, 2);
21142 var aPart = 'A ' + rc + ',' + rc + ' 0 0 1 ';
21143 return 'M-' + rp + ',-' + rp + aPart + '-' + rp + ',' + rp +
21144 aPart + rp + ',' + rp + aPart + rp + ',-' + rp +
21145 aPart + '-' + rp + ',-' + rp + 'Z';
21146 }
21147 },
21148 'star-diamond': {
21149 n: 22,
21150 f: function(r) {
21151 var rp = d3.round(r * 1.4, 2);
21152 var rc = d3.round(r * 1.9, 2);
21153 var aPart = 'A ' + rc + ',' + rc + ' 0 0 1 ';
21154 return 'M-' + rp + ',0' + aPart + '0,' + rp +
21155 aPart + rp + ',0' + aPart + '0,-' + rp +
21156 aPart + '-' + rp + ',0' + 'Z';
21157 }
21158 },
21159 'diamond-tall': {
21160 n: 23,
21161 f: function(r) {
21162 var x = d3.round(r * 0.7, 2);
21163 var y = d3.round(r * 1.4, 2);
21164 return 'M0,' + y + 'L' + x + ',0L0,-' + y + 'L-' + x + ',0Z';
21165 }
21166 },
21167 'diamond-wide': {
21168 n: 24,
21169 f: function(r) {
21170 var x = d3.round(r * 1.4, 2);
21171 var y = d3.round(r * 0.7, 2);
21172 return 'M0,' + y + 'L' + x + ',0L0,-' + y + 'L-' + x + ',0Z';
21173 }
21174 },
21175 hourglass: {
21176 n: 25,
21177 f: function(r) {
21178 var rs = d3.round(r, 2);
21179 return 'M' + rs + ',' + rs + 'H-' + rs + 'L' + rs + ',-' + rs + 'H-' + rs + 'Z';
21180 },
21181 noDot: true
21182 },
21183 bowtie: {
21184 n: 26,
21185 f: function(r) {
21186 var rs = d3.round(r, 2);
21187 return 'M' + rs + ',' + rs + 'V-' + rs + 'L-' + rs + ',' + rs + 'V-' + rs + 'Z';
21188 },
21189 noDot: true
21190 },
21191 'circle-cross': {
21192 n: 27,
21193 f: function(r) {
21194 var rs = d3.round(r, 2);
21195 return 'M0,' + rs + 'V-' + rs + 'M' + rs + ',0H-' + rs +
21196 'M' + rs + ',0A' + rs + ',' + rs + ' 0 1,1 0,-' + rs +
21197 'A' + rs + ',' + rs + ' 0 0,1 ' + rs + ',0Z';
21198 },
21199 needLine: true,
21200 noDot: true
21201 },
21202 'circle-x': {
21203 n: 28,
21204 f: function(r) {
21205 var rs = d3.round(r, 2);
21206 var rc = d3.round(r / Math.sqrt(2), 2);
21207 return 'M' + rc + ',' + rc + 'L-' + rc + ',-' + rc +
21208 'M' + rc + ',-' + rc + 'L-' + rc + ',' + rc +
21209 'M' + rs + ',0A' + rs + ',' + rs + ' 0 1,1 0,-' + rs +
21210 'A' + rs + ',' + rs + ' 0 0,1 ' + rs + ',0Z';
21211 },
21212 needLine: true,
21213 noDot: true
21214 },
21215 'square-cross': {
21216 n: 29,
21217 f: function(r) {
21218 var rs = d3.round(r, 2);
21219 return 'M0,' + rs + 'V-' + rs + 'M' + rs + ',0H-' + rs +
21220 'M' + rs + ',' + rs + 'H-' + rs + 'V-' + rs + 'H' + rs + 'Z';
21221 },
21222 needLine: true,
21223 noDot: true
21224 },
21225 'square-x': {
21226 n: 30,
21227 f: function(r) {
21228 var rs = d3.round(r, 2);
21229 return 'M' + rs + ',' + rs + 'L-' + rs + ',-' + rs +
21230 'M' + rs + ',-' + rs + 'L-' + rs + ',' + rs +
21231 'M' + rs + ',' + rs + 'H-' + rs + 'V-' + rs + 'H' + rs + 'Z';
21232 },
21233 needLine: true,
21234 noDot: true
21235 },
21236 'diamond-cross': {
21237 n: 31,
21238 f: function(r) {
21239 var rd = d3.round(r * 1.3, 2);
21240 return 'M' + rd + ',0L0,' + rd + 'L-' + rd + ',0L0,-' + rd + 'Z' +
21241 'M0,-' + rd + 'V' + rd + 'M-' + rd + ',0H' + rd;
21242 },
21243 needLine: true,
21244 noDot: true
21245 },
21246 'diamond-x': {
21247 n: 32,
21248 f: function(r) {
21249 var rd = d3.round(r * 1.3, 2);
21250 var r2 = d3.round(r * 0.65, 2);
21251 return 'M' + rd + ',0L0,' + rd + 'L-' + rd + ',0L0,-' + rd + 'Z' +
21252 'M-' + r2 + ',-' + r2 + 'L' + r2 + ',' + r2 +
21253 'M-' + r2 + ',' + r2 + 'L' + r2 + ',-' + r2;
21254 },
21255 needLine: true,
21256 noDot: true
21257 },
21258 'cross-thin': {
21259 n: 33,
21260 f: function(r) {
21261 var rc = d3.round(r * 1.4, 2);
21262 return 'M0,' + rc + 'V-' + rc + 'M' + rc + ',0H-' + rc;
21263 },
21264 needLine: true,
21265 noDot: true,
21266 noFill: true
21267 },
21268 'x-thin': {
21269 n: 34,
21270 f: function(r) {
21271 var rx = d3.round(r, 2);
21272 return 'M' + rx + ',' + rx + 'L-' + rx + ',-' + rx +
21273 'M' + rx + ',-' + rx + 'L-' + rx + ',' + rx;
21274 },
21275 needLine: true,
21276 noDot: true,
21277 noFill: true
21278 },
21279 asterisk: {
21280 n: 35,
21281 f: function(r) {
21282 var rc = d3.round(r * 1.2, 2);
21283 var rs = d3.round(r * 0.85, 2);
21284 return 'M0,' + rc + 'V-' + rc + 'M' + rc + ',0H-' + rc +
21285 'M' + rs + ',' + rs + 'L-' + rs + ',-' + rs +
21286 'M' + rs + ',-' + rs + 'L-' + rs + ',' + rs;
21287 },
21288 needLine: true,
21289 noDot: true,
21290 noFill: true
21291 },
21292 hash: {
21293 n: 36,
21294 f: function(r) {
21295 var r1 = d3.round(r / 2, 2);
21296 var r2 = d3.round(r, 2);
21297 return 'M' + r1 + ',' + r2 + 'V-' + r2 +
21298 'm-' + r2 + ',0V' + r2 +
21299 'M' + r2 + ',' + r1 + 'H-' + r2 +
21300 'm0,-' + r2 + 'H' + r2;
21301 },
21302 needLine: true,
21303 noFill: true
21304 },
21305 'y-up': {
21306 n: 37,
21307 f: function(r) {
21308 var x = d3.round(r * 1.2, 2);
21309 var y0 = d3.round(r * 1.6, 2);
21310 var y1 = d3.round(r * 0.8, 2);
21311 return 'M-' + x + ',' + y1 + 'L0,0M' + x + ',' + y1 + 'L0,0M0,-' + y0 + 'L0,0';
21312 },
21313 needLine: true,
21314 noDot: true,
21315 noFill: true
21316 },
21317 'y-down': {
21318 n: 38,
21319 f: function(r) {
21320 var x = d3.round(r * 1.2, 2);
21321 var y0 = d3.round(r * 1.6, 2);
21322 var y1 = d3.round(r * 0.8, 2);
21323 return 'M-' + x + ',-' + y1 + 'L0,0M' + x + ',-' + y1 + 'L0,0M0,' + y0 + 'L0,0';
21324 },
21325 needLine: true,
21326 noDot: true,
21327 noFill: true
21328 },
21329 'y-left': {
21330 n: 39,
21331 f: function(r) {
21332 var y = d3.round(r * 1.2, 2);
21333 var x0 = d3.round(r * 1.6, 2);
21334 var x1 = d3.round(r * 0.8, 2);
21335 return 'M' + x1 + ',' + y + 'L0,0M' + x1 + ',-' + y + 'L0,0M-' + x0 + ',0L0,0';
21336 },
21337 needLine: true,
21338 noDot: true,
21339 noFill: true
21340 },
21341 'y-right': {
21342 n: 40,
21343 f: function(r) {
21344 var y = d3.round(r * 1.2, 2);
21345 var x0 = d3.round(r * 1.6, 2);
21346 var x1 = d3.round(r * 0.8, 2);
21347 return 'M-' + x1 + ',' + y + 'L0,0M-' + x1 + ',-' + y + 'L0,0M' + x0 + ',0L0,0';
21348 },
21349 needLine: true,
21350 noDot: true,
21351 noFill: true
21352 },
21353 'line-ew': {
21354 n: 41,
21355 f: function(r) {
21356 var rc = d3.round(r * 1.4, 2);
21357 return 'M' + rc + ',0H-' + rc;
21358 },
21359 needLine: true,
21360 noDot: true,
21361 noFill: true
21362 },
21363 'line-ns': {
21364 n: 42,
21365 f: function(r) {
21366 var rc = d3.round(r * 1.4, 2);
21367 return 'M0,' + rc + 'V-' + rc;
21368 },
21369 needLine: true,
21370 noDot: true,
21371 noFill: true
21372 },
21373 'line-ne': {
21374 n: 43,
21375 f: function(r) {
21376 var rx = d3.round(r, 2);
21377 return 'M' + rx + ',-' + rx + 'L-' + rx + ',' + rx;
21378 },
21379 needLine: true,
21380 noDot: true,
21381 noFill: true
21382 },
21383 'line-nw': {
21384 n: 44,
21385 f: function(r) {
21386 var rx = d3.round(r, 2);
21387 return 'M' + rx + ',' + rx + 'L-' + rx + ',-' + rx;
21388 },
21389 needLine: true,
21390 noDot: true,
21391 noFill: true
21392 }
21393};
21394
21395},{"d3":16}],76:[function(_dereq_,module,exports){
21396/**
21397* Copyright 2012-2020, Plotly, Inc.
21398* All rights reserved.
21399*
21400* This source code is licensed under the MIT license found in the
21401* LICENSE file in the root directory of this source tree.
21402*/
21403
21404'use strict';
21405
21406
21407module.exports = {
21408 visible: {
21409 valType: 'boolean',
21410
21411 editType: 'calc',
21412
21413 },
21414 type: {
21415 valType: 'enumerated',
21416 values: ['percent', 'constant', 'sqrt', 'data'],
21417
21418 editType: 'calc',
21419
21420 },
21421 symmetric: {
21422 valType: 'boolean',
21423
21424 editType: 'calc',
21425
21426 },
21427 array: {
21428 valType: 'data_array',
21429 editType: 'calc',
21430
21431 },
21432 arrayminus: {
21433 valType: 'data_array',
21434 editType: 'calc',
21435
21436 },
21437 value: {
21438 valType: 'number',
21439 min: 0,
21440 dflt: 10,
21441
21442 editType: 'calc',
21443
21444 },
21445 valueminus: {
21446 valType: 'number',
21447 min: 0,
21448 dflt: 10,
21449
21450 editType: 'calc',
21451
21452 },
21453 traceref: {
21454 valType: 'integer',
21455 min: 0,
21456 dflt: 0,
21457
21458 editType: 'style'
21459 },
21460 tracerefminus: {
21461 valType: 'integer',
21462 min: 0,
21463 dflt: 0,
21464
21465 editType: 'style'
21466 },
21467 copy_ystyle: {
21468 valType: 'boolean',
21469
21470 editType: 'plot'
21471 },
21472 copy_zstyle: {
21473 valType: 'boolean',
21474
21475 editType: 'style'
21476 },
21477 color: {
21478 valType: 'color',
21479
21480 editType: 'style',
21481
21482 },
21483 thickness: {
21484 valType: 'number',
21485 min: 0,
21486 dflt: 2,
21487
21488 editType: 'style',
21489
21490 },
21491 width: {
21492 valType: 'number',
21493 min: 0,
21494
21495 editType: 'plot',
21496
21497 },
21498 editType: 'calc',
21499
21500 _deprecated: {
21501 opacity: {
21502 valType: 'number',
21503
21504 editType: 'style',
21505
21506 }
21507 }
21508};
21509
21510},{}],77:[function(_dereq_,module,exports){
21511/**
21512* Copyright 2012-2020, Plotly, Inc.
21513* All rights reserved.
21514*
21515* This source code is licensed under the MIT license found in the
21516* LICENSE file in the root directory of this source tree.
21517*/
21518
21519'use strict';
21520
21521var isNumeric = _dereq_('fast-isnumeric');
21522
21523var Registry = _dereq_('../../registry');
21524var Axes = _dereq_('../../plots/cartesian/axes');
21525var Lib = _dereq_('../../lib');
21526
21527var makeComputeError = _dereq_('./compute_error');
21528
21529module.exports = function calc(gd) {
21530 var calcdata = gd.calcdata;
21531
21532 for(var i = 0; i < calcdata.length; i++) {
21533 var calcTrace = calcdata[i];
21534 var trace = calcTrace[0].trace;
21535
21536 if(trace.visible === true && Registry.traceIs(trace, 'errorBarsOK')) {
21537 var xa = Axes.getFromId(gd, trace.xaxis);
21538 var ya = Axes.getFromId(gd, trace.yaxis);
21539 calcOneAxis(calcTrace, trace, xa, 'x');
21540 calcOneAxis(calcTrace, trace, ya, 'y');
21541 }
21542 }
21543};
21544
21545function calcOneAxis(calcTrace, trace, axis, coord) {
21546 var opts = trace['error_' + coord] || {};
21547 var isVisible = (opts.visible && ['linear', 'log'].indexOf(axis.type) !== -1);
21548 var vals = [];
21549
21550 if(!isVisible) return;
21551
21552 var computeError = makeComputeError(opts);
21553
21554 for(var i = 0; i < calcTrace.length; i++) {
21555 var calcPt = calcTrace[i];
21556
21557 var iIn = calcPt.i;
21558
21559 // for types that don't include `i` in each calcdata point
21560 if(iIn === undefined) iIn = i;
21561
21562 // for stacked area inserted points
21563 // TODO: errorbars have been tested cursorily with stacked area,
21564 // but not thoroughly. It's not even really clear what you want to do:
21565 // Should it just be calculated based on that trace's size data?
21566 // Should you add errors from below in quadrature?
21567 // And what about normalization, where in principle the errors shrink
21568 // again when you get up to the top end?
21569 // One option would be to forbid errorbars with stacking until we
21570 // decide how to handle these questions.
21571 else if(iIn === null) continue;
21572
21573 var calcCoord = calcPt[coord];
21574
21575 if(!isNumeric(axis.c2l(calcCoord))) continue;
21576
21577 var errors = computeError(calcCoord, iIn);
21578 if(isNumeric(errors[0]) && isNumeric(errors[1])) {
21579 var shoe = calcPt[coord + 's'] = calcCoord - errors[0];
21580 var hat = calcPt[coord + 'h'] = calcCoord + errors[1];
21581 vals.push(shoe, hat);
21582 }
21583 }
21584
21585 var axId = axis._id;
21586 var baseExtremes = trace._extremes[axId];
21587 var extremes = Axes.findExtremes(
21588 axis,
21589 vals,
21590 Lib.extendFlat({tozero: baseExtremes.opts.tozero}, {padded: true})
21591 );
21592 baseExtremes.min = baseExtremes.min.concat(extremes.min);
21593 baseExtremes.max = baseExtremes.max.concat(extremes.max);
21594}
21595
21596},{"../../lib":178,"../../plots/cartesian/axes":222,"../../registry":269,"./compute_error":78,"fast-isnumeric":18}],78:[function(_dereq_,module,exports){
21597/**
21598* Copyright 2012-2020, Plotly, Inc.
21599* All rights reserved.
21600*
21601* This source code is licensed under the MIT license found in the
21602* LICENSE file in the root directory of this source tree.
21603*/
21604
21605
21606'use strict';
21607
21608
21609/**
21610 * Error bar computing function generator
21611 *
21612 * N.B. The generated function does not clean the dataPt entries. Non-numeric
21613 * entries result in undefined error magnitudes.
21614 *
21615 * @param {object} opts error bar attributes
21616 *
21617 * @return {function} :
21618 * @param {numeric} dataPt data point from where to compute the error magnitude
21619 * @param {number} index index of dataPt in its corresponding data array
21620 * @return {array}
21621 * - error[0] : error magnitude in the negative direction
21622 * - error[1] : " " " " positive "
21623 */
21624module.exports = function makeComputeError(opts) {
21625 var type = opts.type;
21626 var symmetric = opts.symmetric;
21627
21628 if(type === 'data') {
21629 var array = opts.array || [];
21630
21631 if(symmetric) {
21632 return function computeError(dataPt, index) {
21633 var val = +(array[index]);
21634 return [val, val];
21635 };
21636 } else {
21637 var arrayminus = opts.arrayminus || [];
21638 return function computeError(dataPt, index) {
21639 var val = +array[index];
21640 var valMinus = +arrayminus[index];
21641 // in case one is present and the other is missing, fill in 0
21642 // so we still see the present one. Mostly useful during manual
21643 // data entry.
21644 if(!isNaN(val) || !isNaN(valMinus)) {
21645 return [valMinus || 0, val || 0];
21646 }
21647 return [NaN, NaN];
21648 };
21649 }
21650 } else {
21651 var computeErrorValue = makeComputeErrorValue(type, opts.value);
21652 var computeErrorValueMinus = makeComputeErrorValue(type, opts.valueminus);
21653
21654 if(symmetric || opts.valueminus === undefined) {
21655 return function computeError(dataPt) {
21656 var val = computeErrorValue(dataPt);
21657 return [val, val];
21658 };
21659 } else {
21660 return function computeError(dataPt) {
21661 return [
21662 computeErrorValueMinus(dataPt),
21663 computeErrorValue(dataPt)
21664 ];
21665 };
21666 }
21667 }
21668};
21669
21670/**
21671 * Compute error bar magnitude (for all types except data)
21672 *
21673 * @param {string} type error bar type
21674 * @param {numeric} value error bar value
21675 *
21676 * @return {function} :
21677 * @param {numeric} dataPt
21678 */
21679function makeComputeErrorValue(type, value) {
21680 if(type === 'percent') {
21681 return function(dataPt) {
21682 return Math.abs(dataPt * value / 100);
21683 };
21684 }
21685 if(type === 'constant') {
21686 return function() {
21687 return Math.abs(value);
21688 };
21689 }
21690 if(type === 'sqrt') {
21691 return function(dataPt) {
21692 return Math.sqrt(Math.abs(dataPt));
21693 };
21694 }
21695}
21696
21697},{}],79:[function(_dereq_,module,exports){
21698/**
21699* Copyright 2012-2020, Plotly, Inc.
21700* All rights reserved.
21701*
21702* This source code is licensed under the MIT license found in the
21703* LICENSE file in the root directory of this source tree.
21704*/
21705
21706'use strict';
21707
21708var isNumeric = _dereq_('fast-isnumeric');
21709
21710var Registry = _dereq_('../../registry');
21711var Lib = _dereq_('../../lib');
21712var Template = _dereq_('../../plot_api/plot_template');
21713
21714var attributes = _dereq_('./attributes');
21715
21716
21717module.exports = function(traceIn, traceOut, defaultColor, opts) {
21718 var objName = 'error_' + opts.axis;
21719 var containerOut = Template.newContainer(traceOut, objName);
21720 var containerIn = traceIn[objName] || {};
21721
21722 function coerce(attr, dflt) {
21723 return Lib.coerce(containerIn, containerOut, attributes, attr, dflt);
21724 }
21725
21726 var hasErrorBars = (
21727 containerIn.array !== undefined ||
21728 containerIn.value !== undefined ||
21729 containerIn.type === 'sqrt'
21730 );
21731
21732 var visible = coerce('visible', hasErrorBars);
21733
21734 if(visible === false) return;
21735
21736 var type = coerce('type', 'array' in containerIn ? 'data' : 'percent');
21737 var symmetric = true;
21738
21739 if(type !== 'sqrt') {
21740 symmetric = coerce('symmetric',
21741 !((type === 'data' ? 'arrayminus' : 'valueminus') in containerIn));
21742 }
21743
21744 if(type === 'data') {
21745 coerce('array');
21746 coerce('traceref');
21747 if(!symmetric) {
21748 coerce('arrayminus');
21749 coerce('tracerefminus');
21750 }
21751 } else if(type === 'percent' || type === 'constant') {
21752 coerce('value');
21753 if(!symmetric) coerce('valueminus');
21754 }
21755
21756 var copyAttr = 'copy_' + opts.inherit + 'style';
21757 if(opts.inherit) {
21758 var inheritObj = traceOut['error_' + opts.inherit];
21759 if((inheritObj || {}).visible) {
21760 coerce(copyAttr, !(containerIn.color ||
21761 isNumeric(containerIn.thickness) ||
21762 isNumeric(containerIn.width)));
21763 }
21764 }
21765 if(!opts.inherit || !containerOut[copyAttr]) {
21766 coerce('color', defaultColor);
21767 coerce('thickness');
21768 coerce('width', Registry.traceIs(traceOut, 'gl3d') ? 0 : 4);
21769 }
21770};
21771
21772},{"../../lib":178,"../../plot_api/plot_template":212,"../../registry":269,"./attributes":76,"fast-isnumeric":18}],80:[function(_dereq_,module,exports){
21773/**
21774* Copyright 2012-2020, Plotly, Inc.
21775* All rights reserved.
21776*
21777* This source code is licensed under the MIT license found in the
21778* LICENSE file in the root directory of this source tree.
21779*/
21780
21781'use strict';
21782
21783var Lib = _dereq_('../../lib');
21784var overrideAll = _dereq_('../../plot_api/edit_types').overrideAll;
21785
21786var attributes = _dereq_('./attributes');
21787
21788var xyAttrs = {
21789 error_x: Lib.extendFlat({}, attributes),
21790 error_y: Lib.extendFlat({}, attributes)
21791};
21792delete xyAttrs.error_x.copy_zstyle;
21793delete xyAttrs.error_y.copy_zstyle;
21794delete xyAttrs.error_y.copy_ystyle;
21795
21796var xyzAttrs = {
21797 error_x: Lib.extendFlat({}, attributes),
21798 error_y: Lib.extendFlat({}, attributes),
21799 error_z: Lib.extendFlat({}, attributes)
21800};
21801delete xyzAttrs.error_x.copy_ystyle;
21802delete xyzAttrs.error_y.copy_ystyle;
21803delete xyzAttrs.error_z.copy_ystyle;
21804delete xyzAttrs.error_z.copy_zstyle;
21805
21806module.exports = {
21807 moduleType: 'component',
21808 name: 'errorbars',
21809
21810 schema: {
21811 traces: {
21812 scatter: xyAttrs,
21813 bar: xyAttrs,
21814 histogram: xyAttrs,
21815 scatter3d: overrideAll(xyzAttrs, 'calc', 'nested'),
21816 scattergl: overrideAll(xyAttrs, 'calc', 'nested')
21817 }
21818 },
21819
21820 supplyDefaults: _dereq_('./defaults'),
21821
21822 calc: _dereq_('./calc'),
21823 makeComputeError: _dereq_('./compute_error'),
21824
21825 plot: _dereq_('./plot'),
21826 style: _dereq_('./style'),
21827 hoverInfo: hoverInfo
21828};
21829
21830function hoverInfo(calcPoint, trace, hoverPoint) {
21831 if((trace.error_y || {}).visible) {
21832 hoverPoint.yerr = calcPoint.yh - calcPoint.y;
21833 if(!trace.error_y.symmetric) hoverPoint.yerrneg = calcPoint.y - calcPoint.ys;
21834 }
21835 if((trace.error_x || {}).visible) {
21836 hoverPoint.xerr = calcPoint.xh - calcPoint.x;
21837 if(!trace.error_x.symmetric) hoverPoint.xerrneg = calcPoint.x - calcPoint.xs;
21838 }
21839}
21840
21841},{"../../lib":178,"../../plot_api/edit_types":205,"./attributes":76,"./calc":77,"./compute_error":78,"./defaults":79,"./plot":81,"./style":82}],81:[function(_dereq_,module,exports){
21842/**
21843* Copyright 2012-2020, Plotly, Inc.
21844* All rights reserved.
21845*
21846* This source code is licensed under the MIT license found in the
21847* LICENSE file in the root directory of this source tree.
21848*/
21849
21850
21851'use strict';
21852
21853var d3 = _dereq_('d3');
21854var isNumeric = _dereq_('fast-isnumeric');
21855
21856var Drawing = _dereq_('../drawing');
21857var subTypes = _dereq_('../../traces/scatter/subtypes');
21858
21859module.exports = function plot(gd, traces, plotinfo, transitionOpts) {
21860 var isNew;
21861
21862 var xa = plotinfo.xaxis;
21863 var ya = plotinfo.yaxis;
21864
21865 var hasAnimation = transitionOpts && transitionOpts.duration > 0;
21866
21867 traces.each(function(d) {
21868 var trace = d[0].trace;
21869 // || {} is in case the trace (specifically scatterternary)
21870 // doesn't support error bars at all, but does go through
21871 // the scatter.plot mechanics, which calls ErrorBars.plot
21872 // internally
21873 var xObj = trace.error_x || {};
21874 var yObj = trace.error_y || {};
21875
21876 var keyFunc;
21877
21878 if(trace.ids) {
21879 keyFunc = function(d) {return d.id;};
21880 }
21881
21882 var sparse = (
21883 subTypes.hasMarkers(trace) &&
21884 trace.marker.maxdisplayed > 0
21885 );
21886
21887 if(!yObj.visible && !xObj.visible) d = [];
21888
21889 var errorbars = d3.select(this).selectAll('g.errorbar')
21890 .data(d, keyFunc);
21891
21892 errorbars.exit().remove();
21893
21894 if(!d.length) return;
21895
21896 if(!xObj.visible) errorbars.selectAll('path.xerror').remove();
21897 if(!yObj.visible) errorbars.selectAll('path.yerror').remove();
21898
21899 errorbars.style('opacity', 1);
21900
21901 var enter = errorbars.enter().append('g')
21902 .classed('errorbar', true);
21903
21904 if(hasAnimation) {
21905 enter.style('opacity', 0).transition()
21906 .duration(transitionOpts.duration)
21907 .style('opacity', 1);
21908 }
21909
21910 Drawing.setClipUrl(errorbars, plotinfo.layerClipId, gd);
21911
21912 errorbars.each(function(d) {
21913 var errorbar = d3.select(this);
21914 var coords = errorCoords(d, xa, ya);
21915
21916 if(sparse && !d.vis) return;
21917
21918 var path;
21919
21920 var yerror = errorbar.select('path.yerror');
21921 if(yObj.visible && isNumeric(coords.x) &&
21922 isNumeric(coords.yh) &&
21923 isNumeric(coords.ys)) {
21924 var yw = yObj.width;
21925
21926 path = 'M' + (coords.x - yw) + ',' +
21927 coords.yh + 'h' + (2 * yw) + // hat
21928 'm-' + yw + ',0V' + coords.ys; // bar
21929
21930
21931 if(!coords.noYS) path += 'm-' + yw + ',0h' + (2 * yw); // shoe
21932
21933 isNew = !yerror.size();
21934
21935 if(isNew) {
21936 yerror = errorbar.append('path')
21937 .style('vector-effect', 'non-scaling-stroke')
21938 .classed('yerror', true);
21939 } else if(hasAnimation) {
21940 yerror = yerror
21941 .transition()
21942 .duration(transitionOpts.duration)
21943 .ease(transitionOpts.easing);
21944 }
21945
21946 yerror.attr('d', path);
21947 } else yerror.remove();
21948
21949 var xerror = errorbar.select('path.xerror');
21950 if(xObj.visible && isNumeric(coords.y) &&
21951 isNumeric(coords.xh) &&
21952 isNumeric(coords.xs)) {
21953 var xw = (xObj.copy_ystyle ? yObj : xObj).width;
21954
21955 path = 'M' + coords.xh + ',' +
21956 (coords.y - xw) + 'v' + (2 * xw) + // hat
21957 'm0,-' + xw + 'H' + coords.xs; // bar
21958
21959 if(!coords.noXS) path += 'm0,-' + xw + 'v' + (2 * xw); // shoe
21960
21961 isNew = !xerror.size();
21962
21963 if(isNew) {
21964 xerror = errorbar.append('path')
21965 .style('vector-effect', 'non-scaling-stroke')
21966 .classed('xerror', true);
21967 } else if(hasAnimation) {
21968 xerror = xerror
21969 .transition()
21970 .duration(transitionOpts.duration)
21971 .ease(transitionOpts.easing);
21972 }
21973
21974 xerror.attr('d', path);
21975 } else xerror.remove();
21976 });
21977 });
21978};
21979
21980// compute the coordinates of the error-bar objects
21981function errorCoords(d, xa, ya) {
21982 var out = {
21983 x: xa.c2p(d.x),
21984 y: ya.c2p(d.y)
21985 };
21986
21987 // calculate the error bar size and hat and shoe locations
21988 if(d.yh !== undefined) {
21989 out.yh = ya.c2p(d.yh);
21990 out.ys = ya.c2p(d.ys);
21991
21992 // if the shoes go off-scale (ie log scale, error bars past zero)
21993 // clip the bar and hide the shoes
21994 if(!isNumeric(out.ys)) {
21995 out.noYS = true;
21996 out.ys = ya.c2p(d.ys, true);
21997 }
21998 }
21999
22000 if(d.xh !== undefined) {
22001 out.xh = xa.c2p(d.xh);
22002 out.xs = xa.c2p(d.xs);
22003
22004 if(!isNumeric(out.xs)) {
22005 out.noXS = true;
22006 out.xs = xa.c2p(d.xs, true);
22007 }
22008 }
22009
22010 return out;
22011}
22012
22013},{"../../traces/scatter/subtypes":413,"../drawing":74,"d3":16,"fast-isnumeric":18}],82:[function(_dereq_,module,exports){
22014/**
22015* Copyright 2012-2020, Plotly, Inc.
22016* All rights reserved.
22017*
22018* This source code is licensed under the MIT license found in the
22019* LICENSE file in the root directory of this source tree.
22020*/
22021
22022
22023'use strict';
22024
22025var d3 = _dereq_('d3');
22026
22027var Color = _dereq_('../color');
22028
22029
22030module.exports = function style(traces) {
22031 traces.each(function(d) {
22032 var trace = d[0].trace;
22033 var yObj = trace.error_y || {};
22034 var xObj = trace.error_x || {};
22035
22036 var s = d3.select(this);
22037
22038 s.selectAll('path.yerror')
22039 .style('stroke-width', yObj.thickness + 'px')
22040 .call(Color.stroke, yObj.color);
22041
22042 if(xObj.copy_ystyle) xObj = yObj;
22043
22044 s.selectAll('path.xerror')
22045 .style('stroke-width', xObj.thickness + 'px')
22046 .call(Color.stroke, xObj.color);
22047 });
22048};
22049
22050},{"../color":52,"d3":16}],83:[function(_dereq_,module,exports){
22051/**
22052* Copyright 2012-2020, Plotly, Inc.
22053* All rights reserved.
22054*
22055* This source code is licensed under the MIT license found in the
22056* LICENSE file in the root directory of this source tree.
22057*/
22058
22059'use strict';
22060
22061var fontAttrs = _dereq_('../../plots/font_attributes');
22062var hoverLabelAttrs = _dereq_('./layout_attributes').hoverlabel;
22063var extendFlat = _dereq_('../../lib/extend').extendFlat;
22064
22065module.exports = {
22066 hoverlabel: {
22067 bgcolor: extendFlat({}, hoverLabelAttrs.bgcolor, {
22068 arrayOk: true,
22069
22070 }),
22071 bordercolor: extendFlat({}, hoverLabelAttrs.bordercolor, {
22072 arrayOk: true,
22073
22074 }),
22075 font: fontAttrs({
22076 arrayOk: true,
22077 editType: 'none',
22078
22079 }),
22080 align: extendFlat({}, hoverLabelAttrs.align, {arrayOk: true}),
22081 namelength: extendFlat({}, hoverLabelAttrs.namelength, {arrayOk: true}),
22082 editType: 'none'
22083 }
22084};
22085
22086},{"../../lib/extend":173,"../../plots/font_attributes":250,"./layout_attributes":93}],84:[function(_dereq_,module,exports){
22087/**
22088* Copyright 2012-2020, Plotly, Inc.
22089* All rights reserved.
22090*
22091* This source code is licensed under the MIT license found in the
22092* LICENSE file in the root directory of this source tree.
22093*/
22094
22095'use strict';
22096
22097var Lib = _dereq_('../../lib');
22098var Registry = _dereq_('../../registry');
22099
22100module.exports = function calc(gd) {
22101 var calcdata = gd.calcdata;
22102 var fullLayout = gd._fullLayout;
22103
22104 function makeCoerceHoverInfo(trace) {
22105 return function(val) {
22106 return Lib.coerceHoverinfo({hoverinfo: val}, {_module: trace._module}, fullLayout);
22107 };
22108 }
22109
22110 for(var i = 0; i < calcdata.length; i++) {
22111 var cd = calcdata[i];
22112 var trace = cd[0].trace;
22113
22114 // don't include hover calc fields for pie traces
22115 // as calcdata items might be sorted by value and
22116 // won't match the data array order.
22117 if(Registry.traceIs(trace, 'pie-like')) continue;
22118
22119 var fillFn = Registry.traceIs(trace, '2dMap') ? paste : Lib.fillArray;
22120
22121 fillFn(trace.hoverinfo, cd, 'hi', makeCoerceHoverInfo(trace));
22122
22123 if(trace.hovertemplate) fillFn(trace.hovertemplate, cd, 'ht');
22124
22125 if(!trace.hoverlabel) continue;
22126
22127 fillFn(trace.hoverlabel.bgcolor, cd, 'hbg');
22128 fillFn(trace.hoverlabel.bordercolor, cd, 'hbc');
22129 fillFn(trace.hoverlabel.font.size, cd, 'hts');
22130 fillFn(trace.hoverlabel.font.color, cd, 'htc');
22131 fillFn(trace.hoverlabel.font.family, cd, 'htf');
22132 fillFn(trace.hoverlabel.namelength, cd, 'hnl');
22133 fillFn(trace.hoverlabel.align, cd, 'hta');
22134 }
22135};
22136
22137function paste(traceAttr, cd, cdAttr, fn) {
22138 fn = fn || Lib.identity;
22139
22140 if(Array.isArray(traceAttr)) {
22141 cd[0][cdAttr] = fn(traceAttr);
22142 }
22143}
22144
22145},{"../../lib":178,"../../registry":269}],85:[function(_dereq_,module,exports){
22146/**
22147* Copyright 2012-2020, Plotly, Inc.
22148* All rights reserved.
22149*
22150* This source code is licensed under the MIT license found in the
22151* LICENSE file in the root directory of this source tree.
22152*/
22153
22154'use strict';
22155
22156var Registry = _dereq_('../../registry');
22157var hover = _dereq_('./hover').hover;
22158
22159module.exports = function click(gd, evt, subplot) {
22160 var annotationsDone = Registry.getComponentMethod('annotations', 'onClick')(gd, gd._hoverdata);
22161
22162 // fallback to fail-safe in case the plot type's hover method doesn't pass the subplot.
22163 // Ternary, for example, didn't, but it was caught because tested.
22164 if(subplot !== undefined) {
22165 // The true flag at the end causes it to re-run the hover computation to figure out *which*
22166 // point is being clicked. Without this, clicking is somewhat unreliable.
22167 hover(gd, evt, subplot, true);
22168 }
22169
22170 function emitClick() { gd.emit('plotly_click', {points: gd._hoverdata, event: evt}); }
22171
22172 if(gd._hoverdata && evt && evt.target) {
22173 if(annotationsDone && annotationsDone.then) {
22174 annotationsDone.then(emitClick);
22175 } else emitClick();
22176
22177 // why do we get a double event without this???
22178 if(evt.stopImmediatePropagation) evt.stopImmediatePropagation();
22179 }
22180};
22181
22182},{"../../registry":269,"./hover":89}],86:[function(_dereq_,module,exports){
22183/**
22184* Copyright 2012-2020, Plotly, Inc.
22185* All rights reserved.
22186*
22187* This source code is licensed under the MIT license found in the
22188* LICENSE file in the root directory of this source tree.
22189*/
22190
22191'use strict';
22192
22193module.exports = {
22194 // hover labels for multiple horizontal bars get tilted by this angle
22195 YANGLE: 60,
22196
22197 // size and display constants for hover text
22198
22199 // pixel size of hover arrows
22200 HOVERARROWSIZE: 6,
22201 // pixels padding around text
22202 HOVERTEXTPAD: 3,
22203 // hover font
22204 HOVERFONTSIZE: 13,
22205 HOVERFONT: 'Arial, sans-serif',
22206
22207 // minimum time (msec) between hover calls
22208 HOVERMINTIME: 50,
22209
22210 // ID suffix (with fullLayout._uid) for hover events in the throttle cache
22211 HOVERID: '-hover'
22212};
22213
22214},{}],87:[function(_dereq_,module,exports){
22215/**
22216* Copyright 2012-2020, Plotly, Inc.
22217* All rights reserved.
22218*
22219* This source code is licensed under the MIT license found in the
22220* LICENSE file in the root directory of this source tree.
22221*/
22222
22223'use strict';
22224
22225var Lib = _dereq_('../../lib');
22226var attributes = _dereq_('./attributes');
22227var handleHoverLabelDefaults = _dereq_('./hoverlabel_defaults');
22228
22229module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
22230 function coerce(attr, dflt) {
22231 return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
22232 }
22233
22234 var opts = Lib.extendFlat({}, layout.hoverlabel);
22235 if(traceOut.hovertemplate) opts.namelength = -1;
22236
22237 handleHoverLabelDefaults(traceIn, traceOut, coerce, opts);
22238};
22239
22240},{"../../lib":178,"./attributes":83,"./hoverlabel_defaults":90}],88:[function(_dereq_,module,exports){
22241/**
22242* Copyright 2012-2020, Plotly, Inc.
22243* All rights reserved.
22244*
22245* This source code is licensed under the MIT license found in the
22246* LICENSE file in the root directory of this source tree.
22247*/
22248
22249'use strict';
22250
22251var Lib = _dereq_('../../lib');
22252
22253// look for either subplot or xaxis and yaxis attributes
22254// does not handle splom case
22255exports.getSubplot = function(trace) {
22256 return trace.subplot || (trace.xaxis + trace.yaxis) || trace.geo;
22257};
22258
22259// is trace in given list of subplots?
22260// does handle splom case
22261exports.isTraceInSubplots = function(trace, subplots) {
22262 if(trace.type === 'splom') {
22263 var xaxes = trace.xaxes || [];
22264 var yaxes = trace.yaxes || [];
22265 for(var i = 0; i < xaxes.length; i++) {
22266 for(var j = 0; j < yaxes.length; j++) {
22267 if(subplots.indexOf(xaxes[i] + yaxes[j]) !== -1) {
22268 return true;
22269 }
22270 }
22271 }
22272 return false;
22273 }
22274
22275 return subplots.indexOf(exports.getSubplot(trace)) !== -1;
22276};
22277
22278// convenience functions for mapping all relevant axes
22279exports.flat = function(subplots, v) {
22280 var out = new Array(subplots.length);
22281 for(var i = 0; i < subplots.length; i++) {
22282 out[i] = v;
22283 }
22284 return out;
22285};
22286
22287exports.p2c = function(axArray, v) {
22288 var out = new Array(axArray.length);
22289 for(var i = 0; i < axArray.length; i++) {
22290 out[i] = axArray[i].p2c(v);
22291 }
22292 return out;
22293};
22294
22295exports.getDistanceFunction = function(mode, dx, dy, dxy) {
22296 if(mode === 'closest') return dxy || exports.quadrature(dx, dy);
22297 return mode.charAt(0) === 'x' ? dx : dy;
22298};
22299
22300exports.getClosest = function(cd, distfn, pointData) {
22301 // do we already have a point number? (array mode only)
22302 if(pointData.index !== false) {
22303 if(pointData.index >= 0 && pointData.index < cd.length) {
22304 pointData.distance = 0;
22305 } else pointData.index = false;
22306 } else {
22307 // apply the distance function to each data point
22308 // this is the longest loop... if this bogs down, we may need
22309 // to create pre-sorted data (by x or y), not sure how to
22310 // do this for 'closest'
22311 for(var i = 0; i < cd.length; i++) {
22312 var newDistance = distfn(cd[i]);
22313 if(newDistance <= pointData.distance) {
22314 pointData.index = i;
22315 pointData.distance = newDistance;
22316 }
22317 }
22318 }
22319 return pointData;
22320};
22321
22322/*
22323 * pseudo-distance function for hover effects on areas: inside the region
22324 * distance is finite (`passVal`), outside it's Infinity.
22325 *
22326 * @param {number} v0: signed difference between the current position and the left edge
22327 * @param {number} v1: signed difference between the current position and the right edge
22328 * @param {number} passVal: the value to return on success
22329 */
22330exports.inbox = function(v0, v1, passVal) {
22331 return (v0 * v1 < 0 || v0 === 0) ? passVal : Infinity;
22332};
22333
22334exports.quadrature = function(dx, dy) {
22335 return function(di) {
22336 var x = dx(di);
22337 var y = dy(di);
22338 return Math.sqrt(x * x + y * y);
22339 };
22340};
22341
22342/** Fill event data point object for hover and selection.
22343 * Invokes _module.eventData if present.
22344 *
22345 * N.B. note that point 'index' corresponds to input data array index
22346 * whereas 'number' is its post-transform version.
22347 *
22348 * If the hovered/selected pt corresponds to an multiple input points
22349 * (e.g. for histogram and transformed traces), 'pointNumbers` and 'pointIndices'
22350 * are include in the event data.
22351 *
22352 * @param {object} pt
22353 * @param {object} trace
22354 * @param {object} cd
22355 * @return {object}
22356 */
22357exports.makeEventData = function(pt, trace, cd) {
22358 // hover uses 'index', select uses 'pointNumber'
22359 var pointNumber = 'index' in pt ? pt.index : pt.pointNumber;
22360
22361 var out = {
22362 data: trace._input,
22363 fullData: trace,
22364 curveNumber: trace.index,
22365 pointNumber: pointNumber
22366 };
22367
22368 if(trace._indexToPoints) {
22369 var pointIndices = trace._indexToPoints[pointNumber];
22370
22371 if(pointIndices.length === 1) {
22372 out.pointIndex = pointIndices[0];
22373 } else {
22374 out.pointIndices = pointIndices;
22375 }
22376 } else {
22377 out.pointIndex = pointNumber;
22378 }
22379
22380 if(trace._module.eventData) {
22381 out = trace._module.eventData(out, pt, trace, cd, pointNumber);
22382 } else {
22383 if('xVal' in pt) out.x = pt.xVal;
22384 else if('x' in pt) out.x = pt.x;
22385
22386 if('yVal' in pt) out.y = pt.yVal;
22387 else if('y' in pt) out.y = pt.y;
22388
22389 if(pt.xa) out.xaxis = pt.xa;
22390 if(pt.ya) out.yaxis = pt.ya;
22391 if(pt.zLabelVal !== undefined) out.z = pt.zLabelVal;
22392 }
22393
22394 exports.appendArrayPointValue(out, trace, pointNumber);
22395
22396 return out;
22397};
22398
22399/** Appends values inside array attributes corresponding to given point number
22400 *
22401 * @param {object} pointData : point data object (gets mutated here)
22402 * @param {object} trace : full trace object
22403 * @param {number|Array(number)} pointNumber : point number. May be a length-2 array
22404 * [row, col] to dig into 2D arrays
22405 */
22406exports.appendArrayPointValue = function(pointData, trace, pointNumber) {
22407 var arrayAttrs = trace._arrayAttrs;
22408
22409 if(!arrayAttrs) {
22410 return;
22411 }
22412
22413 for(var i = 0; i < arrayAttrs.length; i++) {
22414 var astr = arrayAttrs[i];
22415 var key = getPointKey(astr);
22416
22417 if(pointData[key] === undefined) {
22418 var val = Lib.nestedProperty(trace, astr).get();
22419 var pointVal = getPointData(val, pointNumber);
22420
22421 if(pointVal !== undefined) pointData[key] = pointVal;
22422 }
22423 }
22424};
22425
22426/**
22427 * Appends values inside array attributes corresponding to given point number array
22428 * For use when pointData references a plot entity that arose (or potentially arose)
22429 * from multiple points in the input data
22430 *
22431 * @param {object} pointData : point data object (gets mutated here)
22432 * @param {object} trace : full trace object
22433 * @param {Array(number)|Array(Array(number))} pointNumbers : Array of point numbers.
22434 * Each entry in the array may itself be a length-2 array [row, col] to dig into 2D arrays
22435 */
22436exports.appendArrayMultiPointValues = function(pointData, trace, pointNumbers) {
22437 var arrayAttrs = trace._arrayAttrs;
22438
22439 if(!arrayAttrs) {
22440 return;
22441 }
22442
22443 for(var i = 0; i < arrayAttrs.length; i++) {
22444 var astr = arrayAttrs[i];
22445 var key = getPointKey(astr);
22446
22447 if(pointData[key] === undefined) {
22448 var val = Lib.nestedProperty(trace, astr).get();
22449 var keyVal = new Array(pointNumbers.length);
22450
22451 for(var j = 0; j < pointNumbers.length; j++) {
22452 keyVal[j] = getPointData(val, pointNumbers[j]);
22453 }
22454 pointData[key] = keyVal;
22455 }
22456 }
22457};
22458
22459var pointKeyMap = {
22460 ids: 'id',
22461 locations: 'location',
22462 labels: 'label',
22463 values: 'value',
22464 'marker.colors': 'color',
22465 parents: 'parent'
22466};
22467
22468function getPointKey(astr) {
22469 return pointKeyMap[astr] || astr;
22470}
22471
22472function getPointData(val, pointNumber) {
22473 if(Array.isArray(pointNumber)) {
22474 if(Array.isArray(val) && Array.isArray(val[pointNumber[0]])) {
22475 return val[pointNumber[0]][pointNumber[1]];
22476 }
22477 } else {
22478 return val[pointNumber];
22479 }
22480}
22481
22482var xyHoverMode = {
22483 x: true,
22484 y: true
22485};
22486
22487var unifiedHoverMode = {
22488 'x unified': true,
22489 'y unified': true
22490};
22491
22492exports.isUnifiedHover = function(hovermode) {
22493 if(typeof hovermode !== 'string') return false;
22494 return !!unifiedHoverMode[hovermode];
22495};
22496
22497exports.isXYhover = function(hovermode) {
22498 if(typeof hovermode !== 'string') return false;
22499 return !!xyHoverMode[hovermode];
22500};
22501
22502},{"../../lib":178}],89:[function(_dereq_,module,exports){
22503/**
22504* Copyright 2012-2020, Plotly, Inc.
22505* All rights reserved.
22506*
22507* This source code is licensed under the MIT license found in the
22508* LICENSE file in the root directory of this source tree.
22509*/
22510
22511'use strict';
22512
22513var d3 = _dereq_('d3');
22514var isNumeric = _dereq_('fast-isnumeric');
22515var tinycolor = _dereq_('tinycolor2');
22516
22517var Lib = _dereq_('../../lib');
22518var Events = _dereq_('../../lib/events');
22519var svgTextUtils = _dereq_('../../lib/svg_text_utils');
22520var overrideCursor = _dereq_('../../lib/override_cursor');
22521var Drawing = _dereq_('../drawing');
22522var Color = _dereq_('../color');
22523var dragElement = _dereq_('../dragelement');
22524var Axes = _dereq_('../../plots/cartesian/axes');
22525var Registry = _dereq_('../../registry');
22526
22527var helpers = _dereq_('./helpers');
22528var constants = _dereq_('./constants');
22529
22530var legendSupplyDefaults = _dereq_('../legend/defaults');
22531var legendDraw = _dereq_('../legend/draw');
22532
22533// hover labels for multiple horizontal bars get tilted by some angle,
22534// then need to be offset differently if they overlap
22535var YANGLE = constants.YANGLE;
22536var YA_RADIANS = Math.PI * YANGLE / 180;
22537
22538// expansion of projected height
22539var YFACTOR = 1 / Math.sin(YA_RADIANS);
22540
22541// to make the appropriate post-rotation x offset,
22542// you need both x and y offsets
22543var YSHIFTX = Math.cos(YA_RADIANS);
22544var YSHIFTY = Math.sin(YA_RADIANS);
22545
22546// size and display constants for hover text
22547var HOVERARROWSIZE = constants.HOVERARROWSIZE;
22548var HOVERTEXTPAD = constants.HOVERTEXTPAD;
22549
22550// fx.hover: highlight data on hover
22551// evt can be a mousemove event, or an object with data about what points
22552// to hover on
22553// {xpx,ypx[,hovermode]} - pixel locations from top left
22554// (with optional overriding hovermode)
22555// {xval,yval[,hovermode]} - data values
22556// [{curveNumber,(pointNumber|xval and/or yval)}] -
22557// array of specific points to highlight
22558// pointNumber is a single integer if gd.data[curveNumber] is 1D,
22559// or a two-element array if it's 2D
22560// xval and yval are data values,
22561// 1D data may specify either or both,
22562// 2D data must specify both
22563// subplot is an id string (default "xy")
22564// makes use of gl.hovermode, which can be:
22565// x (find the points with the closest x values, ie a column),
22566// closest (find the single closest point)
22567// internally there are two more that occasionally get used:
22568// y (pick out a row - only used for multiple horizontal bar charts)
22569// array (used when the user specifies an explicit
22570// array of points to hover on)
22571//
22572// We wrap the hovers in a timer, to limit their frequency.
22573// The actual rendering is done by private function _hover.
22574exports.hover = function hover(gd, evt, subplot, noHoverEvent) {
22575 gd = Lib.getGraphDiv(gd);
22576
22577 Lib.throttle(
22578 gd._fullLayout._uid + constants.HOVERID,
22579 constants.HOVERMINTIME,
22580 function() { _hover(gd, evt, subplot, noHoverEvent); }
22581 );
22582};
22583
22584/*
22585 * Draw a single hover item or an array of hover item in a pre-existing svg container somewhere
22586 * hoverItem should have keys:
22587 * - x and y (or x0, x1, y0, and y1):
22588 * the pixel position to mark, relative to opts.container
22589 * - xLabel, yLabel, zLabel, text, and name:
22590 * info to go in the label
22591 * - color:
22592 * the background color for the label.
22593 * - idealAlign (optional):
22594 * 'left' or 'right' for which side of the x/y box to try to put this on first
22595 * - borderColor (optional):
22596 * color for the border, defaults to strongest contrast with color
22597 * - fontFamily (optional):
22598 * string, the font for this label, defaults to constants.HOVERFONT
22599 * - fontSize (optional):
22600 * the label font size, defaults to constants.HOVERFONTSIZE
22601 * - fontColor (optional):
22602 * defaults to borderColor
22603 * opts should have keys:
22604 * - bgColor:
22605 * the background color this is against, used if the trace is
22606 * non-opaque, and for the name, which goes outside the box
22607 * - container:
22608 * a <svg> or <g> element to add the hover label to
22609 * - outerContainer:
22610 * normally a parent of `container`, sets the bounding box to use to
22611 * constrain the hover label and determine whether to show it on the left or right
22612 * opts can have optional keys:
22613 * - anchorIndex:
22614 the index of the hover item used as an anchor for positioning.
22615 The other hover items will be pushed up or down to prevent overlap.
22616 */
22617exports.loneHover = function loneHover(hoverItems, opts) {
22618 var multiHover = true;
22619 if(!Array.isArray(hoverItems)) {
22620 multiHover = false;
22621 hoverItems = [hoverItems];
22622 }
22623
22624 var pointsData = hoverItems.map(function(hoverItem) {
22625 return {
22626 color: hoverItem.color || Color.defaultLine,
22627 x0: hoverItem.x0 || hoverItem.x || 0,
22628 x1: hoverItem.x1 || hoverItem.x || 0,
22629 y0: hoverItem.y0 || hoverItem.y || 0,
22630 y1: hoverItem.y1 || hoverItem.y || 0,
22631 xLabel: hoverItem.xLabel,
22632 yLabel: hoverItem.yLabel,
22633 zLabel: hoverItem.zLabel,
22634 text: hoverItem.text,
22635 name: hoverItem.name,
22636 idealAlign: hoverItem.idealAlign,
22637
22638 // optional extra bits of styling
22639 borderColor: hoverItem.borderColor,
22640 fontFamily: hoverItem.fontFamily,
22641 fontSize: hoverItem.fontSize,
22642 fontColor: hoverItem.fontColor,
22643 nameLength: hoverItem.nameLength,
22644 textAlign: hoverItem.textAlign,
22645
22646 // filler to make createHoverText happy
22647 trace: hoverItem.trace || {
22648 index: 0,
22649 hoverinfo: ''
22650 },
22651 xa: {_offset: 0},
22652 ya: {_offset: 0},
22653 index: 0,
22654
22655 hovertemplate: hoverItem.hovertemplate || false,
22656 eventData: hoverItem.eventData || false,
22657 hovertemplateLabels: hoverItem.hovertemplateLabels || false,
22658 };
22659 });
22660
22661 var container3 = d3.select(opts.container);
22662 var outerContainer3 = opts.outerContainer ? d3.select(opts.outerContainer) : container3;
22663
22664 var fullOpts = {
22665 hovermode: 'closest',
22666 rotateLabels: false,
22667 bgColor: opts.bgColor || Color.background,
22668 container: container3,
22669 outerContainer: outerContainer3
22670 };
22671
22672 var hoverLabel = createHoverText(pointsData, fullOpts, opts.gd);
22673
22674 // Fix vertical overlap
22675 var tooltipSpacing = 5;
22676 var lastBottomY = 0;
22677 var anchor = 0;
22678 hoverLabel
22679 .sort(function(a, b) {return a.y0 - b.y0;})
22680 .each(function(d, i) {
22681 var topY = d.y0 - d.by / 2;
22682
22683 if((topY - tooltipSpacing) < lastBottomY) {
22684 d.offset = (lastBottomY - topY) + tooltipSpacing;
22685 } else {
22686 d.offset = 0;
22687 }
22688
22689 lastBottomY = topY + d.by + d.offset;
22690
22691 if(i === opts.anchorIndex || 0) anchor = d.offset;
22692 })
22693 .each(function(d) {
22694 d.offset -= anchor;
22695 });
22696
22697 alignHoverText(hoverLabel, fullOpts.rotateLabels);
22698
22699 return multiHover ? hoverLabel : hoverLabel.node();
22700};
22701
22702// The actual implementation is here:
22703function _hover(gd, evt, subplot, noHoverEvent) {
22704 if(!subplot) subplot = 'xy';
22705
22706 // if the user passed in an array of subplots,
22707 // use those instead of finding overlayed plots
22708 var subplots = Array.isArray(subplot) ? subplot : [subplot];
22709
22710 var fullLayout = gd._fullLayout;
22711 var plots = fullLayout._plots || [];
22712 var plotinfo = plots[subplot];
22713 var hasCartesian = fullLayout._has('cartesian');
22714
22715 // list of all overlaid subplots to look at
22716 if(plotinfo) {
22717 var overlayedSubplots = plotinfo.overlays.map(function(pi) {
22718 return pi.id;
22719 });
22720
22721 subplots = subplots.concat(overlayedSubplots);
22722 }
22723
22724 var len = subplots.length;
22725 var xaArray = new Array(len);
22726 var yaArray = new Array(len);
22727 var supportsCompare = false;
22728
22729 for(var i = 0; i < len; i++) {
22730 var spId = subplots[i];
22731
22732 if(plots[spId]) {
22733 // 'cartesian' case
22734 supportsCompare = true;
22735 xaArray[i] = plots[spId].xaxis;
22736 yaArray[i] = plots[spId].yaxis;
22737 } else if(fullLayout[spId] && fullLayout[spId]._subplot) {
22738 // other subplot types
22739 var _subplot = fullLayout[spId]._subplot;
22740 xaArray[i] = _subplot.xaxis;
22741 yaArray[i] = _subplot.yaxis;
22742 } else {
22743 Lib.warn('Unrecognized subplot: ' + spId);
22744 return;
22745 }
22746 }
22747
22748 var hovermode = evt.hovermode || fullLayout.hovermode;
22749
22750 if(hovermode && !supportsCompare) hovermode = 'closest';
22751
22752 if(['x', 'y', 'closest', 'x unified', 'y unified'].indexOf(hovermode) === -1 || !gd.calcdata ||
22753 gd.querySelector('.zoombox') || gd._dragging) {
22754 return dragElement.unhoverRaw(gd, evt);
22755 }
22756
22757 var hoverdistance = fullLayout.hoverdistance === -1 ? Infinity : fullLayout.hoverdistance;
22758 var spikedistance = fullLayout.spikedistance === -1 ? Infinity : fullLayout.spikedistance;
22759
22760 // hoverData: the set of candidate points we've found to highlight
22761 var hoverData = [];
22762
22763 // searchData: the data to search in. Mostly this is just a copy of
22764 // gd.calcdata, filtered to the subplot and overlays we're on
22765 // but if a point array is supplied it will be a mapping
22766 // of indicated curves
22767 var searchData = [];
22768
22769 // [x|y]valArray: the axis values of the hover event
22770 // mapped onto each of the currently selected overlaid subplots
22771 var xvalArray, yvalArray;
22772
22773 var itemnum, curvenum, cd, trace, subplotId, subploti, mode,
22774 xval, yval, pointData, closedataPreviousLength;
22775
22776 // spikePoints: the set of candidate points we've found to draw spikes to
22777 var spikePoints = {
22778 hLinePoint: null,
22779 vLinePoint: null
22780 };
22781
22782 // does subplot have one (or more) horizontal traces?
22783 // This is used to determine whether we rotate the labels or not
22784 var hasOneHorizontalTrace = false;
22785
22786 // Figure out what we're hovering on:
22787 // mouse location or user-supplied data
22788
22789 if(Array.isArray(evt)) {
22790 // user specified an array of points to highlight
22791 hovermode = 'array';
22792 for(itemnum = 0; itemnum < evt.length; itemnum++) {
22793 cd = gd.calcdata[evt[itemnum].curveNumber || 0];
22794 if(cd) {
22795 trace = cd[0].trace;
22796 if(cd[0].trace.hoverinfo !== 'skip') {
22797 searchData.push(cd);
22798 if(trace.orientation === 'h') {
22799 hasOneHorizontalTrace = true;
22800 }
22801 }
22802 }
22803 }
22804 } else {
22805 for(curvenum = 0; curvenum < gd.calcdata.length; curvenum++) {
22806 cd = gd.calcdata[curvenum];
22807 trace = cd[0].trace;
22808 if(trace.hoverinfo !== 'skip' && helpers.isTraceInSubplots(trace, subplots)) {
22809 searchData.push(cd);
22810 if(trace.orientation === 'h') {
22811 hasOneHorizontalTrace = true;
22812 }
22813 }
22814 }
22815
22816 // [x|y]px: the pixels (from top left) of the mouse location
22817 // on the currently selected plot area
22818 // add pointerX|Y property for drawing the spikes in spikesnap 'cursor' situation
22819 var hasUserCalledHover = !evt.target;
22820 var xpx, ypx;
22821
22822 if(hasUserCalledHover) {
22823 if('xpx' in evt) xpx = evt.xpx;
22824 else xpx = xaArray[0]._length / 2;
22825
22826 if('ypx' in evt) ypx = evt.ypx;
22827 else ypx = yaArray[0]._length / 2;
22828 } else {
22829 // fire the beforehover event and quit if it returns false
22830 // note that we're only calling this on real mouse events, so
22831 // manual calls to fx.hover will always run.
22832 if(Events.triggerHandler(gd, 'plotly_beforehover', evt) === false) {
22833 return;
22834 }
22835
22836 var dbb = evt.target.getBoundingClientRect();
22837
22838 xpx = evt.clientX - dbb.left;
22839 ypx = evt.clientY - dbb.top;
22840
22841 // in case hover was called from mouseout into hovertext,
22842 // it's possible you're not actually over the plot anymore
22843 if(xpx < 0 || xpx > xaArray[0]._length || ypx < 0 || ypx > yaArray[0]._length) {
22844 return dragElement.unhoverRaw(gd, evt);
22845 }
22846 }
22847
22848 evt.pointerX = xpx + xaArray[0]._offset;
22849 evt.pointerY = ypx + yaArray[0]._offset;
22850
22851 if('xval' in evt) xvalArray = helpers.flat(subplots, evt.xval);
22852 else xvalArray = helpers.p2c(xaArray, xpx);
22853
22854 if('yval' in evt) yvalArray = helpers.flat(subplots, evt.yval);
22855 else yvalArray = helpers.p2c(yaArray, ypx);
22856
22857 if(!isNumeric(xvalArray[0]) || !isNumeric(yvalArray[0])) {
22858 Lib.warn('Fx.hover failed', evt, gd);
22859 return dragElement.unhoverRaw(gd, evt);
22860 }
22861 }
22862
22863 // the pixel distance to beat as a matching point
22864 // in 'x' or 'y' mode this resets for each trace
22865 var distance = Infinity;
22866
22867 // find the closest point in each trace
22868 // this is minimum dx and/or dy, depending on mode
22869 // and the pixel position for the label (labelXpx, labelYpx)
22870 function findHoverPoints(customXVal, customYVal) {
22871 for(curvenum = 0; curvenum < searchData.length; curvenum++) {
22872 cd = searchData[curvenum];
22873
22874 // filter out invisible or broken data
22875 if(!cd || !cd[0] || !cd[0].trace) continue;
22876
22877 trace = cd[0].trace;
22878
22879 if(trace.visible !== true || trace._length === 0) continue;
22880
22881 // Explicitly bail out for these two. I don't know how to otherwise prevent
22882 // the rest of this function from running and failing
22883 if(['carpet', 'contourcarpet'].indexOf(trace._module.name) !== -1) continue;
22884
22885 if(trace.type === 'splom') {
22886 // splom traces do not generate overlay subplots,
22887 // it is safe to assume here splom traces correspond to the 0th subplot
22888 subploti = 0;
22889 subplotId = subplots[subploti];
22890 } else {
22891 subplotId = helpers.getSubplot(trace);
22892 subploti = subplots.indexOf(subplotId);
22893 }
22894
22895 // within one trace mode can sometimes be overridden
22896 mode = hovermode;
22897 if(helpers.isUnifiedHover(mode)) {
22898 mode = mode.charAt(0);
22899 }
22900
22901 // container for new point, also used to pass info into module.hoverPoints
22902 pointData = {
22903 // trace properties
22904 cd: cd,
22905 trace: trace,
22906 xa: xaArray[subploti],
22907 ya: yaArray[subploti],
22908
22909 // max distances for hover and spikes - for points that want to show but do not
22910 // want to override other points, set distance/spikeDistance equal to max*Distance
22911 // and it will not get filtered out but it will be guaranteed to have a greater
22912 // distance than any point that calculated a real distance.
22913 maxHoverDistance: hoverdistance,
22914 maxSpikeDistance: spikedistance,
22915
22916 // point properties - override all of these
22917 index: false, // point index in trace - only used by plotly.js hoverdata consumers
22918 distance: Math.min(distance, hoverdistance), // pixel distance or pseudo-distance
22919
22920 // distance/pseudo-distance for spikes. This distance should always be calculated
22921 // as if in "closest" mode, and should only be set if this point should
22922 // generate a spike.
22923 spikeDistance: Infinity,
22924
22925 // in some cases the spikes have different positioning from the hover label
22926 // they don't need x0/x1, just one position
22927 xSpike: undefined,
22928 ySpike: undefined,
22929
22930 // where and how to display the hover label
22931 color: Color.defaultLine, // trace color
22932 name: trace.name,
22933 x0: undefined,
22934 x1: undefined,
22935 y0: undefined,
22936 y1: undefined,
22937 xLabelVal: undefined,
22938 yLabelVal: undefined,
22939 zLabelVal: undefined,
22940 text: undefined
22941 };
22942
22943 // add ref to subplot object (non-cartesian case)
22944 if(fullLayout[subplotId]) {
22945 pointData.subplot = fullLayout[subplotId]._subplot;
22946 }
22947 // add ref to splom scene
22948 if(fullLayout._splomScenes && fullLayout._splomScenes[trace.uid]) {
22949 pointData.scene = fullLayout._splomScenes[trace.uid];
22950 }
22951
22952 closedataPreviousLength = hoverData.length;
22953
22954 // for a highlighting array, figure out what
22955 // we're searching for with this element
22956 if(mode === 'array') {
22957 var selection = evt[curvenum];
22958 if('pointNumber' in selection) {
22959 pointData.index = selection.pointNumber;
22960 mode = 'closest';
22961 } else {
22962 mode = '';
22963 if('xval' in selection) {
22964 xval = selection.xval;
22965 mode = 'x';
22966 }
22967 if('yval' in selection) {
22968 yval = selection.yval;
22969 mode = mode ? 'closest' : 'y';
22970 }
22971 }
22972 } else if(customXVal !== undefined && customYVal !== undefined) {
22973 xval = customXVal;
22974 yval = customYVal;
22975 } else {
22976 xval = xvalArray[subploti];
22977 yval = yvalArray[subploti];
22978 }
22979
22980 // Now if there is range to look in, find the points to hover.
22981 if(hoverdistance !== 0) {
22982 if(trace._module && trace._module.hoverPoints) {
22983 var newPoints = trace._module.hoverPoints(pointData, xval, yval, mode, fullLayout._hoverlayer);
22984 if(newPoints) {
22985 var newPoint;
22986 for(var newPointNum = 0; newPointNum < newPoints.length; newPointNum++) {
22987 newPoint = newPoints[newPointNum];
22988 if(isNumeric(newPoint.x0) && isNumeric(newPoint.y0)) {
22989 hoverData.push(cleanPoint(newPoint, hovermode));
22990 }
22991 }
22992 }
22993 } else {
22994 Lib.log('Unrecognized trace type in hover:', trace);
22995 }
22996 }
22997
22998 // in closest mode, remove any existing (farther) points
22999 // and don't look any farther than this latest point (or points, some
23000 // traces like box & violin make multiple hover labels at once)
23001 if(hovermode === 'closest' && hoverData.length > closedataPreviousLength) {
23002 hoverData.splice(0, closedataPreviousLength);
23003 distance = hoverData[0].distance;
23004 }
23005
23006 // Now if there is range to look in, find the points to draw the spikelines
23007 // Do it only if there is no hoverData
23008 if(hasCartesian && (spikedistance !== 0)) {
23009 if(hoverData.length === 0) {
23010 pointData.distance = spikedistance;
23011 pointData.index = false;
23012 var closestPoints = trace._module.hoverPoints(pointData, xval, yval, 'closest', fullLayout._hoverlayer);
23013 if(closestPoints) {
23014 closestPoints = closestPoints.filter(function(point) {
23015 // some hover points, like scatter fills, do not allow spikes,
23016 // so will generate a hover point but without a valid spikeDistance
23017 return point.spikeDistance <= spikedistance;
23018 });
23019 }
23020 if(closestPoints && closestPoints.length) {
23021 var tmpPoint;
23022 var closestVPoints = closestPoints.filter(function(point) {
23023 return point.xa.showspikes && point.xa.spikesnap !== 'hovered data';
23024 });
23025 if(closestVPoints.length) {
23026 var closestVPt = closestVPoints[0];
23027 if(isNumeric(closestVPt.x0) && isNumeric(closestVPt.y0)) {
23028 tmpPoint = fillSpikePoint(closestVPt);
23029 if(!spikePoints.vLinePoint || (spikePoints.vLinePoint.spikeDistance > tmpPoint.spikeDistance)) {
23030 spikePoints.vLinePoint = tmpPoint;
23031 }
23032 }
23033 }
23034
23035 var closestHPoints = closestPoints.filter(function(point) {
23036 return point.ya.showspikes && point.ya.spikesnap !== 'hovered data';
23037 });
23038 if(closestHPoints.length) {
23039 var closestHPt = closestHPoints[0];
23040 if(isNumeric(closestHPt.x0) && isNumeric(closestHPt.y0)) {
23041 tmpPoint = fillSpikePoint(closestHPt);
23042 if(!spikePoints.hLinePoint || (spikePoints.hLinePoint.spikeDistance > tmpPoint.spikeDistance)) {
23043 spikePoints.hLinePoint = tmpPoint;
23044 }
23045 }
23046 }
23047 }
23048 }
23049 }
23050 }
23051 }
23052
23053 findHoverPoints();
23054
23055 function selectClosestPoint(pointsData, spikedistance) {
23056 var resultPoint = null;
23057 var minDistance = Infinity;
23058 var thisSpikeDistance;
23059 for(var i = 0; i < pointsData.length; i++) {
23060 thisSpikeDistance = pointsData[i].spikeDistance;
23061 if(thisSpikeDistance <= minDistance && thisSpikeDistance <= spikedistance) {
23062 resultPoint = pointsData[i];
23063 minDistance = thisSpikeDistance;
23064 }
23065 }
23066 return resultPoint;
23067 }
23068
23069 function fillSpikePoint(point) {
23070 if(!point) return null;
23071 return {
23072 xa: point.xa,
23073 ya: point.ya,
23074 x: point.xSpike !== undefined ? point.xSpike : (point.x0 + point.x1) / 2,
23075 y: point.ySpike !== undefined ? point.ySpike : (point.y0 + point.y1) / 2,
23076 distance: point.distance,
23077 spikeDistance: point.spikeDistance,
23078 curveNumber: point.trace.index,
23079 color: point.color,
23080 pointNumber: point.index
23081 };
23082 }
23083
23084 var spikelineOpts = {
23085 fullLayout: fullLayout,
23086 container: fullLayout._hoverlayer,
23087 outerContainer: fullLayout._paperdiv,
23088 event: evt
23089 };
23090 var oldspikepoints = gd._spikepoints;
23091 var newspikepoints = {
23092 vLinePoint: spikePoints.vLinePoint,
23093 hLinePoint: spikePoints.hLinePoint
23094 };
23095 gd._spikepoints = newspikepoints;
23096
23097 // Now if it is not restricted by spikedistance option, set the points to draw the spikelines
23098 if(hasCartesian && (spikedistance !== 0)) {
23099 if(hoverData.length !== 0) {
23100 var tmpHPointData = hoverData.filter(function(point) {
23101 return point.ya.showspikes;
23102 });
23103 var tmpHPoint = selectClosestPoint(tmpHPointData, spikedistance);
23104 spikePoints.hLinePoint = fillSpikePoint(tmpHPoint);
23105
23106 var tmpVPointData = hoverData.filter(function(point) {
23107 return point.xa.showspikes;
23108 });
23109 var tmpVPoint = selectClosestPoint(tmpVPointData, spikedistance);
23110 spikePoints.vLinePoint = fillSpikePoint(tmpVPoint);
23111 }
23112 }
23113
23114 // if hoverData is empty check for the spikes to draw and quit if there are none
23115 if(hoverData.length === 0) {
23116 var result = dragElement.unhoverRaw(gd, evt);
23117 if(hasCartesian && ((spikePoints.hLinePoint !== null) || (spikePoints.vLinePoint !== null))) {
23118 if(spikesChanged(oldspikepoints)) {
23119 createSpikelines(gd, spikePoints, spikelineOpts);
23120 }
23121 }
23122 return result;
23123 }
23124
23125 if(hasCartesian) {
23126 if(spikesChanged(oldspikepoints)) {
23127 createSpikelines(gd, spikePoints, spikelineOpts);
23128 }
23129 }
23130
23131 hoverData.sort(function(d1, d2) { return d1.distance - d2.distance; });
23132
23133 // If in compare mode, select every point at position
23134 if(
23135 helpers.isXYhover(mode) &&
23136 hoverData[0].length !== 0 &&
23137 hoverData[0].trace.type !== 'splom' // TODO: add support for splom
23138 ) {
23139 var hd = hoverData[0];
23140 var cd0 = hd.cd[hd.index];
23141 var isGrouped = (fullLayout.boxmode === 'group' || fullLayout.violinmode === 'group');
23142
23143 var xVal = hd.xVal;
23144 var ax = hd.xa;
23145 if(ax.type === 'category') xVal = ax._categoriesMap[xVal];
23146 if(ax.type === 'date') xVal = ax.d2c(xVal);
23147 if(cd0 && cd0.t && cd0.t.posLetter === ax._id && isGrouped) {
23148 xVal += cd0.t.dPos;
23149 }
23150
23151 var yVal = hd.yVal;
23152 ax = hd.ya;
23153 if(ax.type === 'category') yVal = ax._categoriesMap[yVal];
23154 if(ax.type === 'date') yVal = ax.d2c(yVal);
23155 if(cd0 && cd0.t && cd0.t.posLetter === ax._id && isGrouped) {
23156 yVal += cd0.t.dPos;
23157 }
23158
23159 findHoverPoints(xVal, yVal);
23160
23161 // Remove duplicated hoverData points
23162 // note that d3 also filters identical points in the rendering steps
23163 var repeated = {};
23164 hoverData = hoverData.filter(function(hd) {
23165 var key = hoverDataKey(hd);
23166 if(!repeated[key]) {
23167 repeated[key] = true;
23168 return repeated[key];
23169 }
23170 });
23171 }
23172
23173 // lastly, emit custom hover/unhover events
23174 var oldhoverdata = gd._hoverdata;
23175 var newhoverdata = [];
23176
23177 // pull out just the data that's useful to
23178 // other people and send it to the event
23179 for(itemnum = 0; itemnum < hoverData.length; itemnum++) {
23180 var pt = hoverData[itemnum];
23181 var eventData = helpers.makeEventData(pt, pt.trace, pt.cd);
23182
23183 if(pt.hovertemplate !== false) {
23184 var ht = false;
23185 if(pt.cd[pt.index] && pt.cd[pt.index].ht) {
23186 ht = pt.cd[pt.index].ht;
23187 }
23188 pt.hovertemplate = ht || pt.trace.hovertemplate || false;
23189 }
23190
23191 pt.eventData = [eventData];
23192 newhoverdata.push(eventData);
23193 }
23194
23195 gd._hoverdata = newhoverdata;
23196
23197 var rotateLabels = (
23198 (hovermode === 'y' && (searchData.length > 1 || hoverData.length > 1)) ||
23199 (hovermode === 'closest' && hasOneHorizontalTrace && hoverData.length > 1)
23200 );
23201
23202 var bgColor = Color.combine(
23203 fullLayout.plot_bgcolor || Color.background,
23204 fullLayout.paper_bgcolor
23205 );
23206
23207 var labelOpts = {
23208 hovermode: hovermode,
23209 rotateLabels: rotateLabels,
23210 bgColor: bgColor,
23211 container: fullLayout._hoverlayer,
23212 outerContainer: fullLayout._paperdiv,
23213 commonLabelOpts: fullLayout.hoverlabel,
23214 hoverdistance: fullLayout.hoverdistance
23215 };
23216
23217 var hoverLabels = createHoverText(hoverData, labelOpts, gd);
23218
23219 if(!helpers.isUnifiedHover(hovermode)) {
23220 hoverAvoidOverlaps(hoverLabels, rotateLabels ? 'xa' : 'ya', fullLayout);
23221 alignHoverText(hoverLabels, rotateLabels);
23222 }
23223
23224 // TODO: tagName hack is needed to appease geo.js's hack of using evt.target=true
23225 // we should improve the "fx" API so other plots can use it without these hack.
23226 if(evt.target && evt.target.tagName) {
23227 var hasClickToShow = Registry.getComponentMethod('annotations', 'hasClickToShow')(gd, newhoverdata);
23228 overrideCursor(d3.select(evt.target), hasClickToShow ? 'pointer' : '');
23229 }
23230
23231 // don't emit events if called manually
23232 if(!evt.target || noHoverEvent || !hoverChanged(gd, evt, oldhoverdata)) return;
23233
23234 if(oldhoverdata) {
23235 gd.emit('plotly_unhover', {
23236 event: evt,
23237 points: oldhoverdata
23238 });
23239 }
23240
23241 gd.emit('plotly_hover', {
23242 event: evt,
23243 points: gd._hoverdata,
23244 xaxes: xaArray,
23245 yaxes: yaArray,
23246 xvals: xvalArray,
23247 yvals: yvalArray
23248 });
23249}
23250
23251function hoverDataKey(d) {
23252 return [d.trace.index, d.index, d.x0, d.y0, d.name, d.attr, d.xa, d.ya || ''].join(',');
23253}
23254
23255var EXTRA_STRING_REGEX = /<extra>([\s\S]*)<\/extra>/;
23256
23257function createHoverText(hoverData, opts, gd) {
23258 var fullLayout = gd._fullLayout;
23259 var hovermode = opts.hovermode;
23260 var rotateLabels = opts.rotateLabels;
23261 var bgColor = opts.bgColor;
23262 var container = opts.container;
23263 var outerContainer = opts.outerContainer;
23264 var commonLabelOpts = opts.commonLabelOpts || {};
23265
23266 // opts.fontFamily/Size are used for the common label
23267 // and as defaults for each hover label, though the individual labels
23268 // can override this.
23269 var fontFamily = opts.fontFamily || constants.HOVERFONT;
23270 var fontSize = opts.fontSize || constants.HOVERFONTSIZE;
23271
23272 var c0 = hoverData[0];
23273 var xa = c0.xa;
23274 var ya = c0.ya;
23275 var commonAttr = hovermode.charAt(0) === 'y' ? 'yLabel' : 'xLabel';
23276 var t0 = c0[commonAttr];
23277 var t00 = (String(t0) || '').split(' ')[0];
23278 var outerContainerBB = outerContainer.node().getBoundingClientRect();
23279 var outerTop = outerContainerBB.top;
23280 var outerWidth = outerContainerBB.width;
23281 var outerHeight = outerContainerBB.height;
23282
23283 // show the common label, if any, on the axis
23284 // never show a common label in array mode,
23285 // even if sometimes there could be one
23286 var showCommonLabel = (
23287 (t0 !== undefined) &&
23288 (c0.distance <= opts.hoverdistance) &&
23289 (hovermode === 'x' || hovermode === 'y')
23290 );
23291
23292 // all hover traces hoverinfo must contain the hovermode
23293 // to have common labels
23294 if(showCommonLabel) {
23295 var allHaveZ = true;
23296 var i, traceHoverinfo;
23297 for(i = 0; i < hoverData.length; i++) {
23298 if(allHaveZ && hoverData[i].zLabel === undefined) allHaveZ = false;
23299
23300 traceHoverinfo = hoverData[i].hoverinfo || hoverData[i].trace.hoverinfo;
23301 if(traceHoverinfo) {
23302 var parts = Array.isArray(traceHoverinfo) ? traceHoverinfo : traceHoverinfo.split('+');
23303 if(parts.indexOf('all') === -1 &&
23304 parts.indexOf(hovermode) === -1) {
23305 showCommonLabel = false;
23306 break;
23307 }
23308 }
23309 }
23310
23311 // xyz labels put all info in their main label, so have no need of a common label
23312 if(allHaveZ) showCommonLabel = false;
23313 }
23314
23315 var commonLabel = container.selectAll('g.axistext')
23316 .data(showCommonLabel ? [0] : []);
23317 commonLabel.enter().append('g')
23318 .classed('axistext', true);
23319 commonLabel.exit().remove();
23320
23321 commonLabel.each(function() {
23322 var label = d3.select(this);
23323 var lpath = Lib.ensureSingle(label, 'path', '', function(s) {
23324 s.style({'stroke-width': '1px'});
23325 });
23326 var ltext = Lib.ensureSingle(label, 'text', '', function(s) {
23327 // prohibit tex interpretation until we can handle
23328 // tex and regular text together
23329 s.attr('data-notex', 1);
23330 });
23331
23332 var commonBgColor = commonLabelOpts.bgcolor || Color.defaultLine;
23333 var commonStroke = commonLabelOpts.bordercolor || Color.contrast(commonBgColor);
23334 var contrastColor = Color.contrast(commonBgColor);
23335 var commonLabelFont = {
23336 family: commonLabelOpts.font.family || fontFamily,
23337 size: commonLabelOpts.font.size || fontSize,
23338 color: commonLabelOpts.font.color || contrastColor
23339 };
23340
23341 lpath.style({
23342 fill: commonBgColor,
23343 stroke: commonStroke
23344 });
23345
23346 ltext.text(t0)
23347 .call(Drawing.font, commonLabelFont)
23348 .call(svgTextUtils.positionText, 0, 0)
23349 .call(svgTextUtils.convertToTspans, gd);
23350
23351 label.attr('transform', '');
23352
23353 var tbb = ltext.node().getBoundingClientRect();
23354 var lx, ly;
23355
23356 if(hovermode === 'x') {
23357 var topsign = xa.side === 'top' ? '-' : '';
23358
23359 ltext.attr('text-anchor', 'middle')
23360 .call(svgTextUtils.positionText, 0, (xa.side === 'top' ?
23361 (outerTop - tbb.bottom - HOVERARROWSIZE - HOVERTEXTPAD) :
23362 (outerTop - tbb.top + HOVERARROWSIZE + HOVERTEXTPAD)));
23363
23364 lx = xa._offset + (c0.x0 + c0.x1) / 2;
23365 ly = ya._offset + (xa.side === 'top' ? 0 : ya._length);
23366
23367 var halfWidth = tbb.width / 2 + HOVERTEXTPAD;
23368
23369 if(lx < halfWidth) {
23370 lx = halfWidth;
23371
23372 lpath.attr('d', 'M-' + (halfWidth - HOVERARROWSIZE) + ',0' +
23373 'L-' + (halfWidth - HOVERARROWSIZE * 2) + ',' + topsign + HOVERARROWSIZE +
23374 'H' + (HOVERTEXTPAD + tbb.width / 2) +
23375 'v' + topsign + (HOVERTEXTPAD * 2 + tbb.height) +
23376 'H-' + halfWidth +
23377 'V' + topsign + HOVERARROWSIZE +
23378 'Z');
23379 } else if(lx > (fullLayout.width - halfWidth)) {
23380 lx = fullLayout.width - halfWidth;
23381
23382 lpath.attr('d', 'M' + (halfWidth - HOVERARROWSIZE) + ',0' +
23383 'L' + halfWidth + ',' + topsign + HOVERARROWSIZE +
23384 'v' + topsign + (HOVERTEXTPAD * 2 + tbb.height) +
23385 'H-' + halfWidth +
23386 'V' + topsign + HOVERARROWSIZE +
23387 'H' + (halfWidth - HOVERARROWSIZE * 2) + 'Z');
23388 } else {
23389 lpath.attr('d', 'M0,0' +
23390 'L' + HOVERARROWSIZE + ',' + topsign + HOVERARROWSIZE +
23391 'H' + (HOVERTEXTPAD + tbb.width / 2) +
23392 'v' + topsign + (HOVERTEXTPAD * 2 + tbb.height) +
23393 'H-' + (HOVERTEXTPAD + tbb.width / 2) +
23394 'V' + topsign + HOVERARROWSIZE +
23395 'H-' + HOVERARROWSIZE + 'Z');
23396 }
23397 } else {
23398 var anchor;
23399 var sgn;
23400 var leftsign;
23401 if(ya.side === 'right') {
23402 anchor = 'start';
23403 sgn = 1;
23404 leftsign = '';
23405 lx = xa._offset + xa._length;
23406 } else {
23407 anchor = 'end';
23408 sgn = -1;
23409 leftsign = '-';
23410 lx = xa._offset;
23411 }
23412
23413 ly = ya._offset + (c0.y0 + c0.y1) / 2;
23414
23415 ltext.attr('text-anchor', anchor);
23416
23417 lpath.attr('d', 'M0,0' +
23418 'L' + leftsign + HOVERARROWSIZE + ',' + HOVERARROWSIZE +
23419 'V' + (HOVERTEXTPAD + tbb.height / 2) +
23420 'h' + leftsign + (HOVERTEXTPAD * 2 + tbb.width) +
23421 'V-' + (HOVERTEXTPAD + tbb.height / 2) +
23422 'H' + leftsign + HOVERARROWSIZE + 'V-' + HOVERARROWSIZE + 'Z');
23423
23424 var halfHeight = tbb.height / 2;
23425 var lty = outerTop - tbb.top - halfHeight;
23426 var clipId = 'clip' + fullLayout._uid + 'commonlabel' + ya._id;
23427 var clipPath;
23428
23429 if(lx < (tbb.width + 2 * HOVERTEXTPAD + HOVERARROWSIZE)) {
23430 clipPath = 'M-' + (HOVERARROWSIZE + HOVERTEXTPAD) + '-' + halfHeight +
23431 'h-' + (tbb.width - HOVERTEXTPAD) +
23432 'V' + halfHeight +
23433 'h' + (tbb.width - HOVERTEXTPAD) + 'Z';
23434
23435 var ltx = tbb.width - lx + HOVERTEXTPAD;
23436 svgTextUtils.positionText(ltext, ltx, lty);
23437
23438 // shift each line (except the longest) so that start-of-line
23439 // is always visible
23440 if(anchor === 'end') {
23441 ltext.selectAll('tspan').each(function() {
23442 var s = d3.select(this);
23443 var dummy = Drawing.tester.append('text')
23444 .text(s.text())
23445 .call(Drawing.font, commonLabelFont);
23446 var dummyBB = dummy.node().getBoundingClientRect();
23447 if(Math.round(dummyBB.width) < Math.round(tbb.width)) {
23448 s.attr('x', ltx - dummyBB.width);
23449 }
23450 dummy.remove();
23451 });
23452 }
23453 } else {
23454 svgTextUtils.positionText(ltext, sgn * (HOVERTEXTPAD + HOVERARROWSIZE), lty);
23455 clipPath = null;
23456 }
23457
23458 var textClip = fullLayout._topclips.selectAll('#' + clipId).data(clipPath ? [0] : []);
23459 textClip.enter().append('clipPath').attr('id', clipId).append('path');
23460 textClip.exit().remove();
23461 textClip.select('path').attr('d', clipPath);
23462 Drawing.setClipUrl(ltext, clipPath ? clipId : null, gd);
23463 }
23464
23465 label.attr('transform', 'translate(' + lx + ',' + ly + ')');
23466
23467 // remove the "close but not quite" points
23468 // because of error bars, only take up to a space
23469 hoverData = filterClosePoints(hoverData);
23470 });
23471
23472 function filterClosePoints(hoverData) {
23473 return hoverData.filter(function(d) {
23474 return (d.zLabelVal !== undefined) ||
23475 (d[commonAttr] || '').split(' ')[0] === t00;
23476 });
23477 }
23478
23479 // Show a single hover label
23480 if(helpers.isUnifiedHover(hovermode)) {
23481 // Delete leftover hover labels from other hovermodes
23482 container.selectAll('g.hovertext').remove();
23483
23484 // similarly to compare mode, we remove the "close but not quite together" points
23485 if((t0 !== undefined) && (c0.distance <= opts.hoverdistance)) hoverData = filterClosePoints(hoverData);
23486
23487 // Return early if nothing is hovered on
23488 if(hoverData.length === 0) return;
23489
23490 // mock legend
23491 var mockLayoutIn = {
23492 showlegend: true,
23493 legend: {
23494 title: {text: t0, font: fullLayout.hoverlabel.font},
23495 font: fullLayout.hoverlabel.font,
23496 bgcolor: fullLayout.hoverlabel.bgcolor,
23497 bordercolor: fullLayout.hoverlabel.bordercolor,
23498 borderwidth: 1,
23499 tracegroupgap: 7,
23500 traceorder: fullLayout.legend ? fullLayout.legend.traceorder : undefined,
23501 orientation: 'v'
23502 }
23503 };
23504 var mockLayoutOut = {};
23505 legendSupplyDefaults(mockLayoutIn, mockLayoutOut, gd._fullData);
23506 var legendOpts = mockLayoutOut.legend;
23507
23508 // prepare items for the legend
23509 legendOpts.entries = [];
23510 for(var j = 0; j < hoverData.length; j++) {
23511 var texts = getHoverLabelText(hoverData[j], true, hovermode, fullLayout, t0);
23512 var text = texts[0];
23513 var name = texts[1];
23514 var pt = hoverData[j];
23515 pt.name = name;
23516 if(name !== '') {
23517 pt.text = name + ' : ' + text;
23518 } else {
23519 pt.text = text;
23520 }
23521
23522 // pass through marker's calcdata to style legend items
23523 var cd = pt.cd[pt.index];
23524 if(cd) {
23525 if(cd.mc) pt.mc = cd.mc;
23526 if(cd.mcc) pt.mc = cd.mcc;
23527 if(cd.mlc) pt.mlc = cd.mlc;
23528 if(cd.mlcc) pt.mlc = cd.mlcc;
23529 if(cd.mlw) pt.mlw = cd.mlw;
23530 if(cd.mrc) pt.mrc = cd.mrc;
23531 if(cd.dir) pt.dir = cd.dir;
23532 }
23533 pt._distinct = true;
23534
23535 legendOpts.entries.push([pt]);
23536 }
23537 legendOpts.entries.sort(function(a, b) { return a[0].trace.index - b[0].trace.index;});
23538 legendOpts.layer = container;
23539
23540 // Draw unified hover label
23541 legendDraw(gd, legendOpts);
23542
23543 // Position the hover
23544 var ly = Lib.mean(hoverData.map(function(c) {return (c.y0 + c.y1) / 2;}));
23545 var lx = Lib.mean(hoverData.map(function(c) {return (c.x0 + c.x1) / 2;}));
23546 var legendContainer = container.select('g.legend');
23547 var tbb = legendContainer.node().getBoundingClientRect();
23548 lx += xa._offset;
23549 ly += ya._offset - tbb.height / 2;
23550
23551 // Change horizontal alignment to end up on screen
23552 var txWidth = tbb.width + 2 * HOVERTEXTPAD;
23553 var anchorStartOK = lx + txWidth <= outerWidth;
23554 var anchorEndOK = lx - txWidth >= 0;
23555 if(!anchorStartOK && anchorEndOK) {
23556 lx -= txWidth;
23557 } else {
23558 lx += 2 * HOVERTEXTPAD;
23559 }
23560
23561 // Change vertical alignement to end up on screen
23562 var txHeight = tbb.height + 2 * HOVERTEXTPAD;
23563 var overflowTop = ly <= outerTop;
23564 var overflowBottom = ly + txHeight >= outerHeight;
23565 var canFit = txHeight <= outerHeight;
23566 if(canFit) {
23567 if(overflowTop) {
23568 ly = ya._offset + 2 * HOVERTEXTPAD;
23569 } else if(overflowBottom) {
23570 ly = outerHeight - txHeight;
23571 }
23572 }
23573 legendContainer.attr('transform', 'translate(' + lx + ',' + ly + ')');
23574
23575 return legendContainer;
23576 }
23577
23578 // show all the individual labels
23579
23580 // first create the objects
23581 var hoverLabels = container.selectAll('g.hovertext')
23582 .data(hoverData, function(d) {
23583 // N.B. when multiple items have the same result key-function value,
23584 // only the first of those items in hoverData gets rendered
23585 return hoverDataKey(d);
23586 });
23587 hoverLabels.enter().append('g')
23588 .classed('hovertext', true)
23589 .each(function() {
23590 var g = d3.select(this);
23591 // trace name label (rect and text.name)
23592 g.append('rect')
23593 .call(Color.fill, Color.addOpacity(bgColor, 0.8));
23594 g.append('text').classed('name', true);
23595 // trace data label (path and text.nums)
23596 g.append('path')
23597 .style('stroke-width', '1px');
23598 g.append('text').classed('nums', true)
23599 .call(Drawing.font, fontFamily, fontSize);
23600 });
23601 hoverLabels.exit().remove();
23602
23603 // then put the text in, position the pointer to the data,
23604 // and figure out sizes
23605 hoverLabels.each(function(d) {
23606 var g = d3.select(this).attr('transform', '');
23607
23608 // combine possible non-opaque trace color with bgColor
23609 var color0 = d.bgcolor || d.color;
23610 // color for 'nums' part of the label
23611 var numsColor = Color.combine(
23612 Color.opacity(color0) ? color0 : Color.defaultLine,
23613 bgColor
23614 );
23615 // color for 'name' part of the label
23616 var nameColor = Color.combine(
23617 Color.opacity(d.color) ? d.color : Color.defaultLine,
23618 bgColor
23619 );
23620 // find a contrasting color for border and text
23621 var contrastColor = d.borderColor || Color.contrast(numsColor);
23622
23623 var texts = getHoverLabelText(d, showCommonLabel, hovermode, fullLayout, t0, g);
23624 var text = texts[0];
23625 var name = texts[1];
23626
23627 // main label
23628 var tx = g.select('text.nums')
23629 .call(Drawing.font,
23630 d.fontFamily || fontFamily,
23631 d.fontSize || fontSize,
23632 d.fontColor || contrastColor)
23633 .text(text)
23634 .attr('data-notex', 1)
23635 .call(svgTextUtils.positionText, 0, 0)
23636 .call(svgTextUtils.convertToTspans, gd);
23637
23638 var tx2 = g.select('text.name');
23639 var tx2width = 0;
23640 var tx2height = 0;
23641
23642 // secondary label for non-empty 'name'
23643 if(name && name !== text) {
23644 tx2.call(Drawing.font,
23645 d.fontFamily || fontFamily,
23646 d.fontSize || fontSize,
23647 nameColor)
23648 .text(name)
23649 .attr('data-notex', 1)
23650 .call(svgTextUtils.positionText, 0, 0)
23651 .call(svgTextUtils.convertToTspans, gd);
23652
23653 var t2bb = tx2.node().getBoundingClientRect();
23654 tx2width = t2bb.width + 2 * HOVERTEXTPAD;
23655 tx2height = t2bb.height + 2 * HOVERTEXTPAD;
23656 } else {
23657 tx2.remove();
23658 g.select('rect').remove();
23659 }
23660
23661 g.select('path').style({
23662 fill: numsColor,
23663 stroke: contrastColor
23664 });
23665
23666 var tbb = tx.node().getBoundingClientRect();
23667 var htx = d.xa._offset + (d.x0 + d.x1) / 2;
23668 var hty = d.ya._offset + (d.y0 + d.y1) / 2;
23669 var dx = Math.abs(d.x1 - d.x0);
23670 var dy = Math.abs(d.y1 - d.y0);
23671 var txTotalWidth = tbb.width + HOVERARROWSIZE + HOVERTEXTPAD + tx2width;
23672 var anchorStartOK, anchorEndOK;
23673
23674 d.ty0 = outerTop - tbb.top;
23675 d.bx = tbb.width + 2 * HOVERTEXTPAD;
23676 d.by = Math.max(tbb.height + 2 * HOVERTEXTPAD, tx2height);
23677 d.anchor = 'start';
23678 d.txwidth = tbb.width;
23679 d.tx2width = tx2width;
23680 d.offset = 0;
23681
23682 if(rotateLabels) {
23683 d.pos = htx;
23684 anchorStartOK = hty + dy / 2 + txTotalWidth <= outerHeight;
23685 anchorEndOK = hty - dy / 2 - txTotalWidth >= 0;
23686 if((d.idealAlign === 'top' || !anchorStartOK) && anchorEndOK) {
23687 hty -= dy / 2;
23688 d.anchor = 'end';
23689 } else if(anchorStartOK) {
23690 hty += dy / 2;
23691 d.anchor = 'start';
23692 } else d.anchor = 'middle';
23693 } else {
23694 d.pos = hty;
23695 anchorStartOK = htx + dx / 2 + txTotalWidth <= outerWidth;
23696 anchorEndOK = htx - dx / 2 - txTotalWidth >= 0;
23697
23698 if((d.idealAlign === 'left' || !anchorStartOK) && anchorEndOK) {
23699 htx -= dx / 2;
23700 d.anchor = 'end';
23701 } else if(anchorStartOK) {
23702 htx += dx / 2;
23703 d.anchor = 'start';
23704 } else {
23705 d.anchor = 'middle';
23706
23707 var txHalfWidth = txTotalWidth / 2;
23708 var overflowR = htx + txHalfWidth - outerWidth;
23709 var overflowL = htx - txHalfWidth;
23710 if(overflowR > 0) htx -= overflowR;
23711 if(overflowL < 0) htx += -overflowL;
23712 }
23713 }
23714
23715 tx.attr('text-anchor', d.anchor);
23716 if(tx2width) tx2.attr('text-anchor', d.anchor);
23717 g.attr('transform', 'translate(' + htx + ',' + hty + ')' +
23718 (rotateLabels ? 'rotate(' + YANGLE + ')' : ''));
23719 });
23720
23721 return hoverLabels;
23722}
23723
23724function getHoverLabelText(d, showCommonLabel, hovermode, fullLayout, t0, g) {
23725 var name = '';
23726 var text = '';
23727 // to get custom 'name' labels pass cleanPoint
23728 if(d.nameOverride !== undefined) d.name = d.nameOverride;
23729
23730 if(d.name) {
23731 if(d.trace._meta) {
23732 d.name = Lib.templateString(d.name, d.trace._meta);
23733 }
23734 name = plainText(d.name, d.nameLength);
23735 }
23736
23737 if(d.zLabel !== undefined) {
23738 if(d.xLabel !== undefined) text += 'x: ' + d.xLabel + '<br>';
23739 if(d.yLabel !== undefined) text += 'y: ' + d.yLabel + '<br>';
23740 if(d.trace.type !== 'choropleth' && d.trace.type !== 'choroplethmapbox') {
23741 text += (text ? 'z: ' : '') + d.zLabel;
23742 }
23743 } else if(showCommonLabel && d[hovermode.charAt(0) + 'Label'] === t0) {
23744 text = d[(hovermode.charAt(0) === 'x' ? 'y' : 'x') + 'Label'] || '';
23745 } else if(d.xLabel === undefined) {
23746 if(d.yLabel !== undefined && d.trace.type !== 'scattercarpet') {
23747 text = d.yLabel;
23748 }
23749 } else if(d.yLabel === undefined) text = d.xLabel;
23750 else text = '(' + d.xLabel + ', ' + d.yLabel + ')';
23751
23752 if((d.text || d.text === 0) && !Array.isArray(d.text)) {
23753 text += (text ? '<br>' : '') + d.text;
23754 }
23755
23756 // used by other modules (initially just ternary) that
23757 // manage their own hoverinfo independent of cleanPoint
23758 // the rest of this will still apply, so such modules
23759 // can still put things in (x|y|z)Label, text, and name
23760 // and hoverinfo will still determine their visibility
23761 if(d.extraText !== undefined) text += (text ? '<br>' : '') + d.extraText;
23762
23763 // if 'text' is empty at this point,
23764 // and hovertemplate is not defined,
23765 // put 'name' in main label and don't show secondary label
23766 if(g && text === '' && !d.hovertemplate) {
23767 // if 'name' is also empty, remove entire label
23768 if(name === '') g.remove();
23769 text = name;
23770 }
23771
23772 // hovertemplate
23773 var d3locale = fullLayout._d3locale;
23774 var hovertemplate = d.hovertemplate || false;
23775 var hovertemplateLabels = d.hovertemplateLabels || d;
23776 var eventData = d.eventData[0] || {};
23777 if(hovertemplate) {
23778 text = Lib.hovertemplateString(
23779 hovertemplate,
23780 hovertemplateLabels,
23781 d3locale,
23782 eventData,
23783 d.trace._meta
23784 );
23785
23786 text = text.replace(EXTRA_STRING_REGEX, function(match, extra) {
23787 // assign name for secondary text label
23788 name = plainText(extra, d.nameLength);
23789 // remove from main text label
23790 return '';
23791 });
23792 }
23793 return [text, name];
23794}
23795
23796// Make groups of touching points, and within each group
23797// move each point so that no labels overlap, but the average
23798// label position is the same as it was before moving. Indicentally,
23799// this is equivalent to saying all the labels are on equal linear
23800// springs about their initial position. Initially, each point is
23801// its own group, but as we find overlaps we will clump the points.
23802//
23803// Also, there are hard constraints at the edges of the graphs,
23804// that push all groups to the middle so they are visible. I don't
23805// know what happens if the group spans all the way from one edge to
23806// the other, though it hardly matters - there's just too much
23807// information then.
23808function hoverAvoidOverlaps(hoverLabels, axKey, fullLayout) {
23809 var nummoves = 0;
23810 var axSign = 1;
23811 var nLabels = hoverLabels.size();
23812
23813 // make groups of touching points
23814 var pointgroups = new Array(nLabels);
23815 var k = 0;
23816
23817 hoverLabels.each(function(d) {
23818 var ax = d[axKey];
23819 var axIsX = ax._id.charAt(0) === 'x';
23820 var rng = ax.range;
23821
23822 if(k === 0 && rng && ((rng[0] > rng[1]) !== axIsX)) {
23823 axSign = -1;
23824 }
23825 pointgroups[k++] = [{
23826 datum: d,
23827 traceIndex: d.trace.index,
23828 dp: 0,
23829 pos: d.pos,
23830 posref: d.posref,
23831 size: d.by * (axIsX ? YFACTOR : 1) / 2,
23832 pmin: 0,
23833 pmax: (axIsX ? fullLayout.width : fullLayout.height)
23834 }];
23835 });
23836
23837 pointgroups.sort(function(a, b) {
23838 return (a[0].posref - b[0].posref) ||
23839 // for equal positions, sort trace indices increasing or decreasing
23840 // depending on whether the axis is reversed or not... so stacked
23841 // traces will generally keep their order even if one trace adds
23842 // nothing to the stack.
23843 (axSign * (b[0].traceIndex - a[0].traceIndex));
23844 });
23845
23846 var donepositioning, topOverlap, bottomOverlap, i, j, pti, sumdp;
23847
23848 function constrainGroup(grp) {
23849 var minPt = grp[0];
23850 var maxPt = grp[grp.length - 1];
23851
23852 // overlap with the top - positive vals are overlaps
23853 topOverlap = minPt.pmin - minPt.pos - minPt.dp + minPt.size;
23854
23855 // overlap with the bottom - positive vals are overlaps
23856 bottomOverlap = maxPt.pos + maxPt.dp + maxPt.size - minPt.pmax;
23857
23858 // check for min overlap first, so that we always
23859 // see the largest labels
23860 // allow for .01px overlap, so we don't get an
23861 // infinite loop from rounding errors
23862 if(topOverlap > 0.01) {
23863 for(j = grp.length - 1; j >= 0; j--) grp[j].dp += topOverlap;
23864 donepositioning = false;
23865 }
23866 if(bottomOverlap < 0.01) return;
23867 if(topOverlap < -0.01) {
23868 // make sure we're not pushing back and forth
23869 for(j = grp.length - 1; j >= 0; j--) grp[j].dp -= bottomOverlap;
23870 donepositioning = false;
23871 }
23872 if(!donepositioning) return;
23873
23874 // no room to fix positioning, delete off-screen points
23875
23876 // first see how many points we need to delete
23877 var deleteCount = 0;
23878 for(i = 0; i < grp.length; i++) {
23879 pti = grp[i];
23880 if(pti.pos + pti.dp + pti.size > minPt.pmax) deleteCount++;
23881 }
23882
23883 // start by deleting points whose data is off screen
23884 for(i = grp.length - 1; i >= 0; i--) {
23885 if(deleteCount <= 0) break;
23886 pti = grp[i];
23887
23888 // pos has already been constrained to [pmin,pmax]
23889 // so look for points close to that to delete
23890 if(pti.pos > minPt.pmax - 1) {
23891 pti.del = true;
23892 deleteCount--;
23893 }
23894 }
23895 for(i = 0; i < grp.length; i++) {
23896 if(deleteCount <= 0) break;
23897 pti = grp[i];
23898
23899 // pos has already been constrained to [pmin,pmax]
23900 // so look for points close to that to delete
23901 if(pti.pos < minPt.pmin + 1) {
23902 pti.del = true;
23903 deleteCount--;
23904
23905 // shift the whole group minus into this new space
23906 bottomOverlap = pti.size * 2;
23907 for(j = grp.length - 1; j >= 0; j--) grp[j].dp -= bottomOverlap;
23908 }
23909 }
23910 // then delete points that go off the bottom
23911 for(i = grp.length - 1; i >= 0; i--) {
23912 if(deleteCount <= 0) break;
23913 pti = grp[i];
23914 if(pti.pos + pti.dp + pti.size > minPt.pmax) {
23915 pti.del = true;
23916 deleteCount--;
23917 }
23918 }
23919 }
23920
23921 // loop through groups, combining them if they overlap,
23922 // until nothing moves
23923 while(!donepositioning && nummoves <= nLabels) {
23924 // to avoid infinite loops, don't move more times
23925 // than there are traces
23926 nummoves++;
23927
23928 // assume nothing will move in this iteration,
23929 // reverse this if it does
23930 donepositioning = true;
23931 i = 0;
23932 while(i < pointgroups.length - 1) {
23933 // the higher (g0) and lower (g1) point group
23934 var g0 = pointgroups[i];
23935 var g1 = pointgroups[i + 1];
23936
23937 // the lowest point in the higher group (p0)
23938 // the highest point in the lower group (p1)
23939 var p0 = g0[g0.length - 1];
23940 var p1 = g1[0];
23941 topOverlap = p0.pos + p0.dp + p0.size - p1.pos - p1.dp + p1.size;
23942
23943 // Only group points that lie on the same axes
23944 if(topOverlap > 0.01 && (p0.pmin === p1.pmin) && (p0.pmax === p1.pmax)) {
23945 // push the new point(s) added to this group out of the way
23946 for(j = g1.length - 1; j >= 0; j--) g1[j].dp += topOverlap;
23947
23948 // add them to the group
23949 g0.push.apply(g0, g1);
23950 pointgroups.splice(i + 1, 1);
23951
23952 // adjust for minimum average movement
23953 sumdp = 0;
23954 for(j = g0.length - 1; j >= 0; j--) sumdp += g0[j].dp;
23955 bottomOverlap = sumdp / g0.length;
23956 for(j = g0.length - 1; j >= 0; j--) g0[j].dp -= bottomOverlap;
23957 donepositioning = false;
23958 } else i++;
23959 }
23960
23961 // check if we're going off the plot on either side and fix
23962 pointgroups.forEach(constrainGroup);
23963 }
23964
23965 // now put these offsets into hoverData
23966 for(i = pointgroups.length - 1; i >= 0; i--) {
23967 var grp = pointgroups[i];
23968 for(j = grp.length - 1; j >= 0; j--) {
23969 var pt = grp[j];
23970 var hoverPt = pt.datum;
23971 hoverPt.offset = pt.dp;
23972 hoverPt.del = pt.del;
23973 }
23974 }
23975}
23976
23977function alignHoverText(hoverLabels, rotateLabels) {
23978 // finally set the text positioning relative to the data and draw the
23979 // box around it
23980 hoverLabels.each(function(d) {
23981 var g = d3.select(this);
23982 if(d.del) return g.remove();
23983
23984 var tx = g.select('text.nums');
23985 var anchor = d.anchor;
23986 var horzSign = anchor === 'end' ? -1 : 1;
23987 var alignShift = {start: 1, end: -1, middle: 0}[anchor];
23988 var txx = alignShift * (HOVERARROWSIZE + HOVERTEXTPAD);
23989 var tx2x = txx + alignShift * (d.txwidth + HOVERTEXTPAD);
23990 var offsetX = 0;
23991 var offsetY = d.offset;
23992
23993 if(anchor === 'middle') {
23994 txx -= d.tx2width / 2;
23995 tx2x += d.txwidth / 2 + HOVERTEXTPAD;
23996 }
23997 if(rotateLabels) {
23998 offsetY *= -YSHIFTY;
23999 offsetX = d.offset * YSHIFTX;
24000 }
24001
24002 g.select('path').attr('d', anchor === 'middle' ?
24003 // middle aligned: rect centered on data
24004 ('M-' + (d.bx / 2 + d.tx2width / 2) + ',' + (offsetY - d.by / 2) +
24005 'h' + d.bx + 'v' + d.by + 'h-' + d.bx + 'Z') :
24006 // left or right aligned: side rect with arrow to data
24007 ('M0,0L' + (horzSign * HOVERARROWSIZE + offsetX) + ',' + (HOVERARROWSIZE + offsetY) +
24008 'v' + (d.by / 2 - HOVERARROWSIZE) +
24009 'h' + (horzSign * d.bx) +
24010 'v-' + d.by +
24011 'H' + (horzSign * HOVERARROWSIZE + offsetX) +
24012 'V' + (offsetY - HOVERARROWSIZE) +
24013 'Z'));
24014
24015 var posX = txx + offsetX;
24016 var posY = offsetY + d.ty0 - d.by / 2 + HOVERTEXTPAD;
24017 var textAlign = d.textAlign || 'auto';
24018
24019 if(textAlign !== 'auto') {
24020 if(textAlign === 'left' && anchor !== 'start') {
24021 tx.attr('text-anchor', 'start');
24022 posX = anchor === 'middle' ?
24023 -d.bx / 2 - d.tx2width / 2 + HOVERTEXTPAD :
24024 -d.bx - HOVERTEXTPAD;
24025 } else if(textAlign === 'right' && anchor !== 'end') {
24026 tx.attr('text-anchor', 'end');
24027 posX = anchor === 'middle' ?
24028 d.bx / 2 - d.tx2width / 2 - HOVERTEXTPAD :
24029 d.bx + HOVERTEXTPAD;
24030 }
24031 }
24032
24033 tx.call(svgTextUtils.positionText, posX, posY);
24034
24035 if(d.tx2width) {
24036 g.select('text.name')
24037 .call(svgTextUtils.positionText,
24038 tx2x + alignShift * HOVERTEXTPAD + offsetX,
24039 offsetY + d.ty0 - d.by / 2 + HOVERTEXTPAD);
24040 g.select('rect')
24041 .call(Drawing.setRect,
24042 tx2x + (alignShift - 1) * d.tx2width / 2 + offsetX,
24043 offsetY - d.by / 2 - 1,
24044 d.tx2width, d.by + 2);
24045 }
24046 });
24047}
24048
24049function cleanPoint(d, hovermode) {
24050 var index = d.index;
24051 var trace = d.trace || {};
24052 var cd0 = d.cd[0];
24053 var cd = d.cd[index] || {};
24054
24055 function pass(v) {
24056 return v || (isNumeric(v) && v === 0);
24057 }
24058
24059 var getVal = Array.isArray(index) ?
24060 function(calcKey, traceKey) {
24061 var v = Lib.castOption(cd0, index, calcKey);
24062 return pass(v) ? v : Lib.extractOption({}, trace, '', traceKey);
24063 } :
24064 function(calcKey, traceKey) {
24065 return Lib.extractOption(cd, trace, calcKey, traceKey);
24066 };
24067
24068 function fill(key, calcKey, traceKey) {
24069 var val = getVal(calcKey, traceKey);
24070 if(pass(val)) d[key] = val;
24071 }
24072
24073 fill('hoverinfo', 'hi', 'hoverinfo');
24074 fill('bgcolor', 'hbg', 'hoverlabel.bgcolor');
24075 fill('borderColor', 'hbc', 'hoverlabel.bordercolor');
24076 fill('fontFamily', 'htf', 'hoverlabel.font.family');
24077 fill('fontSize', 'hts', 'hoverlabel.font.size');
24078 fill('fontColor', 'htc', 'hoverlabel.font.color');
24079 fill('nameLength', 'hnl', 'hoverlabel.namelength');
24080 fill('textAlign', 'hta', 'hoverlabel.align');
24081
24082 d.posref = (hovermode === 'y' || (hovermode === 'closest' && trace.orientation === 'h')) ?
24083 (d.xa._offset + (d.x0 + d.x1) / 2) :
24084 (d.ya._offset + (d.y0 + d.y1) / 2);
24085
24086 // then constrain all the positions to be on the plot
24087 d.x0 = Lib.constrain(d.x0, 0, d.xa._length);
24088 d.x1 = Lib.constrain(d.x1, 0, d.xa._length);
24089 d.y0 = Lib.constrain(d.y0, 0, d.ya._length);
24090 d.y1 = Lib.constrain(d.y1, 0, d.ya._length);
24091
24092 // and convert the x and y label values into formatted text
24093 if(d.xLabelVal !== undefined) {
24094 d.xLabel = ('xLabel' in d) ? d.xLabel : Axes.hoverLabelText(d.xa, d.xLabelVal);
24095 d.xVal = d.xa.c2d(d.xLabelVal);
24096 }
24097 if(d.yLabelVal !== undefined) {
24098 d.yLabel = ('yLabel' in d) ? d.yLabel : Axes.hoverLabelText(d.ya, d.yLabelVal);
24099 d.yVal = d.ya.c2d(d.yLabelVal);
24100 }
24101
24102 // Traces like heatmaps generate the zLabel in their hoverPoints function
24103 if(d.zLabelVal !== undefined && d.zLabel === undefined) {
24104 d.zLabel = String(d.zLabelVal);
24105 }
24106
24107 // for box means and error bars, add the range to the label
24108 if(!isNaN(d.xerr) && !(d.xa.type === 'log' && d.xerr <= 0)) {
24109 var xeText = Axes.tickText(d.xa, d.xa.c2l(d.xerr), 'hover').text;
24110 if(d.xerrneg !== undefined) {
24111 d.xLabel += ' +' + xeText + ' / -' +
24112 Axes.tickText(d.xa, d.xa.c2l(d.xerrneg), 'hover').text;
24113 } else d.xLabel += ' ± ' + xeText;
24114
24115 // small distance penalty for error bars, so that if there are
24116 // traces with errors and some without, the error bar label will
24117 // hoist up to the point
24118 if(hovermode === 'x') d.distance += 1;
24119 }
24120 if(!isNaN(d.yerr) && !(d.ya.type === 'log' && d.yerr <= 0)) {
24121 var yeText = Axes.tickText(d.ya, d.ya.c2l(d.yerr), 'hover').text;
24122 if(d.yerrneg !== undefined) {
24123 d.yLabel += ' +' + yeText + ' / -' +
24124 Axes.tickText(d.ya, d.ya.c2l(d.yerrneg), 'hover').text;
24125 } else d.yLabel += ' ± ' + yeText;
24126
24127 if(hovermode === 'y') d.distance += 1;
24128 }
24129
24130 var infomode = d.hoverinfo || d.trace.hoverinfo;
24131
24132 if(infomode && infomode !== 'all') {
24133 infomode = Array.isArray(infomode) ? infomode : infomode.split('+');
24134 if(infomode.indexOf('x') === -1) d.xLabel = undefined;
24135 if(infomode.indexOf('y') === -1) d.yLabel = undefined;
24136 if(infomode.indexOf('z') === -1) d.zLabel = undefined;
24137 if(infomode.indexOf('text') === -1) d.text = undefined;
24138 if(infomode.indexOf('name') === -1) d.name = undefined;
24139 }
24140
24141 return d;
24142}
24143
24144function createSpikelines(gd, closestPoints, opts) {
24145 var container = opts.container;
24146 var fullLayout = opts.fullLayout;
24147 var gs = fullLayout._size;
24148 var evt = opts.event;
24149 var showY = !!closestPoints.hLinePoint;
24150 var showX = !!closestPoints.vLinePoint;
24151
24152 var xa, ya;
24153
24154 // Remove old spikeline items
24155 container.selectAll('.spikeline').remove();
24156
24157 if(!(showX || showY)) return;
24158
24159 var contrastColor = Color.combine(fullLayout.plot_bgcolor, fullLayout.paper_bgcolor);
24160
24161 // Horizontal line (to y-axis)
24162 if(showY) {
24163 var hLinePoint = closestPoints.hLinePoint;
24164 var hLinePointX, hLinePointY;
24165
24166 xa = hLinePoint && hLinePoint.xa;
24167 ya = hLinePoint && hLinePoint.ya;
24168 var ySnap = ya.spikesnap;
24169
24170 if(ySnap === 'cursor') {
24171 hLinePointX = evt.pointerX;
24172 hLinePointY = evt.pointerY;
24173 } else {
24174 hLinePointX = xa._offset + hLinePoint.x;
24175 hLinePointY = ya._offset + hLinePoint.y;
24176 }
24177 var dfltHLineColor = tinycolor.readability(hLinePoint.color, contrastColor) < 1.5 ?
24178 Color.contrast(contrastColor) : hLinePoint.color;
24179 var yMode = ya.spikemode;
24180 var yThickness = ya.spikethickness;
24181 var yColor = ya.spikecolor || dfltHLineColor;
24182 var xEdge = Axes.getPxPosition(gd, ya);
24183 var xBase, xEndSpike;
24184
24185 if(yMode.indexOf('toaxis') !== -1 || yMode.indexOf('across') !== -1) {
24186 if(yMode.indexOf('toaxis') !== -1) {
24187 xBase = xEdge;
24188 xEndSpike = hLinePointX;
24189 }
24190 if(yMode.indexOf('across') !== -1) {
24191 var xAcross0 = ya._counterDomainMin;
24192 var xAcross1 = ya._counterDomainMax;
24193 if(ya.anchor === 'free') {
24194 xAcross0 = Math.min(xAcross0, ya.position);
24195 xAcross1 = Math.max(xAcross1, ya.position);
24196 }
24197 xBase = gs.l + xAcross0 * gs.w;
24198 xEndSpike = gs.l + xAcross1 * gs.w;
24199 }
24200
24201 // Foreground horizontal line (to y-axis)
24202 container.insert('line', ':first-child')
24203 .attr({
24204 x1: xBase,
24205 x2: xEndSpike,
24206 y1: hLinePointY,
24207 y2: hLinePointY,
24208 'stroke-width': yThickness,
24209 stroke: yColor,
24210 'stroke-dasharray': Drawing.dashStyle(ya.spikedash, yThickness)
24211 })
24212 .classed('spikeline', true)
24213 .classed('crisp', true);
24214
24215 // Background horizontal Line (to y-axis)
24216 container.insert('line', ':first-child')
24217 .attr({
24218 x1: xBase,
24219 x2: xEndSpike,
24220 y1: hLinePointY,
24221 y2: hLinePointY,
24222 'stroke-width': yThickness + 2,
24223 stroke: contrastColor
24224 })
24225 .classed('spikeline', true)
24226 .classed('crisp', true);
24227 }
24228 // Y axis marker
24229 if(yMode.indexOf('marker') !== -1) {
24230 container.insert('circle', ':first-child')
24231 .attr({
24232 cx: xEdge + (ya.side !== 'right' ? yThickness : -yThickness),
24233 cy: hLinePointY,
24234 r: yThickness,
24235 fill: yColor
24236 })
24237 .classed('spikeline', true);
24238 }
24239 }
24240
24241 if(showX) {
24242 var vLinePoint = closestPoints.vLinePoint;
24243 var vLinePointX, vLinePointY;
24244
24245 xa = vLinePoint && vLinePoint.xa;
24246 ya = vLinePoint && vLinePoint.ya;
24247 var xSnap = xa.spikesnap;
24248
24249 if(xSnap === 'cursor') {
24250 vLinePointX = evt.pointerX;
24251 vLinePointY = evt.pointerY;
24252 } else {
24253 vLinePointX = xa._offset + vLinePoint.x;
24254 vLinePointY = ya._offset + vLinePoint.y;
24255 }
24256 var dfltVLineColor = tinycolor.readability(vLinePoint.color, contrastColor) < 1.5 ?
24257 Color.contrast(contrastColor) : vLinePoint.color;
24258 var xMode = xa.spikemode;
24259 var xThickness = xa.spikethickness;
24260 var xColor = xa.spikecolor || dfltVLineColor;
24261 var yEdge = Axes.getPxPosition(gd, xa);
24262 var yBase, yEndSpike;
24263
24264 if(xMode.indexOf('toaxis') !== -1 || xMode.indexOf('across') !== -1) {
24265 if(xMode.indexOf('toaxis') !== -1) {
24266 yBase = yEdge;
24267 yEndSpike = vLinePointY;
24268 }
24269 if(xMode.indexOf('across') !== -1) {
24270 var yAcross0 = xa._counterDomainMin;
24271 var yAcross1 = xa._counterDomainMax;
24272 if(xa.anchor === 'free') {
24273 yAcross0 = Math.min(yAcross0, xa.position);
24274 yAcross1 = Math.max(yAcross1, xa.position);
24275 }
24276 yBase = gs.t + (1 - yAcross1) * gs.h;
24277 yEndSpike = gs.t + (1 - yAcross0) * gs.h;
24278 }
24279
24280 // Foreground vertical line (to x-axis)
24281 container.insert('line', ':first-child')
24282 .attr({
24283 x1: vLinePointX,
24284 x2: vLinePointX,
24285 y1: yBase,
24286 y2: yEndSpike,
24287 'stroke-width': xThickness,
24288 stroke: xColor,
24289 'stroke-dasharray': Drawing.dashStyle(xa.spikedash, xThickness)
24290 })
24291 .classed('spikeline', true)
24292 .classed('crisp', true);
24293
24294 // Background vertical line (to x-axis)
24295 container.insert('line', ':first-child')
24296 .attr({
24297 x1: vLinePointX,
24298 x2: vLinePointX,
24299 y1: yBase,
24300 y2: yEndSpike,
24301 'stroke-width': xThickness + 2,
24302 stroke: contrastColor
24303 })
24304 .classed('spikeline', true)
24305 .classed('crisp', true);
24306 }
24307
24308 // X axis marker
24309 if(xMode.indexOf('marker') !== -1) {
24310 container.insert('circle', ':first-child')
24311 .attr({
24312 cx: vLinePointX,
24313 cy: yEdge - (xa.side !== 'top' ? xThickness : -xThickness),
24314 r: xThickness,
24315 fill: xColor
24316 })
24317 .classed('spikeline', true);
24318 }
24319 }
24320}
24321
24322function hoverChanged(gd, evt, oldhoverdata) {
24323 // don't emit any events if nothing changed
24324 if(!oldhoverdata || oldhoverdata.length !== gd._hoverdata.length) return true;
24325
24326 for(var i = oldhoverdata.length - 1; i >= 0; i--) {
24327 var oldPt = oldhoverdata[i];
24328 var newPt = gd._hoverdata[i];
24329
24330 if(oldPt.curveNumber !== newPt.curveNumber ||
24331 String(oldPt.pointNumber) !== String(newPt.pointNumber) ||
24332 String(oldPt.pointNumbers) !== String(newPt.pointNumbers)
24333 ) {
24334 return true;
24335 }
24336 }
24337 return false;
24338}
24339
24340function spikesChanged(gd, oldspikepoints) {
24341 // don't relayout the plot because of new spikelines if spikelines points didn't change
24342 if(!oldspikepoints) return true;
24343 if(oldspikepoints.vLinePoint !== gd._spikepoints.vLinePoint ||
24344 oldspikepoints.hLinePoint !== gd._spikepoints.hLinePoint
24345 ) return true;
24346 return false;
24347}
24348
24349function plainText(s, len) {
24350 return svgTextUtils.plainText(s || '', {
24351 len: len,
24352 allowedTags: ['br', 'sub', 'sup', 'b', 'i', 'em']
24353 });
24354}
24355
24356},{"../../lib":178,"../../lib/events":172,"../../lib/override_cursor":189,"../../lib/svg_text_utils":199,"../../plots/cartesian/axes":222,"../../registry":269,"../color":52,"../dragelement":71,"../drawing":74,"../legend/defaults":104,"../legend/draw":105,"./constants":86,"./helpers":88,"d3":16,"fast-isnumeric":18,"tinycolor2":35}],90:[function(_dereq_,module,exports){
24357/**
24358* Copyright 2012-2020, Plotly, Inc.
24359* All rights reserved.
24360*
24361* This source code is licensed under the MIT license found in the
24362* LICENSE file in the root directory of this source tree.
24363*/
24364
24365'use strict';
24366
24367var Lib = _dereq_('../../lib');
24368var Color = _dereq_('../color');
24369var isUnifiedHover = _dereq_('./helpers').isUnifiedHover;
24370
24371module.exports = function handleHoverLabelDefaults(contIn, contOut, coerce, opts) {
24372 opts = opts || {};
24373
24374 function inheritFontAttr(attr) {
24375 if(!opts.font[attr]) {
24376 opts.font[attr] = contOut.legend ? contOut.legend.font[attr] : contOut.font[attr];
24377 }
24378 }
24379
24380 // In unified hover, inherit from layout.legend if available or layout
24381 if(contOut && isUnifiedHover(contOut.hovermode)) {
24382 if(!opts.font) opts.font = {};
24383 inheritFontAttr('size');
24384 inheritFontAttr('family');
24385 inheritFontAttr('color');
24386
24387 if(contOut.legend) {
24388 if(!opts.bgcolor) opts.bgcolor = Color.combine(contOut.legend.bgcolor, contOut.paper_bgcolor);
24389 if(!opts.bordercolor) opts.bordercolor = contOut.legend.bordercolor;
24390 } else {
24391 if(!opts.bgcolor) opts.bgcolor = contOut.paper_bgcolor;
24392 }
24393 }
24394
24395 coerce('hoverlabel.bgcolor', opts.bgcolor);
24396 coerce('hoverlabel.bordercolor', opts.bordercolor);
24397 coerce('hoverlabel.namelength', opts.namelength);
24398 Lib.coerceFont(coerce, 'hoverlabel.font', opts.font);
24399 coerce('hoverlabel.align', opts.align);
24400};
24401
24402},{"../../lib":178,"../color":52,"./helpers":88}],91:[function(_dereq_,module,exports){
24403/**
24404* Copyright 2012-2020, Plotly, Inc.
24405* All rights reserved.
24406*
24407* This source code is licensed under the MIT license found in the
24408* LICENSE file in the root directory of this source tree.
24409*/
24410
24411'use strict';
24412
24413var Lib = _dereq_('../../lib');
24414var layoutAttributes = _dereq_('./layout_attributes');
24415
24416module.exports = function handleHoverModeDefaults(layoutIn, layoutOut, fullData) {
24417 function coerce(attr, dflt) {
24418 // don't coerce if it is already coerced in other place e.g. in cartesian defaults
24419 if(layoutOut[attr] !== undefined) return layoutOut[attr];
24420
24421 return Lib.coerce(layoutIn, layoutOut, layoutAttributes, attr, dflt);
24422 }
24423
24424 var clickmode = coerce('clickmode');
24425
24426 var hovermodeDflt;
24427 if(layoutOut._has('cartesian')) {
24428 if(clickmode.indexOf('select') > -1) {
24429 hovermodeDflt = 'closest';
24430 } else {
24431 // flag for 'horizontal' plots:
24432 // determines the state of the mode bar 'compare' hovermode button
24433 layoutOut._isHoriz = isHoriz(fullData, layoutOut);
24434 hovermodeDflt = layoutOut._isHoriz ? 'y' : 'x';
24435 }
24436 } else hovermodeDflt = 'closest';
24437
24438 return coerce('hovermode', hovermodeDflt);
24439};
24440
24441function isHoriz(fullData, fullLayout) {
24442 var stackOpts = fullLayout._scatterStackOpts || {};
24443
24444 for(var i = 0; i < fullData.length; i++) {
24445 var trace = fullData[i];
24446 var subplot = trace.xaxis + trace.yaxis;
24447 var subplotStackOpts = stackOpts[subplot] || {};
24448 var groupOpts = subplotStackOpts[trace.stackgroup] || {};
24449
24450 if(trace.orientation !== 'h' && groupOpts.orientation !== 'h') {
24451 return false;
24452 }
24453 }
24454
24455 return true;
24456}
24457
24458},{"../../lib":178,"./layout_attributes":93}],92:[function(_dereq_,module,exports){
24459/**
24460* Copyright 2012-2020, Plotly, Inc.
24461* All rights reserved.
24462*
24463* This source code is licensed under the MIT license found in the
24464* LICENSE file in the root directory of this source tree.
24465*/
24466
24467'use strict';
24468
24469var d3 = _dereq_('d3');
24470var Lib = _dereq_('../../lib');
24471var dragElement = _dereq_('../dragelement');
24472var helpers = _dereq_('./helpers');
24473var layoutAttributes = _dereq_('./layout_attributes');
24474var hoverModule = _dereq_('./hover');
24475
24476module.exports = {
24477 moduleType: 'component',
24478 name: 'fx',
24479
24480 constants: _dereq_('./constants'),
24481 schema: {
24482 layout: layoutAttributes
24483 },
24484
24485 attributes: _dereq_('./attributes'),
24486 layoutAttributes: layoutAttributes,
24487
24488 supplyLayoutGlobalDefaults: _dereq_('./layout_global_defaults'),
24489 supplyDefaults: _dereq_('./defaults'),
24490 supplyLayoutDefaults: _dereq_('./layout_defaults'),
24491
24492 calc: _dereq_('./calc'),
24493
24494 getDistanceFunction: helpers.getDistanceFunction,
24495 getClosest: helpers.getClosest,
24496 inbox: helpers.inbox,
24497 quadrature: helpers.quadrature,
24498 appendArrayPointValue: helpers.appendArrayPointValue,
24499
24500 castHoverOption: castHoverOption,
24501 castHoverinfo: castHoverinfo,
24502
24503 hover: hoverModule.hover,
24504 unhover: dragElement.unhover,
24505
24506 loneHover: hoverModule.loneHover,
24507 loneUnhover: loneUnhover,
24508
24509 click: _dereq_('./click')
24510};
24511
24512function loneUnhover(containerOrSelection) {
24513 // duck type whether the arg is a d3 selection because ie9 doesn't
24514 // handle instanceof like modern browsers do.
24515 var selection = Lib.isD3Selection(containerOrSelection) ?
24516 containerOrSelection :
24517 d3.select(containerOrSelection);
24518
24519 selection.selectAll('g.hovertext').remove();
24520 selection.selectAll('.spikeline').remove();
24521}
24522
24523// helpers for traces that use Fx.loneHover
24524
24525function castHoverOption(trace, ptNumber, attr) {
24526 return Lib.castOption(trace, ptNumber, 'hoverlabel.' + attr);
24527}
24528
24529function castHoverinfo(trace, fullLayout, ptNumber) {
24530 function _coerce(val) {
24531 return Lib.coerceHoverinfo({hoverinfo: val}, {_module: trace._module}, fullLayout);
24532 }
24533
24534 return Lib.castOption(trace, ptNumber, 'hoverinfo', _coerce);
24535}
24536
24537},{"../../lib":178,"../dragelement":71,"./attributes":83,"./calc":84,"./click":85,"./constants":86,"./defaults":87,"./helpers":88,"./hover":89,"./layout_attributes":93,"./layout_defaults":94,"./layout_global_defaults":95,"d3":16}],93:[function(_dereq_,module,exports){
24538/**
24539* Copyright 2012-2020, Plotly, Inc.
24540* All rights reserved.
24541*
24542* This source code is licensed under the MIT license found in the
24543* LICENSE file in the root directory of this source tree.
24544*/
24545
24546'use strict';
24547
24548var constants = _dereq_('./constants');
24549
24550var fontAttrs = _dereq_('../../plots/font_attributes')({
24551 editType: 'none',
24552
24553});
24554fontAttrs.family.dflt = constants.HOVERFONT;
24555fontAttrs.size.dflt = constants.HOVERFONTSIZE;
24556
24557module.exports = {
24558 clickmode: {
24559 valType: 'flaglist',
24560
24561 flags: ['event', 'select'],
24562 dflt: 'event',
24563 editType: 'plot',
24564 extras: ['none'],
24565
24566 },
24567 dragmode: {
24568 valType: 'enumerated',
24569
24570 values: [
24571 'zoom',
24572 'pan',
24573 'select',
24574 'lasso',
24575 'drawclosedpath',
24576 'drawopenpath',
24577 'drawline',
24578 'drawrect',
24579 'drawcircle',
24580 'orbit',
24581 'turntable',
24582 false
24583 ],
24584 dflt: 'zoom',
24585 editType: 'modebar',
24586
24587 },
24588 hovermode: {
24589 valType: 'enumerated',
24590
24591 values: ['x', 'y', 'closest', false, 'x unified', 'y unified'],
24592 editType: 'modebar',
24593
24594 },
24595 hoverdistance: {
24596 valType: 'integer',
24597 min: -1,
24598 dflt: 20,
24599
24600 editType: 'none',
24601
24602 },
24603 spikedistance: {
24604 valType: 'integer',
24605 min: -1,
24606 dflt: 20,
24607
24608 editType: 'none',
24609
24610 },
24611 hoverlabel: {
24612 bgcolor: {
24613 valType: 'color',
24614
24615 editType: 'none',
24616
24617 },
24618 bordercolor: {
24619 valType: 'color',
24620
24621 editType: 'none',
24622
24623 },
24624 font: fontAttrs,
24625 align: {
24626 valType: 'enumerated',
24627 values: ['left', 'right', 'auto'],
24628 dflt: 'auto',
24629
24630 editType: 'none',
24631
24632 },
24633 namelength: {
24634 valType: 'integer',
24635 min: -1,
24636 dflt: 15,
24637
24638 editType: 'none',
24639
24640 },
24641 editType: 'none'
24642 },
24643 selectdirection: {
24644 valType: 'enumerated',
24645
24646 values: ['h', 'v', 'd', 'any'],
24647 dflt: 'any',
24648
24649 editType: 'none'
24650 }
24651};
24652
24653},{"../../plots/font_attributes":250,"./constants":86}],94:[function(_dereq_,module,exports){
24654/**
24655* Copyright 2012-2020, Plotly, Inc.
24656* All rights reserved.
24657*
24658* This source code is licensed under the MIT license found in the
24659* LICENSE file in the root directory of this source tree.
24660*/
24661
24662'use strict';
24663
24664var Lib = _dereq_('../../lib');
24665var isUnifiedHover = _dereq_('./helpers').isUnifiedHover;
24666var layoutAttributes = _dereq_('./layout_attributes');
24667var handleHoverModeDefaults = _dereq_('./hovermode_defaults');
24668var handleHoverLabelDefaults = _dereq_('./hoverlabel_defaults');
24669
24670module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) {
24671 function coerce(attr, dflt) {
24672 return Lib.coerce(layoutIn, layoutOut, layoutAttributes, attr, dflt);
24673 }
24674
24675 var hoverMode = handleHoverModeDefaults(layoutIn, layoutOut, fullData);
24676 if(hoverMode) {
24677 coerce('hoverdistance');
24678 coerce('spikedistance', isUnifiedHover(hoverMode) ? -1 : undefined);
24679 }
24680
24681 var dragMode = coerce('dragmode');
24682 if(dragMode === 'select') coerce('selectdirection');
24683
24684 // if only mapbox or geo subplots is present on graph,
24685 // reset 'zoom' dragmode to 'pan' until 'zoom' is implemented,
24686 // so that the correct modebar button is active
24687 var hasMapbox = layoutOut._has('mapbox');
24688 var hasGeo = layoutOut._has('geo');
24689 var len = layoutOut._basePlotModules.length;
24690
24691 if(layoutOut.dragmode === 'zoom' && (
24692 ((hasMapbox || hasGeo) && len === 1) ||
24693 (hasMapbox && hasGeo && len === 2)
24694 )) {
24695 layoutOut.dragmode = 'pan';
24696 }
24697
24698 handleHoverLabelDefaults(layoutIn, layoutOut, coerce);
24699};
24700
24701},{"../../lib":178,"./helpers":88,"./hoverlabel_defaults":90,"./hovermode_defaults":91,"./layout_attributes":93}],95:[function(_dereq_,module,exports){
24702/**
24703* Copyright 2012-2020, Plotly, Inc.
24704* All rights reserved.
24705*
24706* This source code is licensed under the MIT license found in the
24707* LICENSE file in the root directory of this source tree.
24708*/
24709
24710'use strict';
24711
24712var Lib = _dereq_('../../lib');
24713var handleHoverLabelDefaults = _dereq_('./hoverlabel_defaults');
24714var layoutAttributes = _dereq_('./layout_attributes');
24715
24716module.exports = function supplyLayoutGlobalDefaults(layoutIn, layoutOut) {
24717 function coerce(attr, dflt) {
24718 return Lib.coerce(layoutIn, layoutOut, layoutAttributes, attr, dflt);
24719 }
24720
24721 handleHoverLabelDefaults(layoutIn, layoutOut, coerce);
24722};
24723
24724},{"../../lib":178,"./hoverlabel_defaults":90,"./layout_attributes":93}],96:[function(_dereq_,module,exports){
24725/**
24726* Copyright 2012-2020, Plotly, Inc.
24727* All rights reserved.
24728*
24729* This source code is licensed under the MIT license found in the
24730* LICENSE file in the root directory of this source tree.
24731*/
24732
24733'use strict';
24734
24735var Lib = _dereq_('../../lib');
24736var counterRegex = _dereq_('../../lib/regex').counter;
24737var domainAttrs = _dereq_('../../plots/domain').attributes;
24738var cartesianIdRegex = _dereq_('../../plots/cartesian/constants').idRegex;
24739var Template = _dereq_('../../plot_api/plot_template');
24740
24741var gridAttrs = {
24742 rows: {
24743 valType: 'integer',
24744 min: 1,
24745
24746 editType: 'plot',
24747
24748 },
24749 roworder: {
24750 valType: 'enumerated',
24751 values: ['top to bottom', 'bottom to top'],
24752 dflt: 'top to bottom',
24753
24754 editType: 'plot',
24755
24756 },
24757 columns: {
24758 valType: 'integer',
24759 min: 1,
24760
24761 editType: 'plot',
24762
24763 },
24764 subplots: {
24765 valType: 'info_array',
24766 freeLength: true,
24767 dimensions: 2,
24768 items: {valType: 'enumerated', values: [counterRegex('xy').toString(), ''], editType: 'plot'},
24769
24770 editType: 'plot',
24771
24772 },
24773 xaxes: {
24774 valType: 'info_array',
24775 freeLength: true,
24776 items: {valType: 'enumerated', values: [cartesianIdRegex.x.toString(), ''], editType: 'plot'},
24777
24778 editType: 'plot',
24779
24780 },
24781 yaxes: {
24782 valType: 'info_array',
24783 freeLength: true,
24784 items: {valType: 'enumerated', values: [cartesianIdRegex.y.toString(), ''], editType: 'plot'},
24785
24786 editType: 'plot',
24787
24788 },
24789 pattern: {
24790 valType: 'enumerated',
24791 values: ['independent', 'coupled'],
24792 dflt: 'coupled',
24793
24794 editType: 'plot',
24795
24796 },
24797 xgap: {
24798 valType: 'number',
24799 min: 0,
24800 max: 1,
24801
24802 editType: 'plot',
24803
24804 },
24805 ygap: {
24806 valType: 'number',
24807 min: 0,
24808 max: 1,
24809
24810 editType: 'plot',
24811
24812 },
24813 domain: domainAttrs({name: 'grid', editType: 'plot', noGridCell: true}, {
24814
24815 }),
24816 xside: {
24817 valType: 'enumerated',
24818 values: ['bottom', 'bottom plot', 'top plot', 'top'],
24819 dflt: 'bottom plot',
24820
24821 editType: 'plot',
24822
24823 },
24824 yside: {
24825 valType: 'enumerated',
24826 values: ['left', 'left plot', 'right plot', 'right'],
24827 dflt: 'left plot',
24828
24829 editType: 'plot',
24830
24831 },
24832 editType: 'plot'
24833};
24834
24835function getAxes(layout, grid, axLetter) {
24836 var gridVal = grid[axLetter + 'axes'];
24837 var splomVal = Object.keys((layout._splomAxes || {})[axLetter] || {});
24838
24839 if(Array.isArray(gridVal)) return gridVal;
24840 if(splomVal.length) return splomVal;
24841}
24842
24843// the shape of the grid - this needs to be done BEFORE supplyDataDefaults
24844// so that non-subplot traces can place themselves in the grid
24845function sizeDefaults(layoutIn, layoutOut) {
24846 var gridIn = layoutIn.grid || {};
24847 var xAxes = getAxes(layoutOut, gridIn, 'x');
24848 var yAxes = getAxes(layoutOut, gridIn, 'y');
24849
24850 if(!layoutIn.grid && !xAxes && !yAxes) return;
24851
24852 var hasSubplotGrid = Array.isArray(gridIn.subplots) && Array.isArray(gridIn.subplots[0]);
24853 var hasXaxes = Array.isArray(xAxes);
24854 var hasYaxes = Array.isArray(yAxes);
24855 var isSplomGenerated = (
24856 hasXaxes && xAxes !== gridIn.xaxes &&
24857 hasYaxes && yAxes !== gridIn.yaxes
24858 );
24859
24860 var dfltRows, dfltColumns;
24861
24862 if(hasSubplotGrid) {
24863 dfltRows = gridIn.subplots.length;
24864 dfltColumns = gridIn.subplots[0].length;
24865 } else {
24866 if(hasYaxes) dfltRows = yAxes.length;
24867 if(hasXaxes) dfltColumns = xAxes.length;
24868 }
24869
24870 var gridOut = Template.newContainer(layoutOut, 'grid');
24871
24872 function coerce(attr, dflt) {
24873 return Lib.coerce(gridIn, gridOut, gridAttrs, attr, dflt);
24874 }
24875
24876 var rows = coerce('rows', dfltRows);
24877 var columns = coerce('columns', dfltColumns);
24878
24879 if(!(rows * columns > 1)) {
24880 delete layoutOut.grid;
24881 return;
24882 }
24883
24884 if(!hasSubplotGrid && !hasXaxes && !hasYaxes) {
24885 var useDefaultSubplots = coerce('pattern') === 'independent';
24886 if(useDefaultSubplots) hasSubplotGrid = true;
24887 }
24888 gridOut._hasSubplotGrid = hasSubplotGrid;
24889
24890 var rowOrder = coerce('roworder');
24891 var reversed = rowOrder === 'top to bottom';
24892
24893 var dfltGapX = hasSubplotGrid ? 0.2 : 0.1;
24894 var dfltGapY = hasSubplotGrid ? 0.3 : 0.1;
24895
24896 var dfltSideX, dfltSideY;
24897 if(isSplomGenerated && layoutOut._splomGridDflt) {
24898 dfltSideX = layoutOut._splomGridDflt.xside;
24899 dfltSideY = layoutOut._splomGridDflt.yside;
24900 }
24901
24902 gridOut._domains = {
24903 x: fillGridPositions('x', coerce, dfltGapX, dfltSideX, columns),
24904 y: fillGridPositions('y', coerce, dfltGapY, dfltSideY, rows, reversed)
24905 };
24906}
24907
24908// coerce x or y sizing attributes and return an array of domains for this direction
24909function fillGridPositions(axLetter, coerce, dfltGap, dfltSide, len, reversed) {
24910 var dirGap = coerce(axLetter + 'gap', dfltGap);
24911 var domain = coerce('domain.' + axLetter);
24912 coerce(axLetter + 'side', dfltSide);
24913
24914 var out = new Array(len);
24915 var start = domain[0];
24916 var step = (domain[1] - start) / (len - dirGap);
24917 var cellDomain = step * (1 - dirGap);
24918 for(var i = 0; i < len; i++) {
24919 var cellStart = start + step * i;
24920 out[reversed ? (len - 1 - i) : i] = [cellStart, cellStart + cellDomain];
24921 }
24922 return out;
24923}
24924
24925// the (cartesian) contents of the grid - this needs to happen AFTER supplyDataDefaults
24926// so that we know what cartesian subplots are available
24927function contentDefaults(layoutIn, layoutOut) {
24928 var gridOut = layoutOut.grid;
24929 // make sure we got to the end of handleGridSizing
24930 if(!gridOut || !gridOut._domains) return;
24931
24932 var gridIn = layoutIn.grid || {};
24933 var subplots = layoutOut._subplots;
24934 var hasSubplotGrid = gridOut._hasSubplotGrid;
24935 var rows = gridOut.rows;
24936 var columns = gridOut.columns;
24937 var useDefaultSubplots = gridOut.pattern === 'independent';
24938
24939 var i, j, xId, yId, subplotId, subplotsOut, yPos;
24940
24941 var axisMap = gridOut._axisMap = {};
24942
24943 if(hasSubplotGrid) {
24944 var subplotsIn = gridIn.subplots || [];
24945 subplotsOut = gridOut.subplots = new Array(rows);
24946 var index = 1;
24947
24948 for(i = 0; i < rows; i++) {
24949 var rowOut = subplotsOut[i] = new Array(columns);
24950 var rowIn = subplotsIn[i] || [];
24951 for(j = 0; j < columns; j++) {
24952 if(useDefaultSubplots) {
24953 subplotId = (index === 1) ? 'xy' : ('x' + index + 'y' + index);
24954 index++;
24955 } else subplotId = rowIn[j];
24956
24957 rowOut[j] = '';
24958
24959 if(subplots.cartesian.indexOf(subplotId) !== -1) {
24960 yPos = subplotId.indexOf('y');
24961 xId = subplotId.slice(0, yPos);
24962 yId = subplotId.slice(yPos);
24963 if((axisMap[xId] !== undefined && axisMap[xId] !== j) ||
24964 (axisMap[yId] !== undefined && axisMap[yId] !== i)
24965 ) {
24966 continue;
24967 }
24968
24969 rowOut[j] = subplotId;
24970 axisMap[xId] = j;
24971 axisMap[yId] = i;
24972 }
24973 }
24974 }
24975 } else {
24976 var xAxes = getAxes(layoutOut, gridIn, 'x');
24977 var yAxes = getAxes(layoutOut, gridIn, 'y');
24978 gridOut.xaxes = fillGridAxes(xAxes, subplots.xaxis, columns, axisMap, 'x');
24979 gridOut.yaxes = fillGridAxes(yAxes, subplots.yaxis, rows, axisMap, 'y');
24980 }
24981
24982 var anchors = gridOut._anchors = {};
24983 var reversed = gridOut.roworder === 'top to bottom';
24984
24985 for(var axisId in axisMap) {
24986 var axLetter = axisId.charAt(0);
24987 var side = gridOut[axLetter + 'side'];
24988
24989 var i0, inc, iFinal;
24990
24991 if(side.length < 8) {
24992 // grid edge - ie not "* plot" - make these as free axes
24993 // since we're not guaranteed to have a subplot there at all
24994 anchors[axisId] = 'free';
24995 } else if(axLetter === 'x') {
24996 if((side.charAt(0) === 't') === reversed) {
24997 i0 = 0;
24998 inc = 1;
24999 iFinal = rows;
25000 } else {
25001 i0 = rows - 1;
25002 inc = -1;
25003 iFinal = -1;
25004 }
25005 if(hasSubplotGrid) {
25006 var column = axisMap[axisId];
25007 for(i = i0; i !== iFinal; i += inc) {
25008 subplotId = subplotsOut[i][column];
25009 if(!subplotId) continue;
25010 yPos = subplotId.indexOf('y');
25011 if(subplotId.slice(0, yPos) === axisId) {
25012 anchors[axisId] = subplotId.slice(yPos);
25013 break;
25014 }
25015 }
25016 } else {
25017 for(i = i0; i !== iFinal; i += inc) {
25018 yId = gridOut.yaxes[i];
25019 if(subplots.cartesian.indexOf(axisId + yId) !== -1) {
25020 anchors[axisId] = yId;
25021 break;
25022 }
25023 }
25024 }
25025 } else {
25026 if((side.charAt(0) === 'l')) {
25027 i0 = 0;
25028 inc = 1;
25029 iFinal = columns;
25030 } else {
25031 i0 = columns - 1;
25032 inc = -1;
25033 iFinal = -1;
25034 }
25035 if(hasSubplotGrid) {
25036 var row = axisMap[axisId];
25037 for(i = i0; i !== iFinal; i += inc) {
25038 subplotId = subplotsOut[row][i];
25039 if(!subplotId) continue;
25040 yPos = subplotId.indexOf('y');
25041 if(subplotId.slice(yPos) === axisId) {
25042 anchors[axisId] = subplotId.slice(0, yPos);
25043 break;
25044 }
25045 }
25046 } else {
25047 for(i = i0; i !== iFinal; i += inc) {
25048 xId = gridOut.xaxes[i];
25049 if(subplots.cartesian.indexOf(xId + axisId) !== -1) {
25050 anchors[axisId] = xId;
25051 break;
25052 }
25053 }
25054 }
25055 }
25056 }
25057}
25058
25059function fillGridAxes(axesIn, axesAllowed, len, axisMap, axLetter) {
25060 var out = new Array(len);
25061 var i;
25062
25063 function fillOneAxis(i, axisId) {
25064 if(axesAllowed.indexOf(axisId) !== -1 && axisMap[axisId] === undefined) {
25065 out[i] = axisId;
25066 axisMap[axisId] = i;
25067 } else out[i] = '';
25068 }
25069
25070 if(Array.isArray(axesIn)) {
25071 for(i = 0; i < len; i++) {
25072 fillOneAxis(i, axesIn[i]);
25073 }
25074 } else {
25075 // default axis list is the first `len` axis ids
25076 fillOneAxis(0, axLetter);
25077 for(i = 1; i < len; i++) {
25078 fillOneAxis(i, axLetter + (i + 1));
25079 }
25080 }
25081
25082 return out;
25083}
25084
25085module.exports = {
25086 moduleType: 'component',
25087 name: 'grid',
25088
25089 schema: {
25090 layout: {grid: gridAttrs}
25091 },
25092
25093 layoutAttributes: gridAttrs,
25094 sizeDefaults: sizeDefaults,
25095 contentDefaults: contentDefaults
25096};
25097
25098},{"../../lib":178,"../../lib/regex":193,"../../plot_api/plot_template":212,"../../plots/cartesian/constants":228,"../../plots/domain":249}],97:[function(_dereq_,module,exports){
25099/**
25100* Copyright 2012-2020, Plotly, Inc.
25101* All rights reserved.
25102*
25103* This source code is licensed under the MIT license found in the
25104* LICENSE file in the root directory of this source tree.
25105*/
25106
25107'use strict';
25108
25109var cartesianConstants = _dereq_('../../plots/cartesian/constants');
25110var templatedArray = _dereq_('../../plot_api/plot_template').templatedArray;
25111
25112
25113module.exports = templatedArray('image', {
25114 visible: {
25115 valType: 'boolean',
25116
25117 dflt: true,
25118 editType: 'arraydraw',
25119
25120 },
25121
25122 source: {
25123 valType: 'string',
25124
25125 editType: 'arraydraw',
25126
25127 },
25128
25129 layer: {
25130 valType: 'enumerated',
25131 values: ['below', 'above'],
25132 dflt: 'above',
25133
25134 editType: 'arraydraw',
25135
25136 },
25137
25138 sizex: {
25139 valType: 'number',
25140
25141 dflt: 0,
25142 editType: 'arraydraw',
25143
25144 },
25145
25146 sizey: {
25147 valType: 'number',
25148
25149 dflt: 0,
25150 editType: 'arraydraw',
25151
25152 },
25153
25154 sizing: {
25155 valType: 'enumerated',
25156 values: ['fill', 'contain', 'stretch'],
25157 dflt: 'contain',
25158
25159 editType: 'arraydraw',
25160
25161 },
25162
25163 opacity: {
25164 valType: 'number',
25165
25166 min: 0,
25167 max: 1,
25168 dflt: 1,
25169 editType: 'arraydraw',
25170
25171 },
25172
25173 x: {
25174 valType: 'any',
25175
25176 dflt: 0,
25177 editType: 'arraydraw',
25178
25179 },
25180
25181 y: {
25182 valType: 'any',
25183
25184 dflt: 0,
25185 editType: 'arraydraw',
25186
25187 },
25188
25189 xanchor: {
25190 valType: 'enumerated',
25191 values: ['left', 'center', 'right'],
25192 dflt: 'left',
25193
25194 editType: 'arraydraw',
25195
25196 },
25197
25198 yanchor: {
25199 valType: 'enumerated',
25200 values: ['top', 'middle', 'bottom'],
25201 dflt: 'top',
25202
25203 editType: 'arraydraw',
25204
25205 },
25206
25207 xref: {
25208 valType: 'enumerated',
25209 values: [
25210 'paper',
25211 cartesianConstants.idRegex.x.toString()
25212 ],
25213 dflt: 'paper',
25214
25215 editType: 'arraydraw',
25216
25217 },
25218
25219 yref: {
25220 valType: 'enumerated',
25221 values: [
25222 'paper',
25223 cartesianConstants.idRegex.y.toString()
25224 ],
25225 dflt: 'paper',
25226
25227 editType: 'arraydraw',
25228
25229 },
25230 editType: 'arraydraw'
25231});
25232
25233},{"../../plot_api/plot_template":212,"../../plots/cartesian/constants":228}],98:[function(_dereq_,module,exports){
25234/**
25235* Copyright 2012-2020, Plotly, Inc.
25236* All rights reserved.
25237*
25238* This source code is licensed under the MIT license found in the
25239* LICENSE file in the root directory of this source tree.
25240*/
25241
25242
25243'use strict';
25244
25245var isNumeric = _dereq_('fast-isnumeric');
25246var toLogRange = _dereq_('../../lib/to_log_range');
25247
25248/*
25249 * convertCoords: when converting an axis between log and linear
25250 * you need to alter any images on that axis to keep them
25251 * pointing at the same data point.
25252 * In v2.0 this will become obsolete (or perhaps size will still need conversion?)
25253 * we convert size by declaring that the maximum extent *in data units* should be
25254 * the same, assuming the image is anchored by its center (could remove that restriction
25255 * if we think it's important) even though the actual left and right values will not be
25256 * quite the same since the scale becomes nonlinear (and central anchor means the pixel
25257 * center of the image, not the data units center)
25258 *
25259 * gd: the plot div
25260 * ax: the axis being changed
25261 * newType: the type it's getting
25262 * doExtra: function(attr, val) from inside relayout that sets the attribute.
25263 * Use this to make the changes as it's aware if any other changes in the
25264 * same relayout call should override this conversion.
25265 */
25266module.exports = function convertCoords(gd, ax, newType, doExtra) {
25267 ax = ax || {};
25268
25269 var toLog = (newType === 'log') && (ax.type === 'linear');
25270 var fromLog = (newType === 'linear') && (ax.type === 'log');
25271
25272 if(!(toLog || fromLog)) return;
25273
25274 var images = gd._fullLayout.images;
25275 var axLetter = ax._id.charAt(0);
25276 var image;
25277 var attrPrefix;
25278
25279 for(var i = 0; i < images.length; i++) {
25280 image = images[i];
25281 attrPrefix = 'images[' + i + '].';
25282
25283 if(image[axLetter + 'ref'] === ax._id) {
25284 var currentPos = image[axLetter];
25285 var currentSize = image['size' + axLetter];
25286 var newPos = null;
25287 var newSize = null;
25288
25289 if(toLog) {
25290 newPos = toLogRange(currentPos, ax.range);
25291
25292 // this is the inverse of the conversion we do in fromLog below
25293 // so that the conversion is reversible (notice the fromLog conversion
25294 // is like sinh, and this one looks like arcsinh)
25295 var dx = currentSize / Math.pow(10, newPos) / 2;
25296 newSize = 2 * Math.log(dx + Math.sqrt(1 + dx * dx)) / Math.LN10;
25297 } else {
25298 newPos = Math.pow(10, currentPos);
25299 newSize = newPos * (Math.pow(10, currentSize / 2) - Math.pow(10, -currentSize / 2));
25300 }
25301
25302 // if conversion failed, delete the value so it can get a default later on
25303 if(!isNumeric(newPos)) {
25304 newPos = null;
25305 newSize = null;
25306 } else if(!isNumeric(newSize)) newSize = null;
25307
25308 doExtra(attrPrefix + axLetter, newPos);
25309 doExtra(attrPrefix + 'size' + axLetter, newSize);
25310 }
25311 }
25312};
25313
25314},{"../../lib/to_log_range":201,"fast-isnumeric":18}],99:[function(_dereq_,module,exports){
25315/**
25316* Copyright 2012-2020, Plotly, Inc.
25317* All rights reserved.
25318*
25319* This source code is licensed under the MIT license found in the
25320* LICENSE file in the root directory of this source tree.
25321*/
25322
25323'use strict';
25324
25325var Lib = _dereq_('../../lib');
25326var Axes = _dereq_('../../plots/cartesian/axes');
25327var handleArrayContainerDefaults = _dereq_('../../plots/array_container_defaults');
25328
25329var attributes = _dereq_('./attributes');
25330var name = 'images';
25331
25332module.exports = function supplyLayoutDefaults(layoutIn, layoutOut) {
25333 var opts = {
25334 name: name,
25335 handleItemDefaults: imageDefaults
25336 };
25337
25338 handleArrayContainerDefaults(layoutIn, layoutOut, opts);
25339};
25340
25341
25342function imageDefaults(imageIn, imageOut, fullLayout) {
25343 function coerce(attr, dflt) {
25344 return Lib.coerce(imageIn, imageOut, attributes, attr, dflt);
25345 }
25346
25347 var source = coerce('source');
25348 var visible = coerce('visible', !!source);
25349
25350 if(!visible) return imageOut;
25351
25352 coerce('layer');
25353 coerce('xanchor');
25354 coerce('yanchor');
25355 coerce('sizex');
25356 coerce('sizey');
25357 coerce('sizing');
25358 coerce('opacity');
25359
25360 var gdMock = { _fullLayout: fullLayout };
25361 var axLetters = ['x', 'y'];
25362
25363 for(var i = 0; i < 2; i++) {
25364 // 'paper' is the fallback axref
25365 var axLetter = axLetters[i];
25366 var axRef = Axes.coerceRef(imageIn, imageOut, gdMock, axLetter, 'paper');
25367
25368 if(axRef !== 'paper') {
25369 var ax = Axes.getFromId(gdMock, axRef);
25370 ax._imgIndices.push(imageOut._index);
25371 }
25372
25373 Axes.coercePosition(imageOut, gdMock, coerce, axRef, axLetter, 0);
25374 }
25375
25376 return imageOut;
25377}
25378
25379},{"../../lib":178,"../../plots/array_container_defaults":218,"../../plots/cartesian/axes":222,"./attributes":97}],100:[function(_dereq_,module,exports){
25380/**
25381* Copyright 2012-2020, Plotly, Inc.
25382* All rights reserved.
25383*
25384* This source code is licensed under the MIT license found in the
25385* LICENSE file in the root directory of this source tree.
25386*/
25387
25388'use strict';
25389
25390var d3 = _dereq_('d3');
25391var Drawing = _dereq_('../drawing');
25392var Axes = _dereq_('../../plots/cartesian/axes');
25393var xmlnsNamespaces = _dereq_('../../constants/xmlns_namespaces');
25394
25395module.exports = function draw(gd) {
25396 var fullLayout = gd._fullLayout;
25397 var imageDataAbove = [];
25398 var imageDataSubplot = {};
25399 var imageDataBelow = [];
25400 var subplot;
25401 var i;
25402
25403 // Sort into top, subplot, and bottom layers
25404 for(i = 0; i < fullLayout.images.length; i++) {
25405 var img = fullLayout.images[i];
25406
25407 if(img.visible) {
25408 if(img.layer === 'below' && img.xref !== 'paper' && img.yref !== 'paper') {
25409 subplot = img.xref + img.yref;
25410
25411 var plotinfo = fullLayout._plots[subplot];
25412
25413 if(!plotinfo) {
25414 // Fall back to _imageLowerLayer in case the requested subplot doesn't exist.
25415 // This can happen if you reference the image to an x / y axis combination
25416 // that doesn't have any data on it (and layer is below)
25417 imageDataBelow.push(img);
25418 continue;
25419 }
25420
25421 if(plotinfo.mainplot) {
25422 subplot = plotinfo.mainplot.id;
25423 }
25424
25425 if(!imageDataSubplot[subplot]) {
25426 imageDataSubplot[subplot] = [];
25427 }
25428 imageDataSubplot[subplot].push(img);
25429 } else if(img.layer === 'above') {
25430 imageDataAbove.push(img);
25431 } else {
25432 imageDataBelow.push(img);
25433 }
25434 }
25435 }
25436
25437
25438 var anchors = {
25439 x: {
25440 left: { sizing: 'xMin', offset: 0 },
25441 center: { sizing: 'xMid', offset: -1 / 2 },
25442 right: { sizing: 'xMax', offset: -1 }
25443 },
25444 y: {
25445 top: { sizing: 'YMin', offset: 0 },
25446 middle: { sizing: 'YMid', offset: -1 / 2 },
25447 bottom: { sizing: 'YMax', offset: -1 }
25448 }
25449 };
25450
25451
25452 // Images must be converted to dataURL's for exporting.
25453 function setImage(d) {
25454 var thisImage = d3.select(this);
25455
25456 if(this._imgSrc === d.source) {
25457 return;
25458 }
25459
25460 thisImage.attr('xmlns', xmlnsNamespaces.svg);
25461
25462 if(d.source && d.source.slice(0, 5) === 'data:') {
25463 thisImage.attr('xlink:href', d.source);
25464 this._imgSrc = d.source;
25465 } else {
25466 var imagePromise = new Promise(function(resolve) {
25467 var img = new Image();
25468 this.img = img;
25469
25470 // If not set, a `tainted canvas` error is thrown
25471 img.setAttribute('crossOrigin', 'anonymous');
25472 img.onerror = errorHandler;
25473 img.onload = function() {
25474 var canvas = document.createElement('canvas');
25475 canvas.width = this.width;
25476 canvas.height = this.height;
25477
25478 var ctx = canvas.getContext('2d');
25479 ctx.drawImage(this, 0, 0);
25480
25481 var dataURL = canvas.toDataURL('image/png');
25482
25483 thisImage.attr('xlink:href', dataURL);
25484
25485 // resolve promise in onload handler instead of on 'load' to support IE11
25486 // see https://github.com/plotly/plotly.js/issues/1685
25487 // for more details
25488 resolve();
25489 };
25490
25491 thisImage.on('error', errorHandler);
25492
25493 img.src = d.source;
25494 this._imgSrc = d.source;
25495
25496 function errorHandler() {
25497 thisImage.remove();
25498 resolve();
25499 }
25500 }.bind(this));
25501
25502 gd._promises.push(imagePromise);
25503 }
25504 }
25505
25506 function applyAttributes(d) {
25507 var thisImage = d3.select(this);
25508
25509 // Axes if specified
25510 var xa = Axes.getFromId(gd, d.xref);
25511 var ya = Axes.getFromId(gd, d.yref);
25512
25513 var size = fullLayout._size;
25514 var width = xa ? Math.abs(xa.l2p(d.sizex) - xa.l2p(0)) : d.sizex * size.w;
25515 var height = ya ? Math.abs(ya.l2p(d.sizey) - ya.l2p(0)) : d.sizey * size.h;
25516
25517 // Offsets for anchor positioning
25518 var xOffset = width * anchors.x[d.xanchor].offset;
25519 var yOffset = height * anchors.y[d.yanchor].offset;
25520
25521 var sizing = anchors.x[d.xanchor].sizing + anchors.y[d.yanchor].sizing;
25522
25523 // Final positions
25524 var xPos = (xa ? xa.r2p(d.x) + xa._offset : d.x * size.w + size.l) + xOffset;
25525 var yPos = (ya ? ya.r2p(d.y) + ya._offset : size.h - d.y * size.h + size.t) + yOffset;
25526
25527 // Construct the proper aspectRatio attribute
25528 switch(d.sizing) {
25529 case 'fill':
25530 sizing += ' slice';
25531 break;
25532
25533 case 'stretch':
25534 sizing = 'none';
25535 break;
25536 }
25537
25538 thisImage.attr({
25539 x: xPos,
25540 y: yPos,
25541 width: width,
25542 height: height,
25543 preserveAspectRatio: sizing,
25544 opacity: d.opacity
25545 });
25546
25547
25548 // Set proper clipping on images
25549 var xId = xa ? xa._id : '';
25550 var yId = ya ? ya._id : '';
25551 var clipAxes = xId + yId;
25552
25553 Drawing.setClipUrl(
25554 thisImage,
25555 clipAxes ? ('clip' + fullLayout._uid + clipAxes) : null,
25556 gd
25557 );
25558 }
25559
25560 var imagesBelow = fullLayout._imageLowerLayer.selectAll('image')
25561 .data(imageDataBelow);
25562 var imagesAbove = fullLayout._imageUpperLayer.selectAll('image')
25563 .data(imageDataAbove);
25564
25565 imagesBelow.enter().append('image');
25566 imagesAbove.enter().append('image');
25567
25568 imagesBelow.exit().remove();
25569 imagesAbove.exit().remove();
25570
25571 imagesBelow.each(function(d) {
25572 setImage.bind(this)(d);
25573 applyAttributes.bind(this)(d);
25574 });
25575 imagesAbove.each(function(d) {
25576 setImage.bind(this)(d);
25577 applyAttributes.bind(this)(d);
25578 });
25579
25580 var allSubplots = Object.keys(fullLayout._plots);
25581 for(i = 0; i < allSubplots.length; i++) {
25582 subplot = allSubplots[i];
25583 var subplotObj = fullLayout._plots[subplot];
25584
25585 // filter out overlaid plots (which havd their images on the main plot)
25586 // and gl2d plots (which don't support below images, at least not yet)
25587 if(!subplotObj.imagelayer) continue;
25588
25589 var imagesOnSubplot = subplotObj.imagelayer.selectAll('image')
25590 // even if there are no images on this subplot, we need to run
25591 // enter and exit in case there were previously
25592 .data(imageDataSubplot[subplot] || []);
25593
25594 imagesOnSubplot.enter().append('image');
25595 imagesOnSubplot.exit().remove();
25596
25597 imagesOnSubplot.each(function(d) {
25598 setImage.bind(this)(d);
25599 applyAttributes.bind(this)(d);
25600 });
25601 }
25602};
25603
25604},{"../../constants/xmlns_namespaces":159,"../../plots/cartesian/axes":222,"../drawing":74,"d3":16}],101:[function(_dereq_,module,exports){
25605/**
25606* Copyright 2012-2020, Plotly, Inc.
25607* All rights reserved.
25608*
25609* This source code is licensed under the MIT license found in the
25610* LICENSE file in the root directory of this source tree.
25611*/
25612
25613'use strict';
25614
25615module.exports = {
25616 moduleType: 'component',
25617 name: 'images',
25618
25619 layoutAttributes: _dereq_('./attributes'),
25620 supplyLayoutDefaults: _dereq_('./defaults'),
25621 includeBasePlot: _dereq_('../../plots/cartesian/include_components')('images'),
25622
25623 draw: _dereq_('./draw'),
25624
25625 convertCoords: _dereq_('./convert_coords')
25626};
25627
25628},{"../../plots/cartesian/include_components":234,"./attributes":97,"./convert_coords":98,"./defaults":99,"./draw":100}],102:[function(_dereq_,module,exports){
25629/**
25630* Copyright 2012-2020, Plotly, Inc.
25631* All rights reserved.
25632*
25633* This source code is licensed under the MIT license found in the
25634* LICENSE file in the root directory of this source tree.
25635*/
25636
25637'use strict';
25638
25639var fontAttrs = _dereq_('../../plots/font_attributes');
25640var colorAttrs = _dereq_('../color/attributes');
25641
25642
25643module.exports = {
25644 bgcolor: {
25645 valType: 'color',
25646
25647 editType: 'legend',
25648
25649 },
25650 bordercolor: {
25651 valType: 'color',
25652 dflt: colorAttrs.defaultLine,
25653
25654 editType: 'legend',
25655
25656 },
25657 borderwidth: {
25658 valType: 'number',
25659 min: 0,
25660 dflt: 0,
25661
25662 editType: 'legend',
25663
25664 },
25665 font: fontAttrs({
25666 editType: 'legend',
25667
25668 }),
25669 orientation: {
25670 valType: 'enumerated',
25671 values: ['v', 'h'],
25672 dflt: 'v',
25673
25674 editType: 'legend',
25675
25676 },
25677 traceorder: {
25678 valType: 'flaglist',
25679 flags: ['reversed', 'grouped'],
25680 extras: ['normal'],
25681
25682 editType: 'legend',
25683
25684 },
25685 tracegroupgap: {
25686 valType: 'number',
25687 min: 0,
25688 dflt: 10,
25689
25690 editType: 'legend',
25691
25692 },
25693 itemsizing: {
25694 valType: 'enumerated',
25695 values: ['trace', 'constant'],
25696 dflt: 'trace',
25697
25698 editType: 'legend',
25699
25700 },
25701
25702 itemclick: {
25703 valType: 'enumerated',
25704 values: ['toggle', 'toggleothers', false],
25705 dflt: 'toggle',
25706
25707 editType: 'legend',
25708
25709 },
25710 itemdoubleclick: {
25711 valType: 'enumerated',
25712 values: ['toggle', 'toggleothers', false],
25713 dflt: 'toggleothers',
25714
25715 editType: 'legend',
25716
25717 },
25718
25719 x: {
25720 valType: 'number',
25721 min: -2,
25722 max: 3,
25723
25724 editType: 'legend',
25725
25726 },
25727 xanchor: {
25728 valType: 'enumerated',
25729 values: ['auto', 'left', 'center', 'right'],
25730 dflt: 'left',
25731
25732 editType: 'legend',
25733
25734 },
25735 y: {
25736 valType: 'number',
25737 min: -2,
25738 max: 3,
25739
25740 editType: 'legend',
25741
25742 },
25743 yanchor: {
25744 valType: 'enumerated',
25745 values: ['auto', 'top', 'middle', 'bottom'],
25746
25747 editType: 'legend',
25748
25749 },
25750 uirevision: {
25751 valType: 'any',
25752
25753 editType: 'none',
25754
25755 },
25756 valign: {
25757 valType: 'enumerated',
25758 values: ['top', 'middle', 'bottom'],
25759 dflt: 'middle',
25760
25761 editType: 'legend',
25762
25763 },
25764 title: {
25765 text: {
25766 valType: 'string',
25767 dflt: '',
25768
25769 editType: 'legend',
25770
25771 },
25772 font: fontAttrs({
25773 editType: 'legend',
25774
25775 }),
25776 side: {
25777 valType: 'enumerated',
25778 values: ['top', 'left', 'top left'],
25779
25780 editType: 'legend',
25781
25782 },
25783 editType: 'legend',
25784 },
25785
25786 editType: 'legend'
25787};
25788
25789},{"../../plots/font_attributes":250,"../color/attributes":51}],103:[function(_dereq_,module,exports){
25790/**
25791* Copyright 2012-2020, Plotly, Inc.
25792* All rights reserved.
25793*
25794* This source code is licensed under the MIT license found in the
25795* LICENSE file in the root directory of this source tree.
25796*/
25797
25798'use strict';
25799
25800module.exports = {
25801 scrollBarWidth: 6,
25802 scrollBarMinHeight: 20,
25803 scrollBarColor: '#808BA4',
25804 scrollBarMargin: 4,
25805 scrollBarEnterAttrs: {rx: 20, ry: 3, width: 0, height: 0},
25806
25807 // number of px between legend title and (left) side of legend (always in x direction and from inner border)
25808 titlePad: 2,
25809 // number of px between legend symbol and legend text (always in x direction)
25810 textGap: 40,
25811 // number of px between each legend item (x and/or y direction)
25812 itemGap: 5
25813};
25814
25815},{}],104:[function(_dereq_,module,exports){
25816/**
25817* Copyright 2012-2020, Plotly, Inc.
25818* All rights reserved.
25819*
25820* This source code is licensed under the MIT license found in the
25821* LICENSE file in the root directory of this source tree.
25822*/
25823
25824'use strict';
25825
25826var Registry = _dereq_('../../registry');
25827var Lib = _dereq_('../../lib');
25828var Template = _dereq_('../../plot_api/plot_template');
25829
25830var attributes = _dereq_('./attributes');
25831var basePlotLayoutAttributes = _dereq_('../../plots/layout_attributes');
25832var helpers = _dereq_('./helpers');
25833
25834
25835module.exports = function legendDefaults(layoutIn, layoutOut, fullData) {
25836 var containerIn = layoutIn.legend || {};
25837
25838 var legendTraceCount = 0;
25839 var legendReallyHasATrace = false;
25840 var defaultOrder = 'normal';
25841
25842 for(var i = 0; i < fullData.length; i++) {
25843 var trace = fullData[i];
25844
25845 if(!trace.visible) continue;
25846
25847 // Note that we explicitly count any trace that is either shown or
25848 // *would* be shown by default, toward the two traces you need to
25849 // ensure the legend is shown by default, because this can still help
25850 // disambiguate.
25851 if(trace.showlegend || (
25852 trace._dfltShowLegend && !(
25853 trace._module &&
25854 trace._module.attributes &&
25855 trace._module.attributes.showlegend &&
25856 trace._module.attributes.showlegend.dflt === false
25857 )
25858 )) {
25859 legendTraceCount++;
25860 if(trace.showlegend) {
25861 legendReallyHasATrace = true;
25862 // Always show the legend by default if there's a pie,
25863 // or if there's only one trace but it's explicitly shown
25864 if(Registry.traceIs(trace, 'pie-like') ||
25865 trace._input.showlegend === true
25866 ) {
25867 legendTraceCount++;
25868 }
25869 }
25870 }
25871
25872 if((Registry.traceIs(trace, 'bar') && layoutOut.barmode === 'stack') ||
25873 ['tonextx', 'tonexty'].indexOf(trace.fill) !== -1) {
25874 defaultOrder = helpers.isGrouped({traceorder: defaultOrder}) ?
25875 'grouped+reversed' : 'reversed';
25876 }
25877
25878 if(trace.legendgroup !== undefined && trace.legendgroup !== '') {
25879 defaultOrder = helpers.isReversed({traceorder: defaultOrder}) ?
25880 'reversed+grouped' : 'grouped';
25881 }
25882 }
25883
25884 var showLegend = Lib.coerce(layoutIn, layoutOut,
25885 basePlotLayoutAttributes, 'showlegend',
25886 legendReallyHasATrace && legendTraceCount > 1);
25887
25888 if(showLegend === false && !containerIn.uirevision) return;
25889
25890 var containerOut = Template.newContainer(layoutOut, 'legend');
25891
25892 function coerce(attr, dflt) {
25893 return Lib.coerce(containerIn, containerOut, attributes, attr, dflt);
25894 }
25895
25896 coerce('uirevision', layoutOut.uirevision);
25897
25898 if(showLegend === false) return;
25899
25900 coerce('bgcolor', layoutOut.paper_bgcolor);
25901 coerce('bordercolor');
25902 coerce('borderwidth');
25903 Lib.coerceFont(coerce, 'font', layoutOut.font);
25904
25905 var orientation = coerce('orientation');
25906 var defaultX, defaultY, defaultYAnchor;
25907
25908 if(orientation === 'h') {
25909 defaultX = 0;
25910
25911 if(Registry.getComponentMethod('rangeslider', 'isVisible')(layoutIn.xaxis)) {
25912 defaultY = 1.1;
25913 defaultYAnchor = 'bottom';
25914 } else {
25915 // maybe use y=1.1 / yanchor=bottom as above
25916 // to avoid https://github.com/plotly/plotly.js/issues/1199
25917 // in v2
25918 defaultY = -0.1;
25919 defaultYAnchor = 'top';
25920 }
25921 } else {
25922 defaultX = 1.02;
25923 defaultY = 1;
25924 defaultYAnchor = 'auto';
25925 }
25926
25927 coerce('traceorder', defaultOrder);
25928 if(helpers.isGrouped(layoutOut.legend)) coerce('tracegroupgap');
25929
25930 coerce('itemsizing');
25931
25932 coerce('itemclick');
25933 coerce('itemdoubleclick');
25934
25935 coerce('x', defaultX);
25936 coerce('xanchor');
25937 coerce('y', defaultY);
25938 coerce('yanchor', defaultYAnchor);
25939 coerce('valign');
25940 Lib.noneOrAll(containerIn, containerOut, ['x', 'y']);
25941
25942 var titleText = coerce('title.text');
25943 if(titleText) {
25944 coerce('title.side', orientation === 'h' ? 'left' : 'top');
25945 Lib.coerceFont(coerce, 'title.font', layoutOut.font);
25946 }
25947};
25948
25949},{"../../lib":178,"../../plot_api/plot_template":212,"../../plots/layout_attributes":254,"../../registry":269,"./attributes":102,"./helpers":108}],105:[function(_dereq_,module,exports){
25950/**
25951* Copyright 2012-2020, Plotly, Inc.
25952* All rights reserved.
25953*
25954* This source code is licensed under the MIT license found in the
25955* LICENSE file in the root directory of this source tree.
25956*/
25957
25958'use strict';
25959
25960var d3 = _dereq_('d3');
25961
25962var Lib = _dereq_('../../lib');
25963var Plots = _dereq_('../../plots/plots');
25964var Registry = _dereq_('../../registry');
25965var Events = _dereq_('../../lib/events');
25966var dragElement = _dereq_('../dragelement');
25967var Drawing = _dereq_('../drawing');
25968var Color = _dereq_('../color');
25969var svgTextUtils = _dereq_('../../lib/svg_text_utils');
25970var handleClick = _dereq_('./handle_click');
25971
25972var constants = _dereq_('./constants');
25973var alignmentConstants = _dereq_('../../constants/alignment');
25974var LINE_SPACING = alignmentConstants.LINE_SPACING;
25975var FROM_TL = alignmentConstants.FROM_TL;
25976var FROM_BR = alignmentConstants.FROM_BR;
25977
25978var getLegendData = _dereq_('./get_legend_data');
25979var style = _dereq_('./style');
25980var helpers = _dereq_('./helpers');
25981
25982module.exports = function draw(gd, opts) {
25983 var fullLayout = gd._fullLayout;
25984 var clipId = 'legend' + fullLayout._uid;
25985 var layer;
25986
25987 // Check whether this is the main legend (ie. called without any opts)
25988 if(!opts) {
25989 opts = fullLayout.legend || {};
25990 opts._main = true;
25991 layer = fullLayout._infolayer;
25992 } else {
25993 layer = opts.layer;
25994 clipId += '-hover';
25995 }
25996
25997 if(!layer) return;
25998
25999 if(!gd._legendMouseDownTime) gd._legendMouseDownTime = 0;
26000
26001 var legendData;
26002 if(opts._main) {
26003 if(!gd.calcdata) return;
26004 legendData = fullLayout.showlegend && getLegendData(gd.calcdata, opts);
26005 } else {
26006 if(!opts.entries) return;
26007 legendData = getLegendData(opts.entries, opts);
26008 }
26009
26010 var hiddenSlices = fullLayout.hiddenlabels || [];
26011
26012 if(opts._main && (!fullLayout.showlegend || !legendData.length)) {
26013 layer.selectAll('.legend').remove();
26014 fullLayout._topdefs.select('#' + clipId).remove();
26015 return Plots.autoMargin(gd, 'legend');
26016 }
26017
26018 var legend = Lib.ensureSingle(layer, 'g', 'legend', function(s) {
26019 if(opts._main) s.attr('pointer-events', 'all');
26020 });
26021
26022 var clipPath = Lib.ensureSingleById(fullLayout._topdefs, 'clipPath', clipId, function(s) {
26023 s.append('rect');
26024 });
26025
26026 var bg = Lib.ensureSingle(legend, 'rect', 'bg', function(s) {
26027 s.attr('shape-rendering', 'crispEdges');
26028 });
26029 bg.call(Color.stroke, opts.bordercolor)
26030 .call(Color.fill, opts.bgcolor)
26031 .style('stroke-width', opts.borderwidth + 'px');
26032
26033 var scrollBox = Lib.ensureSingle(legend, 'g', 'scrollbox');
26034
26035 var title = opts.title;
26036 opts._titleWidth = 0;
26037 opts._titleHeight = 0;
26038 if(title.text) {
26039 var titleEl = Lib.ensureSingle(scrollBox, 'text', 'legendtitletext');
26040 titleEl.attr('text-anchor', 'start')
26041 .classed('user-select-none', true)
26042 .call(Drawing.font, title.font)
26043 .text(title.text);
26044
26045 textLayout(titleEl, scrollBox, gd, opts); // handle mathjax or multi-line text and compute title height
26046 } else {
26047 scrollBox.selectAll('.legendtitletext').remove();
26048 }
26049
26050 var scrollBar = Lib.ensureSingle(legend, 'rect', 'scrollbar', function(s) {
26051 s.attr(constants.scrollBarEnterAttrs)
26052 .call(Color.fill, constants.scrollBarColor);
26053 });
26054
26055 var groups = scrollBox.selectAll('g.groups').data(legendData);
26056 groups.enter().append('g').attr('class', 'groups');
26057 groups.exit().remove();
26058
26059 var traces = groups.selectAll('g.traces').data(Lib.identity);
26060 traces.enter().append('g').attr('class', 'traces');
26061 traces.exit().remove();
26062
26063 traces.style('opacity', function(d) {
26064 var trace = d[0].trace;
26065 if(Registry.traceIs(trace, 'pie-like')) {
26066 return hiddenSlices.indexOf(d[0].label) !== -1 ? 0.5 : 1;
26067 } else {
26068 return trace.visible === 'legendonly' ? 0.5 : 1;
26069 }
26070 })
26071 .each(function() { d3.select(this).call(drawTexts, gd, opts); })
26072 .call(style, gd, opts)
26073 .each(function() { if(opts._main) d3.select(this).call(setupTraceToggle, gd); });
26074
26075 Lib.syncOrAsync([
26076 Plots.previousPromises,
26077 function() { return computeLegendDimensions(gd, groups, traces, opts); },
26078 function() {
26079 // IF expandMargin return a Promise (which is truthy),
26080 // we're under a doAutoMargin redraw, so we don't have to
26081 // draw the remaining pieces below
26082 if(opts._main && expandMargin(gd)) return;
26083
26084 var gs = fullLayout._size;
26085 var bw = opts.borderwidth;
26086
26087 var lx = gs.l + gs.w * opts.x - FROM_TL[getXanchor(opts)] * opts._width;
26088 var ly = gs.t + gs.h * (1 - opts.y) - FROM_TL[getYanchor(opts)] * opts._effHeight;
26089
26090 if(opts._main && fullLayout.margin.autoexpand) {
26091 var lx0 = lx;
26092 var ly0 = ly;
26093
26094 lx = Lib.constrain(lx, 0, fullLayout.width - opts._width);
26095 ly = Lib.constrain(ly, 0, fullLayout.height - opts._effHeight);
26096
26097 if(lx !== lx0) {
26098 Lib.log('Constrain legend.x to make legend fit inside graph');
26099 }
26100 if(ly !== ly0) {
26101 Lib.log('Constrain legend.y to make legend fit inside graph');
26102 }
26103 }
26104
26105 // Set size and position of all the elements that make up a legend:
26106 // legend, background and border, scroll box and scroll bar as well as title
26107 if(opts._main) Drawing.setTranslate(legend, lx, ly);
26108
26109 // to be safe, remove previous listeners
26110 scrollBar.on('.drag', null);
26111 legend.on('wheel', null);
26112
26113 if(!opts._main || opts._height <= opts._maxHeight || gd._context.staticPlot) {
26114 // if scrollbar should not be shown.
26115 var height = opts._effHeight;
26116
26117 // if not the main legend, let it be its full size
26118 if(!opts._main) height = opts._height;
26119
26120 bg.attr({
26121 width: opts._width - bw,
26122 height: height - bw,
26123 x: bw / 2,
26124 y: bw / 2
26125 });
26126
26127 Drawing.setTranslate(scrollBox, 0, 0);
26128
26129 clipPath.select('rect').attr({
26130 width: opts._width - 2 * bw,
26131 height: height - 2 * bw,
26132 x: bw,
26133 y: bw
26134 });
26135
26136 Drawing.setClipUrl(scrollBox, clipId, gd);
26137
26138 Drawing.setRect(scrollBar, 0, 0, 0, 0);
26139 delete opts._scrollY;
26140 } else {
26141 var scrollBarHeight = Math.max(constants.scrollBarMinHeight,
26142 opts._effHeight * opts._effHeight / opts._height);
26143 var scrollBarYMax = opts._effHeight -
26144 scrollBarHeight -
26145 2 * constants.scrollBarMargin;
26146 var scrollBoxYMax = opts._height - opts._effHeight;
26147 var scrollRatio = scrollBarYMax / scrollBoxYMax;
26148
26149 var scrollBoxY = Math.min(opts._scrollY || 0, scrollBoxYMax);
26150
26151 // increase the background and clip-path width
26152 // by the scrollbar width and margin
26153 bg.attr({
26154 width: opts._width -
26155 2 * bw +
26156 constants.scrollBarWidth +
26157 constants.scrollBarMargin,
26158 height: opts._effHeight - bw,
26159 x: bw / 2,
26160 y: bw / 2
26161 });
26162
26163 clipPath.select('rect').attr({
26164 width: opts._width -
26165 2 * bw +
26166 constants.scrollBarWidth +
26167 constants.scrollBarMargin,
26168 height: opts._effHeight - 2 * bw,
26169 x: bw,
26170 y: bw + scrollBoxY
26171 });
26172
26173 Drawing.setClipUrl(scrollBox, clipId, gd);
26174
26175 scrollHandler(scrollBoxY, scrollBarHeight, scrollRatio);
26176
26177 // scroll legend by mousewheel or touchpad swipe up/down
26178 legend.on('wheel', function() {
26179 scrollBoxY = Lib.constrain(
26180 opts._scrollY +
26181 ((d3.event.deltaY / scrollBarYMax) * scrollBoxYMax),
26182 0, scrollBoxYMax);
26183 scrollHandler(scrollBoxY, scrollBarHeight, scrollRatio);
26184 if(scrollBoxY !== 0 && scrollBoxY !== scrollBoxYMax) {
26185 d3.event.preventDefault();
26186 }
26187 });
26188
26189 var eventY0, eventY1, scrollBoxY0;
26190
26191 var getScrollBarDragY = function(scrollBoxY0, eventY0, eventY1) {
26192 var y = ((eventY1 - eventY0) / scrollRatio) + scrollBoxY0;
26193 return Lib.constrain(y, 0, scrollBoxYMax);
26194 };
26195
26196 var getNaturalDragY = function(scrollBoxY0, eventY0, eventY1) {
26197 var y = ((eventY0 - eventY1) / scrollRatio) + scrollBoxY0;
26198 return Lib.constrain(y, 0, scrollBoxYMax);
26199 };
26200
26201 // scroll legend by dragging scrollBAR
26202 var scrollBarDrag = d3.behavior.drag()
26203 .on('dragstart', function() {
26204 var e = d3.event.sourceEvent;
26205 if(e.type === 'touchstart') {
26206 eventY0 = e.changedTouches[0].clientY;
26207 } else {
26208 eventY0 = e.clientY;
26209 }
26210 scrollBoxY0 = scrollBoxY;
26211 })
26212 .on('drag', function() {
26213 var e = d3.event.sourceEvent;
26214 if(e.buttons === 2 || e.ctrlKey) return;
26215 if(e.type === 'touchmove') {
26216 eventY1 = e.changedTouches[0].clientY;
26217 } else {
26218 eventY1 = e.clientY;
26219 }
26220 scrollBoxY = getScrollBarDragY(scrollBoxY0, eventY0, eventY1);
26221 scrollHandler(scrollBoxY, scrollBarHeight, scrollRatio);
26222 });
26223 scrollBar.call(scrollBarDrag);
26224
26225 // scroll legend by touch-dragging scrollBOX
26226 var scrollBoxTouchDrag = d3.behavior.drag()
26227 .on('dragstart', function() {
26228 var e = d3.event.sourceEvent;
26229 if(e.type === 'touchstart') {
26230 eventY0 = e.changedTouches[0].clientY;
26231 scrollBoxY0 = scrollBoxY;
26232 }
26233 })
26234 .on('drag', function() {
26235 var e = d3.event.sourceEvent;
26236 if(e.type === 'touchmove') {
26237 eventY1 = e.changedTouches[0].clientY;
26238 scrollBoxY = getNaturalDragY(scrollBoxY0, eventY0, eventY1);
26239 scrollHandler(scrollBoxY, scrollBarHeight, scrollRatio);
26240 }
26241 });
26242 scrollBox.call(scrollBoxTouchDrag);
26243 }
26244
26245 function scrollHandler(scrollBoxY, scrollBarHeight, scrollRatio) {
26246 opts._scrollY = gd._fullLayout.legend._scrollY = scrollBoxY;
26247 Drawing.setTranslate(scrollBox, 0, -scrollBoxY);
26248
26249 Drawing.setRect(
26250 scrollBar,
26251 opts._width,
26252 constants.scrollBarMargin + scrollBoxY * scrollRatio,
26253 constants.scrollBarWidth,
26254 scrollBarHeight
26255 );
26256 clipPath.select('rect').attr('y', bw + scrollBoxY);
26257 }
26258
26259 if(gd._context.edits.legendPosition) {
26260 var xf, yf, x0, y0;
26261
26262 legend.classed('cursor-move', true);
26263
26264 dragElement.init({
26265 element: legend.node(),
26266 gd: gd,
26267 prepFn: function() {
26268 var transform = Drawing.getTranslate(legend);
26269 x0 = transform.x;
26270 y0 = transform.y;
26271 },
26272 moveFn: function(dx, dy) {
26273 var newX = x0 + dx;
26274 var newY = y0 + dy;
26275
26276 Drawing.setTranslate(legend, newX, newY);
26277
26278 xf = dragElement.align(newX, 0, gs.l, gs.l + gs.w, opts.xanchor);
26279 yf = dragElement.align(newY, 0, gs.t + gs.h, gs.t, opts.yanchor);
26280 },
26281 doneFn: function() {
26282 if(xf !== undefined && yf !== undefined) {
26283 Registry.call('_guiRelayout', gd, {'legend.x': xf, 'legend.y': yf});
26284 }
26285 },
26286 clickFn: function(numClicks, e) {
26287 var clickedTrace = layer.selectAll('g.traces').filter(function() {
26288 var bbox = this.getBoundingClientRect();
26289 return (
26290 e.clientX >= bbox.left && e.clientX <= bbox.right &&
26291 e.clientY >= bbox.top && e.clientY <= bbox.bottom
26292 );
26293 });
26294 if(clickedTrace.size() > 0) {
26295 clickOrDoubleClick(gd, legend, clickedTrace, numClicks, e);
26296 }
26297 }
26298 });
26299 }
26300 }], gd);
26301};
26302
26303function clickOrDoubleClick(gd, legend, legendItem, numClicks, evt) {
26304 var trace = legendItem.data()[0][0].trace;
26305 var evtData = {
26306 event: evt,
26307 node: legendItem.node(),
26308 curveNumber: trace.index,
26309 expandedIndex: trace._expandedIndex,
26310 data: gd.data,
26311 layout: gd.layout,
26312 frames: gd._transitionData._frames,
26313 config: gd._context,
26314 fullData: gd._fullData,
26315 fullLayout: gd._fullLayout
26316 };
26317
26318 if(trace._group) {
26319 evtData.group = trace._group;
26320 }
26321 if(Registry.traceIs(trace, 'pie-like')) {
26322 evtData.label = legendItem.datum()[0].label;
26323 }
26324
26325 var clickVal = Events.triggerHandler(gd, 'plotly_legendclick', evtData);
26326 if(clickVal === false) return;
26327
26328 if(numClicks === 1) {
26329 legend._clickTimeout = setTimeout(function() {
26330 handleClick(legendItem, gd, numClicks);
26331 }, gd._context.doubleClickDelay);
26332 } else if(numClicks === 2) {
26333 if(legend._clickTimeout) clearTimeout(legend._clickTimeout);
26334 gd._legendMouseDownTime = 0;
26335
26336 var dblClickVal = Events.triggerHandler(gd, 'plotly_legenddoubleclick', evtData);
26337 if(dblClickVal !== false) handleClick(legendItem, gd, numClicks);
26338 }
26339}
26340
26341function drawTexts(g, gd, opts) {
26342 var legendItem = g.data()[0][0];
26343 var trace = legendItem.trace;
26344 var isPieLike = Registry.traceIs(trace, 'pie-like');
26345 var traceIndex = trace.index;
26346 var isEditable = opts._main && gd._context.edits.legendText && !isPieLike;
26347 var maxNameLength = opts._maxNameLength;
26348
26349 var name;
26350 if(!opts.entries) {
26351 name = isPieLike ? legendItem.label : trace.name;
26352 if(trace._meta) {
26353 name = Lib.templateString(name, trace._meta);
26354 }
26355 } else {
26356 name = legendItem.text;
26357 }
26358
26359 var textEl = Lib.ensureSingle(g, 'text', 'legendtext');
26360
26361 textEl.attr('text-anchor', 'start')
26362 .classed('user-select-none', true)
26363 .call(Drawing.font, opts.font)
26364 .text(isEditable ? ensureLength(name, maxNameLength) : name);
26365
26366 svgTextUtils.positionText(textEl, constants.textGap, 0);
26367
26368 if(isEditable) {
26369 textEl.call(svgTextUtils.makeEditable, {gd: gd, text: name})
26370 .call(textLayout, g, gd, opts)
26371 .on('edit', function(newName) {
26372 this.text(ensureLength(newName, maxNameLength))
26373 .call(textLayout, g, gd, opts);
26374
26375 var fullInput = legendItem.trace._fullInput || {};
26376 var update = {};
26377
26378 if(Registry.hasTransform(fullInput, 'groupby')) {
26379 var groupbyIndices = Registry.getTransformIndices(fullInput, 'groupby');
26380 var index = groupbyIndices[groupbyIndices.length - 1];
26381
26382 var kcont = Lib.keyedContainer(fullInput, 'transforms[' + index + '].styles', 'target', 'value.name');
26383
26384 kcont.set(legendItem.trace._group, newName);
26385
26386 update = kcont.constructUpdate();
26387 } else {
26388 update.name = newName;
26389 }
26390
26391 return Registry.call('_guiRestyle', gd, update, traceIndex);
26392 });
26393 } else {
26394 textLayout(textEl, g, gd, opts);
26395 }
26396}
26397
26398/*
26399 * Make sure we have a reasonably clickable region.
26400 * If this string is missing or very short, pad it with spaces out to at least
26401 * 4 characters, up to the max length of other labels, on the assumption that
26402 * most characters are wider than spaces so a string of spaces will usually be
26403 * no wider than the real labels.
26404 */
26405function ensureLength(str, maxLength) {
26406 var targetLength = Math.max(4, maxLength);
26407 if(str && str.trim().length >= targetLength / 2) return str;
26408 str = str || '';
26409 for(var i = targetLength - str.length; i > 0; i--) str += ' ';
26410 return str;
26411}
26412
26413function setupTraceToggle(g, gd) {
26414 var doubleClickDelay = gd._context.doubleClickDelay;
26415 var newMouseDownTime;
26416 var numClicks = 1;
26417
26418 var traceToggle = Lib.ensureSingle(g, 'rect', 'legendtoggle', function(s) {
26419 s.style('cursor', 'pointer')
26420 .attr('pointer-events', 'all')
26421 .call(Color.fill, 'rgba(0,0,0,0)');
26422 });
26423
26424 traceToggle.on('mousedown', function() {
26425 newMouseDownTime = (new Date()).getTime();
26426 if(newMouseDownTime - gd._legendMouseDownTime < doubleClickDelay) {
26427 // in a click train
26428 numClicks += 1;
26429 } else {
26430 // new click train
26431 numClicks = 1;
26432 gd._legendMouseDownTime = newMouseDownTime;
26433 }
26434 });
26435 traceToggle.on('mouseup', function() {
26436 if(gd._dragged || gd._editing) return;
26437 var legend = gd._fullLayout.legend;
26438
26439 if((new Date()).getTime() - gd._legendMouseDownTime > doubleClickDelay) {
26440 numClicks = Math.max(numClicks - 1, 1);
26441 }
26442
26443 clickOrDoubleClick(gd, legend, g, numClicks, d3.event);
26444 });
26445}
26446
26447function textLayout(s, g, gd, opts) {
26448 if(!opts._main) s.attr('data-notex', true); // do not process MathJax if not main
26449 svgTextUtils.convertToTspans(s, gd, function() {
26450 computeTextDimensions(g, gd, opts);
26451 });
26452}
26453
26454function computeTextDimensions(g, gd, opts) {
26455 var legendItem = g.data()[0][0];
26456 if(opts._main && legendItem && !legendItem.trace.showlegend) {
26457 g.remove();
26458 return;
26459 }
26460
26461 var mathjaxGroup = g.select('g[class*=math-group]');
26462 var mathjaxNode = mathjaxGroup.node();
26463 if(!opts) opts = gd._fullLayout.legend;
26464 var bw = opts.borderwidth;
26465 var lineHeight = (legendItem ? opts : opts.title).font.size * LINE_SPACING;
26466 var height, width;
26467
26468 if(mathjaxNode) {
26469 var mathjaxBB = Drawing.bBox(mathjaxNode);
26470
26471 height = mathjaxBB.height;
26472 width = mathjaxBB.width;
26473
26474 if(legendItem) {
26475 Drawing.setTranslate(mathjaxGroup, 0, height * 0.25);
26476 } else { // case of title
26477 Drawing.setTranslate(mathjaxGroup, bw, height * 0.75 + bw);
26478 }
26479 } else {
26480 var textEl = g.select(legendItem ?
26481 '.legendtext' : '.legendtitletext'
26482 );
26483 var textLines = svgTextUtils.lineCount(textEl);
26484 var textNode = textEl.node();
26485
26486 height = lineHeight * textLines;
26487 width = textNode ? Drawing.bBox(textNode).width : 0;
26488
26489 // approximation to height offset to center the font
26490 // to avoid getBoundingClientRect
26491 var textY = lineHeight * ((textLines - 1) / 2 - 0.3);
26492 if(legendItem) {
26493 svgTextUtils.positionText(textEl, constants.textGap, -textY);
26494 } else { // case of title
26495 svgTextUtils.positionText(textEl, constants.titlePad + bw, lineHeight + bw);
26496 }
26497 }
26498
26499 if(legendItem) {
26500 legendItem.lineHeight = lineHeight;
26501 legendItem.height = Math.max(height, 16) + 3;
26502 legendItem.width = width;
26503 } else { // case of title
26504 opts._titleWidth = width;
26505 opts._titleHeight = height;
26506 }
26507}
26508
26509function getTitleSize(opts) {
26510 var w = 0;
26511 var h = 0;
26512
26513 var side = opts.title.side;
26514 if(side) {
26515 if(side.indexOf('left') !== -1) {
26516 w = opts._titleWidth;
26517 }
26518 if(side.indexOf('top') !== -1) {
26519 h = opts._titleHeight;
26520 }
26521 }
26522
26523 return [w, h];
26524}
26525
26526/*
26527 * Computes in fullLayout.legend:
26528 *
26529 * - _height: legend height including items past scrollbox height
26530 * - _maxHeight: maximum legend height before scrollbox is required
26531 * - _effHeight: legend height w/ or w/o scrollbox
26532 *
26533 * - _width: legend width
26534 * - _maxWidth (for orientation:h only): maximum width before starting new row
26535 */
26536function computeLegendDimensions(gd, groups, traces, opts) {
26537 var fullLayout = gd._fullLayout;
26538 if(!opts) opts = fullLayout.legend;
26539 var gs = fullLayout._size;
26540
26541 var isVertical = helpers.isVertical(opts);
26542 var isGrouped = helpers.isGrouped(opts);
26543
26544 var bw = opts.borderwidth;
26545 var bw2 = 2 * bw;
26546 var textGap = constants.textGap;
26547 var itemGap = constants.itemGap;
26548 var endPad = 2 * (bw + itemGap);
26549
26550 var yanchor = getYanchor(opts);
26551 var isBelowPlotArea = opts.y < 0 || (opts.y === 0 && yanchor === 'top');
26552 var isAbovePlotArea = opts.y > 1 || (opts.y === 1 && yanchor === 'bottom');
26553
26554 // - if below/above plot area, give it the maximum potential margin-push value
26555 // - otherwise, extend the height of the plot area
26556 opts._maxHeight = Math.max(
26557 (isBelowPlotArea || isAbovePlotArea) ? fullLayout.height / 2 : gs.h,
26558 30
26559 );
26560
26561 var toggleRectWidth = 0;
26562 opts._width = 0;
26563 opts._height = 0;
26564 var titleSize = getTitleSize(opts);
26565
26566 if(isVertical) {
26567 traces.each(function(d) {
26568 var h = d[0].height;
26569 Drawing.setTranslate(this,
26570 bw + titleSize[0],
26571 bw + titleSize[1] + opts._height + h / 2 + itemGap
26572 );
26573 opts._height += h;
26574 opts._width = Math.max(opts._width, d[0].width);
26575 });
26576
26577 toggleRectWidth = textGap + opts._width;
26578 opts._width += itemGap + textGap + bw2;
26579 opts._height += endPad;
26580
26581 if(isGrouped) {
26582 groups.each(function(d, i) {
26583 Drawing.setTranslate(this, 0, i * opts.tracegroupgap);
26584 });
26585 opts._height += (opts._lgroupsLength - 1) * opts.tracegroupgap;
26586 }
26587 } else {
26588 var xanchor = getXanchor(opts);
26589 var isLeftOfPlotArea = opts.x < 0 || (opts.x === 0 && xanchor === 'right');
26590 var isRightOfPlotArea = opts.x > 1 || (opts.x === 1 && xanchor === 'left');
26591 var isBeyondPlotAreaY = isAbovePlotArea || isBelowPlotArea;
26592 var hw = fullLayout.width / 2;
26593
26594 // - if placed within x-margins, extend the width of the plot area
26595 // - else if below/above plot area and anchored in the margin, extend to opposite margin,
26596 // - otherwise give it the maximum potential margin-push value
26597 opts._maxWidth = Math.max(
26598 isLeftOfPlotArea ? ((isBeyondPlotAreaY && xanchor === 'left') ? gs.l + gs.w : hw) :
26599 isRightOfPlotArea ? ((isBeyondPlotAreaY && xanchor === 'right') ? gs.r + gs.w : hw) :
26600 gs.w,
26601 2 * textGap);
26602 var maxItemWidth = 0;
26603 var combinedItemWidth = 0;
26604 traces.each(function(d) {
26605 var w = d[0].width + textGap;
26606 maxItemWidth = Math.max(maxItemWidth, w);
26607 combinedItemWidth += w;
26608 });
26609
26610 toggleRectWidth = null;
26611 var maxRowWidth = 0;
26612
26613 if(isGrouped) {
26614 var maxGroupHeightInRow = 0;
26615 var groupOffsetX = 0;
26616 var groupOffsetY = 0;
26617 groups.each(function() {
26618 var maxWidthInGroup = 0;
26619 var offsetY = 0;
26620 d3.select(this).selectAll('g.traces').each(function(d) {
26621 var h = d[0].height;
26622 Drawing.setTranslate(this,
26623 titleSize[0],
26624 titleSize[1] + bw + itemGap + h / 2 + offsetY
26625 );
26626 offsetY += h;
26627 maxWidthInGroup = Math.max(maxWidthInGroup, textGap + d[0].width);
26628 });
26629 maxGroupHeightInRow = Math.max(maxGroupHeightInRow, offsetY);
26630
26631 var next = maxWidthInGroup + itemGap;
26632
26633 if((next + bw + groupOffsetX) > opts._maxWidth) {
26634 maxRowWidth = Math.max(maxRowWidth, groupOffsetX);
26635 groupOffsetX = 0;
26636 groupOffsetY += maxGroupHeightInRow + opts.tracegroupgap;
26637 maxGroupHeightInRow = offsetY;
26638 }
26639
26640 Drawing.setTranslate(this, groupOffsetX, groupOffsetY);
26641
26642 groupOffsetX += next;
26643 });
26644
26645 opts._width = Math.max(maxRowWidth, groupOffsetX) + bw;
26646 opts._height = groupOffsetY + maxGroupHeightInRow + endPad;
26647 } else {
26648 var nTraces = traces.size();
26649 var oneRowLegend = (combinedItemWidth + bw2 + (nTraces - 1) * itemGap) < opts._maxWidth;
26650
26651 var maxItemHeightInRow = 0;
26652 var offsetX = 0;
26653 var offsetY = 0;
26654 var rowWidth = 0;
26655 traces.each(function(d) {
26656 var h = d[0].height;
26657 var w = textGap + d[0].width;
26658 var next = (oneRowLegend ? w : maxItemWidth) + itemGap;
26659
26660 if((next + bw + offsetX) > opts._maxWidth) {
26661 maxRowWidth = Math.max(maxRowWidth, rowWidth);
26662 offsetX = 0;
26663 offsetY += maxItemHeightInRow;
26664 opts._height += maxItemHeightInRow;
26665 maxItemHeightInRow = 0;
26666 }
26667
26668 Drawing.setTranslate(this,
26669 titleSize[0] + bw + offsetX,
26670 titleSize[1] + bw + offsetY + h / 2 + itemGap
26671 );
26672
26673 rowWidth = offsetX + w + itemGap;
26674 offsetX += next;
26675 maxItemHeightInRow = Math.max(maxItemHeightInRow, h);
26676 });
26677
26678 if(oneRowLegend) {
26679 opts._width = offsetX + bw2;
26680 opts._height = maxItemHeightInRow + endPad;
26681 } else {
26682 opts._width = Math.max(maxRowWidth, rowWidth) + bw2;
26683 opts._height += maxItemHeightInRow + endPad;
26684 }
26685 }
26686 }
26687
26688 opts._width = Math.ceil(
26689 Math.max(
26690 opts._width + titleSize[0],
26691 opts._titleWidth + 2 * (bw + constants.titlePad)
26692 )
26693 );
26694
26695 opts._height = Math.ceil(
26696 Math.max(
26697 opts._height + titleSize[1],
26698 opts._titleHeight + 2 * (bw + constants.itemGap)
26699 )
26700 );
26701
26702 opts._effHeight = Math.min(opts._height, opts._maxHeight);
26703
26704 var edits = gd._context.edits;
26705 var isEditable = edits.legendText || edits.legendPosition;
26706 traces.each(function(d) {
26707 var traceToggle = d3.select(this).select('.legendtoggle');
26708 var h = d[0].height;
26709 var w = isEditable ? textGap : (toggleRectWidth || (textGap + d[0].width));
26710 if(!isVertical) w += itemGap / 2;
26711 Drawing.setRect(traceToggle, 0, -h / 2, w, h);
26712 });
26713}
26714
26715function expandMargin(gd) {
26716 var fullLayout = gd._fullLayout;
26717 var opts = fullLayout.legend;
26718 var xanchor = getXanchor(opts);
26719 var yanchor = getYanchor(opts);
26720
26721 return Plots.autoMargin(gd, 'legend', {
26722 x: opts.x,
26723 y: opts.y,
26724 l: opts._width * (FROM_TL[xanchor]),
26725 r: opts._width * (FROM_BR[xanchor]),
26726 b: opts._effHeight * (FROM_BR[yanchor]),
26727 t: opts._effHeight * (FROM_TL[yanchor])
26728 });
26729}
26730
26731function getXanchor(opts) {
26732 return Lib.isRightAnchor(opts) ? 'right' :
26733 Lib.isCenterAnchor(opts) ? 'center' :
26734 'left';
26735}
26736
26737function getYanchor(opts) {
26738 return Lib.isBottomAnchor(opts) ? 'bottom' :
26739 Lib.isMiddleAnchor(opts) ? 'middle' :
26740 'top';
26741}
26742
26743},{"../../constants/alignment":154,"../../lib":178,"../../lib/events":172,"../../lib/svg_text_utils":199,"../../plots/plots":256,"../../registry":269,"../color":52,"../dragelement":71,"../drawing":74,"./constants":103,"./get_legend_data":106,"./handle_click":107,"./helpers":108,"./style":110,"d3":16}],106:[function(_dereq_,module,exports){
26744/**
26745* Copyright 2012-2020, Plotly, Inc.
26746* All rights reserved.
26747*
26748* This source code is licensed under the MIT license found in the
26749* LICENSE file in the root directory of this source tree.
26750*/
26751
26752'use strict';
26753
26754var Registry = _dereq_('../../registry');
26755var helpers = _dereq_('./helpers');
26756
26757module.exports = function getLegendData(calcdata, opts) {
26758 var lgroupToTraces = {};
26759 var lgroups = [];
26760 var hasOneNonBlankGroup = false;
26761 var slicesShown = {};
26762 var lgroupi = 0;
26763 var maxNameLength = 0;
26764 var i, j;
26765 var main = opts._main;
26766
26767 function addOneItem(legendGroup, legendItem) {
26768 // each '' legend group is treated as a separate group
26769 if(legendGroup === '' || !helpers.isGrouped(opts)) {
26770 // TODO: check this against fullData legendgroups?
26771 var uniqueGroup = '~~i' + lgroupi;
26772 lgroups.push(uniqueGroup);
26773 lgroupToTraces[uniqueGroup] = [[legendItem]];
26774 lgroupi++;
26775 } else if(lgroups.indexOf(legendGroup) === -1) {
26776 lgroups.push(legendGroup);
26777 hasOneNonBlankGroup = true;
26778 lgroupToTraces[legendGroup] = [[legendItem]];
26779 } else {
26780 lgroupToTraces[legendGroup].push([legendItem]);
26781 }
26782 }
26783
26784 // build an { legendgroup: [cd0, cd0], ... } object
26785 for(i = 0; i < calcdata.length; i++) {
26786 var cd = calcdata[i];
26787 var cd0 = cd[0];
26788 var trace = cd0.trace;
26789 var lgroup = trace.legendgroup;
26790
26791 if(main && (!trace.visible || !trace.showlegend)) continue;
26792
26793 if(Registry.traceIs(trace, 'pie-like')) {
26794 if(!slicesShown[lgroup]) slicesShown[lgroup] = {};
26795
26796 for(j = 0; j < cd.length; j++) {
26797 var labelj = cd[j].label;
26798
26799 if(!slicesShown[lgroup][labelj]) {
26800 addOneItem(lgroup, {
26801 label: labelj,
26802 color: cd[j].color,
26803 i: cd[j].i,
26804 trace: trace,
26805 pts: cd[j].pts
26806 });
26807
26808 slicesShown[lgroup][labelj] = true;
26809 maxNameLength = Math.max(maxNameLength, (labelj || '').length);
26810 }
26811 }
26812 } else {
26813 addOneItem(lgroup, cd0);
26814 maxNameLength = Math.max(maxNameLength, (trace.name || '').length);
26815 }
26816 }
26817
26818 // won't draw a legend in this case
26819 if(!lgroups.length) return [];
26820
26821 // rearrange lgroupToTraces into a d3-friendly array of arrays
26822 var lgroupsLength = lgroups.length;
26823 var ltraces;
26824 var legendData;
26825
26826 if(hasOneNonBlankGroup && helpers.isGrouped(opts)) {
26827 legendData = new Array(lgroupsLength);
26828
26829 for(i = 0; i < lgroupsLength; i++) {
26830 ltraces = lgroupToTraces[lgroups[i]];
26831 legendData[i] = helpers.isReversed(opts) ? ltraces.reverse() : ltraces;
26832 }
26833 } else {
26834 // collapse all groups into one if all groups are blank
26835 legendData = [new Array(lgroupsLength)];
26836
26837 for(i = 0; i < lgroupsLength; i++) {
26838 ltraces = lgroupToTraces[lgroups[i]][0];
26839 legendData[0][helpers.isReversed(opts) ? lgroupsLength - i - 1 : i] = ltraces;
26840 }
26841 lgroupsLength = 1;
26842 }
26843
26844 // number of legend groups - needed in legend/draw.js
26845 opts._lgroupsLength = lgroupsLength;
26846 // maximum name/label length - needed in legend/draw.js
26847 opts._maxNameLength = maxNameLength;
26848
26849 return legendData;
26850};
26851
26852},{"../../registry":269,"./helpers":108}],107:[function(_dereq_,module,exports){
26853/**
26854* Copyright 2012-2020, Plotly, Inc.
26855* All rights reserved.
26856*
26857* This source code is licensed under the MIT license found in the
26858* LICENSE file in the root directory of this source tree.
26859*/
26860
26861'use strict';
26862
26863var Lib = _dereq_('../../lib');
26864var Registry = _dereq_('../../registry');
26865
26866var SHOWISOLATETIP = true;
26867
26868module.exports = function handleClick(g, gd, numClicks) {
26869 var fullLayout = gd._fullLayout;
26870
26871 if(gd._dragged || gd._editing) return;
26872
26873 var itemClick = fullLayout.legend.itemclick;
26874 var itemDoubleClick = fullLayout.legend.itemdoubleclick;
26875
26876 if(numClicks === 1 && itemClick === 'toggle' && itemDoubleClick === 'toggleothers' &&
26877 SHOWISOLATETIP && gd.data && gd._context.showTips
26878 ) {
26879 Lib.notifier(Lib._(gd, 'Double-click on legend to isolate one trace'), 'long');
26880 SHOWISOLATETIP = false;
26881 } else {
26882 SHOWISOLATETIP = false;
26883 }
26884
26885 var mode;
26886 if(numClicks === 1) mode = itemClick;
26887 else if(numClicks === 2) mode = itemDoubleClick;
26888 if(!mode) return;
26889
26890 var hiddenSlices = fullLayout.hiddenlabels ?
26891 fullLayout.hiddenlabels.slice() :
26892 [];
26893
26894 var legendItem = g.data()[0][0];
26895 var fullData = gd._fullData;
26896 var fullTrace = legendItem.trace;
26897 var legendgroup = fullTrace.legendgroup;
26898
26899 var i, j, kcont, key, keys, val;
26900 var attrUpdate = {};
26901 var attrIndices = [];
26902 var carrs = [];
26903 var carrIdx = [];
26904
26905 function insertUpdate(traceIndex, key, value) {
26906 var attrIndex = attrIndices.indexOf(traceIndex);
26907 var valueArray = attrUpdate[key];
26908 if(!valueArray) {
26909 valueArray = attrUpdate[key] = [];
26910 }
26911
26912 if(attrIndices.indexOf(traceIndex) === -1) {
26913 attrIndices.push(traceIndex);
26914 attrIndex = attrIndices.length - 1;
26915 }
26916
26917 valueArray[attrIndex] = value;
26918
26919 return attrIndex;
26920 }
26921
26922 function setVisibility(fullTrace, visibility) {
26923 var fullInput = fullTrace._fullInput;
26924 if(Registry.hasTransform(fullInput, 'groupby')) {
26925 var kcont = carrs[fullInput.index];
26926 if(!kcont) {
26927 var groupbyIndices = Registry.getTransformIndices(fullInput, 'groupby');
26928 var lastGroupbyIndex = groupbyIndices[groupbyIndices.length - 1];
26929 kcont = Lib.keyedContainer(fullInput, 'transforms[' + lastGroupbyIndex + '].styles', 'target', 'value.visible');
26930 carrs[fullInput.index] = kcont;
26931 }
26932
26933 var curState = kcont.get(fullTrace._group);
26934
26935 // If not specified, assume visible. This happens if there are other style
26936 // properties set for a group but not the visibility. There are many similar
26937 // ways to do this (e.g. why not just `curState = fullTrace.visible`??? The
26938 // answer is: because it breaks other things like groupby trace names in
26939 // subtle ways.)
26940 if(curState === undefined) {
26941 curState = true;
26942 }
26943
26944 if(curState !== false) {
26945 // true -> legendonly. All others toggle to true:
26946 kcont.set(fullTrace._group, visibility);
26947 }
26948 carrIdx[fullInput.index] = insertUpdate(fullInput.index, 'visible', fullInput.visible === false ? false : true);
26949 } else {
26950 // false -> false (not possible since will not be visible in legend)
26951 // true -> legendonly
26952 // legendonly -> true
26953 var nextVisibility = fullInput.visible === false ? false : visibility;
26954
26955 insertUpdate(fullInput.index, 'visible', nextVisibility);
26956 }
26957 }
26958
26959 if(Registry.traceIs(fullTrace, 'pie-like')) {
26960 var thisLabel = legendItem.label;
26961 var thisLabelIndex = hiddenSlices.indexOf(thisLabel);
26962
26963 if(mode === 'toggle') {
26964 if(thisLabelIndex === -1) hiddenSlices.push(thisLabel);
26965 else hiddenSlices.splice(thisLabelIndex, 1);
26966 } else if(mode === 'toggleothers') {
26967 hiddenSlices = [];
26968 gd.calcdata[0].forEach(function(d) {
26969 if(thisLabel !== d.label) {
26970 hiddenSlices.push(d.label);
26971 }
26972 });
26973 if(gd._fullLayout.hiddenlabels && gd._fullLayout.hiddenlabels.length === hiddenSlices.length && thisLabelIndex === -1) {
26974 hiddenSlices = [];
26975 }
26976 }
26977
26978 Registry.call('_guiRelayout', gd, 'hiddenlabels', hiddenSlices);
26979 } else {
26980 var hasLegendgroup = legendgroup && legendgroup.length;
26981 var traceIndicesInGroup = [];
26982 var tracei;
26983 if(hasLegendgroup) {
26984 for(i = 0; i < fullData.length; i++) {
26985 tracei = fullData[i];
26986 if(!tracei.visible) continue;
26987 if(tracei.legendgroup === legendgroup) {
26988 traceIndicesInGroup.push(i);
26989 }
26990 }
26991 }
26992
26993 if(mode === 'toggle') {
26994 var nextVisibility;
26995
26996 switch(fullTrace.visible) {
26997 case true:
26998 nextVisibility = 'legendonly';
26999 break;
27000 case false:
27001 nextVisibility = false;
27002 break;
27003 case 'legendonly':
27004 nextVisibility = true;
27005 break;
27006 }
27007
27008 if(hasLegendgroup) {
27009 for(i = 0; i < fullData.length; i++) {
27010 if(fullData[i].visible !== false && fullData[i].legendgroup === legendgroup) {
27011 setVisibility(fullData[i], nextVisibility);
27012 }
27013 }
27014 } else {
27015 setVisibility(fullTrace, nextVisibility);
27016 }
27017 } else if(mode === 'toggleothers') {
27018 // Compute the clicked index. expandedIndex does what we want for expanded traces
27019 // but also culls hidden traces. That means we have some work to do.
27020 var isClicked, isInGroup, notInLegend, otherState;
27021 var isIsolated = true;
27022 for(i = 0; i < fullData.length; i++) {
27023 isClicked = fullData[i] === fullTrace;
27024 notInLegend = fullData[i].showlegend !== true;
27025 if(isClicked || notInLegend) continue;
27026
27027 isInGroup = (hasLegendgroup && fullData[i].legendgroup === legendgroup);
27028
27029 if(!isInGroup && fullData[i].visible === true && !Registry.traceIs(fullData[i], 'notLegendIsolatable')) {
27030 isIsolated = false;
27031 break;
27032 }
27033 }
27034
27035 for(i = 0; i < fullData.length; i++) {
27036 // False is sticky; we don't change it.
27037 if(fullData[i].visible === false) continue;
27038
27039 if(Registry.traceIs(fullData[i], 'notLegendIsolatable')) {
27040 continue;
27041 }
27042
27043 switch(fullTrace.visible) {
27044 case 'legendonly':
27045 setVisibility(fullData[i], true);
27046 break;
27047 case true:
27048 otherState = isIsolated ? true : 'legendonly';
27049 isClicked = fullData[i] === fullTrace;
27050 // N.B. consider traces that have a set legendgroup as toggleable
27051 notInLegend = (fullData[i].showlegend !== true && !fullData[i].legendgroup);
27052 isInGroup = isClicked || (hasLegendgroup && fullData[i].legendgroup === legendgroup);
27053 setVisibility(fullData[i], (isInGroup || notInLegend) ? true : otherState);
27054 break;
27055 }
27056 }
27057 }
27058
27059 for(i = 0; i < carrs.length; i++) {
27060 kcont = carrs[i];
27061 if(!kcont) continue;
27062 var update = kcont.constructUpdate();
27063
27064 var updateKeys = Object.keys(update);
27065 for(j = 0; j < updateKeys.length; j++) {
27066 key = updateKeys[j];
27067 val = attrUpdate[key] = attrUpdate[key] || [];
27068 val[carrIdx[i]] = update[key];
27069 }
27070 }
27071
27072 // The length of the value arrays should be equal and any unspecified
27073 // values should be explicitly undefined for them to get properly culled
27074 // as updates and not accidentally reset to the default value. This fills
27075 // out sparse arrays with the required number of undefined values:
27076 keys = Object.keys(attrUpdate);
27077 for(i = 0; i < keys.length; i++) {
27078 key = keys[i];
27079 for(j = 0; j < attrIndices.length; j++) {
27080 // Use hasOwnPropety to protect against falsey values:
27081 if(!attrUpdate[key].hasOwnProperty(j)) {
27082 attrUpdate[key][j] = undefined;
27083 }
27084 }
27085 }
27086
27087 Registry.call('_guiRestyle', gd, attrUpdate, attrIndices);
27088 }
27089};
27090
27091},{"../../lib":178,"../../registry":269}],108:[function(_dereq_,module,exports){
27092/**
27093* Copyright 2012-2020, Plotly, Inc.
27094* All rights reserved.
27095*
27096* This source code is licensed under the MIT license found in the
27097* LICENSE file in the root directory of this source tree.
27098*/
27099
27100
27101'use strict';
27102
27103exports.isGrouped = function isGrouped(legendLayout) {
27104 return (legendLayout.traceorder || '').indexOf('grouped') !== -1;
27105};
27106
27107exports.isVertical = function isVertical(legendLayout) {
27108 return legendLayout.orientation !== 'h';
27109};
27110
27111exports.isReversed = function isReversed(legendLayout) {
27112 return (legendLayout.traceorder || '').indexOf('reversed') !== -1;
27113};
27114
27115},{}],109:[function(_dereq_,module,exports){
27116/**
27117* Copyright 2012-2020, Plotly, Inc.
27118* All rights reserved.
27119*
27120* This source code is licensed under the MIT license found in the
27121* LICENSE file in the root directory of this source tree.
27122*/
27123
27124
27125'use strict';
27126
27127
27128module.exports = {
27129 moduleType: 'component',
27130 name: 'legend',
27131
27132 layoutAttributes: _dereq_('./attributes'),
27133 supplyLayoutDefaults: _dereq_('./defaults'),
27134
27135 draw: _dereq_('./draw'),
27136 style: _dereq_('./style')
27137};
27138
27139},{"./attributes":102,"./defaults":104,"./draw":105,"./style":110}],110:[function(_dereq_,module,exports){
27140/**
27141* Copyright 2012-2020, Plotly, Inc.
27142* All rights reserved.
27143*
27144* This source code is licensed under the MIT license found in the
27145* LICENSE file in the root directory of this source tree.
27146*/
27147
27148'use strict';
27149
27150var d3 = _dereq_('d3');
27151
27152var Registry = _dereq_('../../registry');
27153var Lib = _dereq_('../../lib');
27154var Drawing = _dereq_('../drawing');
27155var Color = _dereq_('../color');
27156var extractOpts = _dereq_('../colorscale/helpers').extractOpts;
27157
27158var subTypes = _dereq_('../../traces/scatter/subtypes');
27159var stylePie = _dereq_('../../traces/pie/style_one');
27160var pieCastOption = _dereq_('../../traces/pie/helpers').castOption;
27161
27162var CST_MARKER_SIZE = 12;
27163var CST_LINE_WIDTH = 5;
27164var CST_MARKER_LINE_WIDTH = 2;
27165var MAX_LINE_WIDTH = 10;
27166var MAX_MARKER_LINE_WIDTH = 5;
27167
27168module.exports = function style(s, gd, legend) {
27169 var fullLayout = gd._fullLayout;
27170 if(!legend) legend = fullLayout.legend;
27171 var constantItemSizing = legend.itemsizing === 'constant';
27172
27173 var boundLineWidth = function(mlw, cont, max, cst) {
27174 var v;
27175 if(mlw + 1) {
27176 v = mlw;
27177 } else if(cont && cont.width > 0) {
27178 v = cont.width;
27179 } else {
27180 return 0;
27181 }
27182 return constantItemSizing ? cst : Math.min(v, max);
27183 };
27184
27185 s.each(function(d) {
27186 var traceGroup = d3.select(this);
27187
27188 var layers = Lib.ensureSingle(traceGroup, 'g', 'layers');
27189 layers.style('opacity', d[0].trace.opacity);
27190
27191 var valign = legend.valign;
27192 var lineHeight = d[0].lineHeight;
27193 var height = d[0].height;
27194
27195 if(valign === 'middle' || !lineHeight || !height) {
27196 layers.attr('transform', null);
27197 } else {
27198 var factor = {top: 1, bottom: -1}[valign];
27199 var markerOffsetY = factor * (0.5 * (lineHeight - height + 3));
27200 layers.attr('transform', 'translate(0,' + markerOffsetY + ')');
27201 }
27202
27203 var fill = layers
27204 .selectAll('g.legendfill')
27205 .data([d]);
27206 fill.enter().append('g')
27207 .classed('legendfill', true);
27208
27209 var line = layers
27210 .selectAll('g.legendlines')
27211 .data([d]);
27212 line.enter().append('g')
27213 .classed('legendlines', true);
27214
27215 var symbol = layers
27216 .selectAll('g.legendsymbols')
27217 .data([d]);
27218 symbol.enter().append('g')
27219 .classed('legendsymbols', true);
27220
27221 symbol.selectAll('g.legendpoints')
27222 .data([d])
27223 .enter().append('g')
27224 .classed('legendpoints', true);
27225 })
27226 .each(styleSpatial)
27227 .each(styleWaterfalls)
27228 .each(styleFunnels)
27229 .each(styleBars)
27230 .each(styleBoxes)
27231 .each(styleFunnelareas)
27232 .each(stylePies)
27233 .each(styleLines)
27234 .each(stylePoints)
27235 .each(styleCandles)
27236 .each(styleOHLC);
27237
27238 function styleLines(d) {
27239 var d0 = d[0];
27240 var trace = d0.trace;
27241 var showFill = trace.visible && trace.fill && trace.fill !== 'none';
27242 var showLine = subTypes.hasLines(trace);
27243 var contours = trace.contours;
27244 var showGradientLine = false;
27245 var showGradientFill = false;
27246 var dMod, tMod;
27247
27248 var cOpts = extractOpts(trace);
27249 var colorscale = cOpts.colorscale;
27250 var reversescale = cOpts.reversescale;
27251
27252 var fillGradient = function(s) {
27253 if(s.size()) {
27254 var gradientID = 'legendfill-' + trace.uid;
27255 Drawing.gradient(s, gd, gradientID,
27256 getGradientDirection(reversescale),
27257 colorscale, 'fill');
27258 }
27259 };
27260
27261 var lineGradient = function(s) {
27262 if(s.size()) {
27263 var gradientID = 'legendline-' + trace.uid;
27264 Drawing.lineGroupStyle(s);
27265 Drawing.gradient(s, gd, gradientID,
27266 getGradientDirection(reversescale),
27267 colorscale, 'stroke');
27268 }
27269 };
27270
27271 if(contours) {
27272 var coloring = contours.coloring;
27273
27274 if(coloring === 'lines') {
27275 showGradientLine = true;
27276 } else {
27277 showLine = coloring === 'none' || coloring === 'heatmap' || contours.showlines;
27278 }
27279
27280 if(contours.type === 'constraint') {
27281 showFill = contours._operation !== '=';
27282 } else if(coloring === 'fill' || coloring === 'heatmap') {
27283 showGradientFill = true;
27284 }
27285 }
27286
27287 // with fill and no markers or text, move the line and fill up a bit
27288 // so it's more centered
27289 var markersOrText = subTypes.hasMarkers(trace) || subTypes.hasText(trace);
27290 var anyFill = showFill || showGradientFill;
27291 var anyLine = showLine || showGradientLine;
27292 var pathStart = (markersOrText || !anyFill) ? 'M5,0' :
27293 // with a line leave it slightly below center, to leave room for the
27294 // line thickness and because the line is usually more prominent
27295 anyLine ? 'M5,-2' : 'M5,-3';
27296
27297 var this3 = d3.select(this);
27298
27299 var fill = this3.select('.legendfill').selectAll('path')
27300 .data(showFill || showGradientFill ? [d] : []);
27301 fill.enter().append('path').classed('js-fill', true);
27302 fill.exit().remove();
27303 fill.attr('d', pathStart + 'h30v6h-30z')
27304 .call(showFill ? Drawing.fillGroupStyle : fillGradient);
27305
27306 if(showLine || showGradientLine) {
27307 var lw = boundLineWidth(undefined, trace.line, MAX_LINE_WIDTH, CST_LINE_WIDTH);
27308 tMod = Lib.minExtend(trace, {line: {width: lw}});
27309 dMod = [Lib.minExtend(d0, {trace: tMod})];
27310 }
27311
27312 var line = this3.select('.legendlines').selectAll('path')
27313 .data(showLine || showGradientLine ? [dMod] : []);
27314 line.enter().append('path').classed('js-line', true);
27315 line.exit().remove();
27316
27317 // this is ugly... but you can't apply a gradient to a perfectly
27318 // horizontal or vertical line. Presumably because then
27319 // the system doesn't know how to scale vertical variation, even
27320 // though there *is* no vertical variation in this case.
27321 // so add an invisibly small angle to the line
27322 // This issue (and workaround) exist across (Mac) Chrome, FF, and Safari
27323 line.attr('d', pathStart + (showGradientLine ? 'l30,0.0001' : 'h30'))
27324 .call(showLine ? Drawing.lineGroupStyle : lineGradient);
27325 }
27326
27327 function stylePoints(d) {
27328 var d0 = d[0];
27329 var trace = d0.trace;
27330 var showMarkers = subTypes.hasMarkers(trace);
27331 var showText = subTypes.hasText(trace);
27332 var showLines = subTypes.hasLines(trace);
27333 var dMod, tMod;
27334
27335 // 'scatter3d' don't use gd.calcdata,
27336 // use d0.trace to infer arrayOk attributes
27337
27338 function boundVal(attrIn, arrayToValFn, bounds, cst) {
27339 var valIn = Lib.nestedProperty(trace, attrIn).get();
27340 var valToBound = (Lib.isArrayOrTypedArray(valIn) && arrayToValFn) ?
27341 arrayToValFn(valIn) :
27342 valIn;
27343
27344 if(constantItemSizing && valToBound && cst !== undefined) {
27345 valToBound = cst;
27346 }
27347
27348 if(bounds) {
27349 if(valToBound < bounds[0]) return bounds[0];
27350 else if(valToBound > bounds[1]) return bounds[1];
27351 }
27352 return valToBound;
27353 }
27354
27355 function pickFirst(array) {
27356 if(d0._distinct && d0.index && array[d0.index]) return array[d0.index];
27357 return array[0];
27358 }
27359
27360 // constrain text, markers, etc so they'll fit on the legend
27361 if(showMarkers || showText || showLines) {
27362 var dEdit = {};
27363 var tEdit = {};
27364
27365 if(showMarkers) {
27366 dEdit.mc = boundVal('marker.color', pickFirst);
27367 dEdit.mx = boundVal('marker.symbol', pickFirst);
27368 dEdit.mo = boundVal('marker.opacity', Lib.mean, [0.2, 1]);
27369 dEdit.mlc = boundVal('marker.line.color', pickFirst);
27370 dEdit.mlw = boundVal('marker.line.width', Lib.mean, [0, 5], CST_MARKER_LINE_WIDTH);
27371 tEdit.marker = {
27372 sizeref: 1,
27373 sizemin: 1,
27374 sizemode: 'diameter'
27375 };
27376
27377 var ms = boundVal('marker.size', Lib.mean, [2, 16], CST_MARKER_SIZE);
27378 dEdit.ms = ms;
27379 tEdit.marker.size = ms;
27380 }
27381
27382 if(showLines) {
27383 tEdit.line = {
27384 width: boundVal('line.width', pickFirst, [0, 10], CST_LINE_WIDTH)
27385 };
27386 }
27387
27388 if(showText) {
27389 dEdit.tx = 'Aa';
27390 dEdit.tp = boundVal('textposition', pickFirst);
27391 dEdit.ts = 10;
27392 dEdit.tc = boundVal('textfont.color', pickFirst);
27393 dEdit.tf = boundVal('textfont.family', pickFirst);
27394 }
27395
27396 dMod = [Lib.minExtend(d0, dEdit)];
27397 tMod = Lib.minExtend(trace, tEdit);
27398
27399 // always show legend items in base state
27400 tMod.selectedpoints = null;
27401
27402 // never show texttemplate
27403 tMod.texttemplate = null;
27404 }
27405
27406 var ptgroup = d3.select(this).select('g.legendpoints');
27407
27408 var pts = ptgroup.selectAll('path.scatterpts')
27409 .data(showMarkers ? dMod : []);
27410 // make sure marker is on the bottom, in case it enters after text
27411 pts.enter().insert('path', ':first-child')
27412 .classed('scatterpts', true)
27413 .attr('transform', 'translate(20,0)');
27414 pts.exit().remove();
27415 pts.call(Drawing.pointStyle, tMod, gd);
27416
27417 // 'mrc' is set in pointStyle and used in textPointStyle:
27418 // constrain it here
27419 if(showMarkers) dMod[0].mrc = 3;
27420
27421 var txt = ptgroup.selectAll('g.pointtext')
27422 .data(showText ? dMod : []);
27423 txt.enter()
27424 .append('g').classed('pointtext', true)
27425 .append('text').attr('transform', 'translate(20,0)');
27426 txt.exit().remove();
27427 txt.selectAll('text').call(Drawing.textPointStyle, tMod, gd);
27428 }
27429
27430 function styleWaterfalls(d) {
27431 var trace = d[0].trace;
27432 var isWaterfall = trace.type === 'waterfall';
27433
27434 if(d[0]._distinct && isWaterfall) {
27435 var cont = d[0].trace[d[0].dir].marker;
27436 d[0].mc = cont.color;
27437 d[0].mlw = cont.line.width;
27438 d[0].mlc = cont.line.color;
27439 return styleBarLike(d, this, 'waterfall');
27440 }
27441
27442 var ptsData = [];
27443 if(trace.visible && isWaterfall) {
27444 ptsData = d[0].hasTotals ?
27445 [['increasing', 'M-6,-6V6H0Z'], ['totals', 'M6,6H0L-6,-6H-0Z'], ['decreasing', 'M6,6V-6H0Z']] :
27446 [['increasing', 'M-6,-6V6H6Z'], ['decreasing', 'M6,6V-6H-6Z']];
27447 }
27448
27449 var pts = d3.select(this).select('g.legendpoints')
27450 .selectAll('path.legendwaterfall')
27451 .data(ptsData);
27452 pts.enter().append('path').classed('legendwaterfall', true)
27453 .attr('transform', 'translate(20,0)')
27454 .style('stroke-miterlimit', 1);
27455 pts.exit().remove();
27456
27457 pts.each(function(dd) {
27458 var pt = d3.select(this);
27459 var cont = trace[dd[0]].marker;
27460 var lw = boundLineWidth(undefined, cont.line, MAX_MARKER_LINE_WIDTH, CST_MARKER_LINE_WIDTH);
27461
27462 pt.attr('d', dd[1])
27463 .style('stroke-width', lw + 'px')
27464 .call(Color.fill, cont.color);
27465
27466 if(lw) {
27467 pt.call(Color.stroke, cont.line.color);
27468 }
27469 });
27470 }
27471
27472 function styleBars(d) {
27473 styleBarLike(d, this);
27474 }
27475
27476 function styleFunnels(d) {
27477 styleBarLike(d, this, 'funnel');
27478 }
27479
27480 function styleBarLike(d, lThis, desiredType) {
27481 var trace = d[0].trace;
27482 var marker = trace.marker || {};
27483 var markerLine = marker.line || {};
27484
27485 var isVisible = (!desiredType) ? Registry.traceIs(trace, 'bar') :
27486 (trace.visible && trace.type === desiredType);
27487
27488 var barpath = d3.select(lThis).select('g.legendpoints')
27489 .selectAll('path.legend' + desiredType)
27490 .data(isVisible ? [d] : []);
27491 barpath.enter().append('path').classed('legend' + desiredType, true)
27492 .attr('d', 'M6,6H-6V-6H6Z')
27493 .attr('transform', 'translate(20,0)');
27494 barpath.exit().remove();
27495
27496 barpath.each(function(d) {
27497 var p = d3.select(this);
27498 var d0 = d[0];
27499 var w = boundLineWidth(d0.mlw, marker.line, MAX_MARKER_LINE_WIDTH, CST_MARKER_LINE_WIDTH);
27500
27501 p.style('stroke-width', w + 'px')
27502 .call(Color.fill, d0.mc || marker.color);
27503
27504 if(w) Color.stroke(p, d0.mlc || markerLine.color);
27505 });
27506 }
27507
27508 function styleBoxes(d) {
27509 var trace = d[0].trace;
27510
27511 var pts = d3.select(this).select('g.legendpoints')
27512 .selectAll('path.legendbox')
27513 .data(trace.visible && Registry.traceIs(trace, 'box-violin') ? [d] : []);
27514 pts.enter().append('path').classed('legendbox', true)
27515 // if we want the median bar, prepend M6,0H-6
27516 .attr('d', 'M6,6H-6V-6H6Z')
27517 .attr('transform', 'translate(20,0)');
27518 pts.exit().remove();
27519
27520 pts.each(function() {
27521 var p = d3.select(this);
27522
27523 if((trace.boxpoints === 'all' || trace.points === 'all') &&
27524 Color.opacity(trace.fillcolor) === 0 && Color.opacity((trace.line || {}).color) === 0
27525 ) {
27526 var tMod = Lib.minExtend(trace, {
27527 marker: {
27528 size: constantItemSizing ? CST_MARKER_SIZE : Lib.constrain(trace.marker.size, 2, 16),
27529 sizeref: 1,
27530 sizemin: 1,
27531 sizemode: 'diameter'
27532 }
27533 });
27534 pts.call(Drawing.pointStyle, tMod, gd);
27535 } else {
27536 var w = boundLineWidth(undefined, trace.line, MAX_MARKER_LINE_WIDTH, CST_MARKER_LINE_WIDTH);
27537
27538 p.style('stroke-width', w + 'px')
27539 .call(Color.fill, trace.fillcolor);
27540
27541 if(w) Color.stroke(p, trace.line.color);
27542 }
27543 });
27544 }
27545
27546 function styleCandles(d) {
27547 var trace = d[0].trace;
27548
27549 var pts = d3.select(this).select('g.legendpoints')
27550 .selectAll('path.legendcandle')
27551 .data(trace.visible && trace.type === 'candlestick' ? [d, d] : []);
27552 pts.enter().append('path').classed('legendcandle', true)
27553 .attr('d', function(_, i) {
27554 if(i) return 'M-15,0H-8M-8,6V-6H8Z'; // increasing
27555 return 'M15,0H8M8,-6V6H-8Z'; // decreasing
27556 })
27557 .attr('transform', 'translate(20,0)')
27558 .style('stroke-miterlimit', 1);
27559 pts.exit().remove();
27560
27561 pts.each(function(_, i) {
27562 var p = d3.select(this);
27563 var cont = trace[i ? 'increasing' : 'decreasing'];
27564 var w = boundLineWidth(undefined, cont.line, MAX_MARKER_LINE_WIDTH, CST_MARKER_LINE_WIDTH);
27565
27566 p.style('stroke-width', w + 'px')
27567 .call(Color.fill, cont.fillcolor);
27568
27569 if(w) Color.stroke(p, cont.line.color);
27570 });
27571 }
27572
27573 function styleOHLC(d) {
27574 var trace = d[0].trace;
27575
27576 var pts = d3.select(this).select('g.legendpoints')
27577 .selectAll('path.legendohlc')
27578 .data(trace.visible && trace.type === 'ohlc' ? [d, d] : []);
27579 pts.enter().append('path').classed('legendohlc', true)
27580 .attr('d', function(_, i) {
27581 if(i) return 'M-15,0H0M-8,-6V0'; // increasing
27582 return 'M15,0H0M8,6V0'; // decreasing
27583 })
27584 .attr('transform', 'translate(20,0)')
27585 .style('stroke-miterlimit', 1);
27586 pts.exit().remove();
27587
27588 pts.each(function(_, i) {
27589 var p = d3.select(this);
27590 var cont = trace[i ? 'increasing' : 'decreasing'];
27591 var w = boundLineWidth(undefined, cont.line, MAX_MARKER_LINE_WIDTH, CST_MARKER_LINE_WIDTH);
27592
27593 p.style('fill', 'none')
27594 .call(Drawing.dashLine, cont.line.dash, w);
27595
27596 if(w) Color.stroke(p, cont.line.color);
27597 });
27598 }
27599
27600 function stylePies(d) {
27601 stylePieLike(d, this, 'pie');
27602 }
27603
27604 function styleFunnelareas(d) {
27605 stylePieLike(d, this, 'funnelarea');
27606 }
27607
27608 function stylePieLike(d, lThis, desiredType) {
27609 var d0 = d[0];
27610 var trace = d0.trace;
27611
27612 var isVisible = (!desiredType) ? Registry.traceIs(trace, desiredType) :
27613 (trace.visible && trace.type === desiredType);
27614
27615 var pts = d3.select(lThis).select('g.legendpoints')
27616 .selectAll('path.legend' + desiredType)
27617 .data(isVisible ? [d] : []);
27618 pts.enter().append('path').classed('legend' + desiredType, true)
27619 .attr('d', 'M6,6H-6V-6H6Z')
27620 .attr('transform', 'translate(20,0)');
27621 pts.exit().remove();
27622
27623 if(pts.size()) {
27624 var cont = (trace.marker || {}).line;
27625 var lw = boundLineWidth(pieCastOption(cont.width, d0.pts), cont, MAX_MARKER_LINE_WIDTH, CST_MARKER_LINE_WIDTH);
27626
27627 var tMod = Lib.minExtend(trace, {marker: {line: {width: lw}}});
27628 // since minExtend do not slice more than 3 items we need to patch line.color here
27629 tMod.marker.line.color = cont.color;
27630
27631 var d0Mod = Lib.minExtend(d0, {trace: tMod});
27632
27633 stylePie(pts, d0Mod, tMod);
27634 }
27635 }
27636
27637 function styleSpatial(d) { // i.e. maninly traces having z and colorscale
27638 var trace = d[0].trace;
27639
27640 var useGradient;
27641 var ptsData = [];
27642 if(trace.visible) {
27643 switch(trace.type) {
27644 case 'histogram2d' :
27645 case 'heatmap' :
27646 ptsData = [
27647 ['M-15,-2V4H15V-2Z'] // similar to contour
27648 ];
27649 useGradient = true;
27650 break;
27651 case 'choropleth' :
27652 case 'choroplethmapbox' :
27653 ptsData = [
27654 ['M-6,-6V6H6V-6Z']
27655 ];
27656 useGradient = true;
27657 break;
27658 case 'densitymapbox' :
27659 ptsData = [
27660 ['M-6,0 a6,6 0 1,0 12,0 a 6,6 0 1,0 -12,0']
27661 ];
27662 useGradient = 'radial';
27663 break;
27664 case 'cone' :
27665 ptsData = [
27666 ['M-6,2 A2,2 0 0,0 -6,6 V6L6,4Z'],
27667 ['M-6,-6 A2,2 0 0,0 -6,-2 L6,-4Z'],
27668 ['M-6,-2 A2,2 0 0,0 -6,2 L6,0Z']
27669 ];
27670 useGradient = false;
27671 break;
27672 case 'streamtube' :
27673 ptsData = [
27674 ['M-6,2 A2,2 0 0,0 -6,6 H6 A2,2 0 0,1 6,2 Z'],
27675 ['M-6,-6 A2,2 0 0,0 -6,-2 H6 A2,2 0 0,1 6,-6 Z'],
27676 ['M-6,-2 A2,2 0 0,0 -6,2 H6 A2,2 0 0,1 6,-2 Z']
27677 ];
27678 useGradient = false;
27679 break;
27680 case 'surface' :
27681 ptsData = [
27682 ['M-6,-6 A2,3 0 0,0 -6,0 H6 A2,3 0 0,1 6,-6 Z'],
27683 ['M-6,1 A2,3 0 0,1 -6,6 H6 A2,3 0 0,0 6,0 Z']
27684 ];
27685 useGradient = true;
27686 break;
27687 case 'mesh3d' :
27688 ptsData = [
27689 ['M-6,6H0L-6,-6Z'],
27690 ['M6,6H0L6,-6Z'],
27691 ['M-6,-6H6L0,6Z']
27692 ];
27693 useGradient = false;
27694 break;
27695 case 'volume' :
27696 ptsData = [
27697 ['M-6,6H0L-6,-6Z'],
27698 ['M6,6H0L6,-6Z'],
27699 ['M-6,-6H6L0,6Z']
27700 ];
27701 useGradient = true;
27702 break;
27703 case 'isosurface':
27704 ptsData = [
27705 ['M-6,6H0L-6,-6Z'],
27706 ['M6,6H0L6,-6Z'],
27707 ['M-6,-6 A12,24 0 0,0 6,-6 L0,6Z']
27708 ];
27709 useGradient = false;
27710 break;
27711 }
27712 }
27713
27714 var pts = d3.select(this).select('g.legendpoints')
27715 .selectAll('path.legend3dandfriends')
27716 .data(ptsData);
27717 pts.enter().append('path').classed('legend3dandfriends', true)
27718 .attr('transform', 'translate(20,0)')
27719 .style('stroke-miterlimit', 1);
27720 pts.exit().remove();
27721
27722 pts.each(function(dd, i) {
27723 var pt = d3.select(this);
27724
27725 var cOpts = extractOpts(trace);
27726 var colorscale = cOpts.colorscale;
27727 var reversescale = cOpts.reversescale;
27728 var fillGradient = function(s) {
27729 if(s.size()) {
27730 var gradientID = 'legendfill-' + trace.uid;
27731 Drawing.gradient(s, gd, gradientID,
27732 getGradientDirection(reversescale, useGradient === 'radial'),
27733 colorscale, 'fill');
27734 }
27735 };
27736
27737 var fillColor;
27738 if(!colorscale) {
27739 var color = trace.vertexcolor || trace.facecolor || trace.color;
27740 fillColor = Lib.isArrayOrTypedArray(color) ? (color[i] || color[0]) : color;
27741 } else {
27742 if(!useGradient) {
27743 var len = colorscale.length;
27744 fillColor =
27745 i === 0 ? colorscale[reversescale ? len - 1 : 0][1] : // minimum
27746 i === 1 ? colorscale[reversescale ? 0 : len - 1][1] : // maximum
27747 colorscale[Math.floor((len - 1) / 2)][1]; // middle
27748 }
27749 }
27750
27751 pt.attr('d', dd[0]);
27752 if(fillColor) {
27753 pt.call(Color.fill, fillColor);
27754 } else {
27755 pt.call(fillGradient);
27756 }
27757 });
27758 }
27759};
27760
27761function getGradientDirection(reversescale, isRadial) {
27762 var str = isRadial ? 'radial' : 'horizontal';
27763 return str + (reversescale ? '' : 'reversed');
27764}
27765
27766},{"../../lib":178,"../../registry":269,"../../traces/pie/helpers":381,"../../traces/pie/style_one":387,"../../traces/scatter/subtypes":413,"../color":52,"../colorscale/helpers":63,"../drawing":74,"d3":16}],111:[function(_dereq_,module,exports){
27767/**
27768* Copyright 2012-2020, Plotly, Inc.
27769* All rights reserved.
27770*
27771* This source code is licensed under the MIT license found in the
27772* LICENSE file in the root directory of this source tree.
27773*/
27774
27775'use strict';
27776
27777var Registry = _dereq_('../../registry');
27778var Plots = _dereq_('../../plots/plots');
27779var axisIds = _dereq_('../../plots/cartesian/axis_ids');
27780var Icons = _dereq_('../../fonts/ploticon');
27781var eraseActiveShape = _dereq_('../shapes/draw').eraseActiveShape;
27782var Lib = _dereq_('../../lib');
27783var _ = Lib._;
27784
27785var modeBarButtons = module.exports = {};
27786
27787/**
27788 * ModeBar buttons configuration
27789 *
27790 * @param {string} name
27791 * name / id of the buttons (for tracking)
27792 * @param {string} title
27793 * text that appears while hovering over the button,
27794 * enter null, false or '' for no hover text
27795 * @param {string} icon
27796 * svg icon object associated with the button
27797 * can be linked to Plotly.Icons to use the default plotly icons
27798 * @param {string} [gravity]
27799 * icon positioning
27800 * @param {function} click
27801 * click handler associated with the button, a function of
27802 * 'gd' (the main graph object) and
27803 * 'ev' (the event object)
27804 * @param {string} [attr]
27805 * attribute associated with button,
27806 * use this with 'val' to keep track of the state
27807 * @param {*} [val]
27808 * initial 'attr' value, can be a function of gd
27809 * @param {boolean} [toggle]
27810 * is the button a toggle button?
27811 */
27812modeBarButtons.toImage = {
27813 name: 'toImage',
27814 title: function(gd) {
27815 var opts = gd._context.toImageButtonOptions || {};
27816 var format = opts.format || 'png';
27817 return format === 'png' ?
27818 _(gd, 'Download plot as a png') : // legacy text
27819 _(gd, 'Download plot'); // generic non-PNG text
27820 },
27821 icon: Icons.camera,
27822 click: function(gd) {
27823 var toImageButtonOptions = gd._context.toImageButtonOptions;
27824 var opts = {format: toImageButtonOptions.format || 'png'};
27825
27826 Lib.notifier(_(gd, 'Taking snapshot - this may take a few seconds'), 'long');
27827
27828 if(opts.format !== 'svg' && Lib.isIE()) {
27829 Lib.notifier(_(gd, 'IE only supports svg. Changing format to svg.'), 'long');
27830 opts.format = 'svg';
27831 }
27832
27833 ['filename', 'width', 'height', 'scale'].forEach(function(key) {
27834 if(key in toImageButtonOptions) {
27835 opts[key] = toImageButtonOptions[key];
27836 }
27837 });
27838
27839 Registry.call('downloadImage', gd, opts)
27840 .then(function(filename) {
27841 Lib.notifier(_(gd, 'Snapshot succeeded') + ' - ' + filename, 'long');
27842 })
27843 .catch(function() {
27844 Lib.notifier(_(gd, 'Sorry, there was a problem downloading your snapshot!'), 'long');
27845 });
27846 }
27847};
27848
27849modeBarButtons.sendDataToCloud = {
27850 name: 'sendDataToCloud',
27851 title: function(gd) { return _(gd, 'Edit in Chart Studio'); },
27852 icon: Icons.disk,
27853 click: function(gd) {
27854 Plots.sendDataToCloud(gd);
27855 }
27856};
27857
27858modeBarButtons.editInChartStudio = {
27859 name: 'editInChartStudio',
27860 title: function(gd) { return _(gd, 'Edit in Chart Studio'); },
27861 icon: Icons.pencil,
27862 click: function(gd) {
27863 Plots.sendDataToCloud(gd);
27864 }
27865};
27866
27867modeBarButtons.zoom2d = {
27868 name: 'zoom2d',
27869 title: function(gd) { return _(gd, 'Zoom'); },
27870 attr: 'dragmode',
27871 val: 'zoom',
27872 icon: Icons.zoombox,
27873 click: handleCartesian
27874};
27875
27876modeBarButtons.pan2d = {
27877 name: 'pan2d',
27878 title: function(gd) { return _(gd, 'Pan'); },
27879 attr: 'dragmode',
27880 val: 'pan',
27881 icon: Icons.pan,
27882 click: handleCartesian
27883};
27884
27885modeBarButtons.select2d = {
27886 name: 'select2d',
27887 title: function(gd) { return _(gd, 'Box Select'); },
27888 attr: 'dragmode',
27889 val: 'select',
27890 icon: Icons.selectbox,
27891 click: handleCartesian
27892};
27893
27894modeBarButtons.lasso2d = {
27895 name: 'lasso2d',
27896 title: function(gd) { return _(gd, 'Lasso Select'); },
27897 attr: 'dragmode',
27898 val: 'lasso',
27899 icon: Icons.lasso,
27900 click: handleCartesian
27901};
27902
27903modeBarButtons.drawclosedpath = {
27904 name: 'drawclosedpath',
27905 title: function(gd) { return _(gd, 'Draw closed freeform'); },
27906 attr: 'dragmode',
27907 val: 'drawclosedpath',
27908 icon: Icons.drawclosedpath,
27909 click: handleCartesian
27910};
27911
27912modeBarButtons.drawopenpath = {
27913 name: 'drawopenpath',
27914 title: function(gd) { return _(gd, 'Draw open freeform'); },
27915 attr: 'dragmode',
27916 val: 'drawopenpath',
27917 icon: Icons.drawopenpath,
27918 click: handleCartesian
27919};
27920
27921modeBarButtons.drawline = {
27922 name: 'drawline',
27923 title: function(gd) { return _(gd, 'Draw line'); },
27924 attr: 'dragmode',
27925 val: 'drawline',
27926 icon: Icons.drawline,
27927 click: handleCartesian
27928};
27929
27930modeBarButtons.drawrect = {
27931 name: 'drawrect',
27932 title: function(gd) { return _(gd, 'Draw rectangle'); },
27933 attr: 'dragmode',
27934 val: 'drawrect',
27935 icon: Icons.drawrect,
27936 click: handleCartesian
27937};
27938
27939modeBarButtons.drawcircle = {
27940 name: 'drawcircle',
27941 title: function(gd) { return _(gd, 'Draw circle'); },
27942 attr: 'dragmode',
27943 val: 'drawcircle',
27944 icon: Icons.drawcircle,
27945 click: handleCartesian
27946};
27947
27948modeBarButtons.eraseshape = {
27949 name: 'eraseshape',
27950 title: function(gd) { return _(gd, 'Erase active shape'); },
27951 icon: Icons.eraseshape,
27952 click: eraseActiveShape
27953};
27954
27955modeBarButtons.zoomIn2d = {
27956 name: 'zoomIn2d',
27957 title: function(gd) { return _(gd, 'Zoom in'); },
27958 attr: 'zoom',
27959 val: 'in',
27960 icon: Icons.zoom_plus,
27961 click: handleCartesian
27962};
27963
27964modeBarButtons.zoomOut2d = {
27965 name: 'zoomOut2d',
27966 title: function(gd) { return _(gd, 'Zoom out'); },
27967 attr: 'zoom',
27968 val: 'out',
27969 icon: Icons.zoom_minus,
27970 click: handleCartesian
27971};
27972
27973modeBarButtons.autoScale2d = {
27974 name: 'autoScale2d',
27975 title: function(gd) { return _(gd, 'Autoscale'); },
27976 attr: 'zoom',
27977 val: 'auto',
27978 icon: Icons.autoscale,
27979 click: handleCartesian
27980};
27981
27982modeBarButtons.resetScale2d = {
27983 name: 'resetScale2d',
27984 title: function(gd) { return _(gd, 'Reset axes'); },
27985 attr: 'zoom',
27986 val: 'reset',
27987 icon: Icons.home,
27988 click: handleCartesian
27989};
27990
27991modeBarButtons.hoverClosestCartesian = {
27992 name: 'hoverClosestCartesian',
27993 title: function(gd) { return _(gd, 'Show closest data on hover'); },
27994 attr: 'hovermode',
27995 val: 'closest',
27996 icon: Icons.tooltip_basic,
27997 gravity: 'ne',
27998 click: handleCartesian
27999};
28000
28001modeBarButtons.hoverCompareCartesian = {
28002 name: 'hoverCompareCartesian',
28003 title: function(gd) { return _(gd, 'Compare data on hover'); },
28004 attr: 'hovermode',
28005 val: function(gd) {
28006 return gd._fullLayout._isHoriz ? 'y' : 'x';
28007 },
28008 icon: Icons.tooltip_compare,
28009 gravity: 'ne',
28010 click: handleCartesian
28011};
28012
28013function handleCartesian(gd, ev) {
28014 var button = ev.currentTarget;
28015 var astr = button.getAttribute('data-attr');
28016 var val = button.getAttribute('data-val') || true;
28017 var fullLayout = gd._fullLayout;
28018 var aobj = {};
28019 var axList = axisIds.list(gd, null, true);
28020 var allSpikesEnabled = fullLayout._cartesianSpikesEnabled;
28021
28022 var ax, i;
28023
28024 if(astr === 'zoom') {
28025 var mag = (val === 'in') ? 0.5 : 2;
28026 var r0 = (1 + mag) / 2;
28027 var r1 = (1 - mag) / 2;
28028 var axName;
28029
28030 for(i = 0; i < axList.length; i++) {
28031 ax = axList[i];
28032
28033 if(!ax.fixedrange) {
28034 axName = ax._name;
28035 if(val === 'auto') {
28036 aobj[axName + '.autorange'] = true;
28037 } else if(val === 'reset') {
28038 if(ax._rangeInitial === undefined) {
28039 aobj[axName + '.autorange'] = true;
28040 } else {
28041 var rangeInitial = ax._rangeInitial.slice();
28042 aobj[axName + '.range[0]'] = rangeInitial[0];
28043 aobj[axName + '.range[1]'] = rangeInitial[1];
28044 }
28045
28046 // N.B. "reset" also resets showspikes
28047 if(ax._showSpikeInitial !== undefined) {
28048 aobj[axName + '.showspikes'] = ax._showSpikeInitial;
28049 if(allSpikesEnabled === 'on' && !ax._showSpikeInitial) {
28050 allSpikesEnabled = 'off';
28051 }
28052 }
28053 } else {
28054 var rangeNow = [
28055 ax.r2l(ax.range[0]),
28056 ax.r2l(ax.range[1]),
28057 ];
28058
28059 var rangeNew = [
28060 r0 * rangeNow[0] + r1 * rangeNow[1],
28061 r0 * rangeNow[1] + r1 * rangeNow[0]
28062 ];
28063
28064 aobj[axName + '.range[0]'] = ax.l2r(rangeNew[0]);
28065 aobj[axName + '.range[1]'] = ax.l2r(rangeNew[1]);
28066 }
28067 }
28068 }
28069 } else {
28070 // if ALL traces have orientation 'h', 'hovermode': 'x' otherwise: 'y'
28071 if(astr === 'hovermode' && (val === 'x' || val === 'y')) {
28072 val = fullLayout._isHoriz ? 'y' : 'x';
28073 button.setAttribute('data-val', val);
28074 }
28075
28076 aobj[astr] = val;
28077 }
28078
28079 fullLayout._cartesianSpikesEnabled = allSpikesEnabled;
28080
28081 Registry.call('_guiRelayout', gd, aobj);
28082}
28083
28084modeBarButtons.zoom3d = {
28085 name: 'zoom3d',
28086 title: function(gd) { return _(gd, 'Zoom'); },
28087 attr: 'scene.dragmode',
28088 val: 'zoom',
28089 icon: Icons.zoombox,
28090 click: handleDrag3d
28091};
28092
28093modeBarButtons.pan3d = {
28094 name: 'pan3d',
28095 title: function(gd) { return _(gd, 'Pan'); },
28096 attr: 'scene.dragmode',
28097 val: 'pan',
28098 icon: Icons.pan,
28099 click: handleDrag3d
28100};
28101
28102modeBarButtons.orbitRotation = {
28103 name: 'orbitRotation',
28104 title: function(gd) { return _(gd, 'Orbital rotation'); },
28105 attr: 'scene.dragmode',
28106 val: 'orbit',
28107 icon: Icons['3d_rotate'],
28108 click: handleDrag3d
28109};
28110
28111modeBarButtons.tableRotation = {
28112 name: 'tableRotation',
28113 title: function(gd) { return _(gd, 'Turntable rotation'); },
28114 attr: 'scene.dragmode',
28115 val: 'turntable',
28116 icon: Icons['z-axis'],
28117 click: handleDrag3d
28118};
28119
28120function handleDrag3d(gd, ev) {
28121 var button = ev.currentTarget;
28122 var attr = button.getAttribute('data-attr');
28123 var val = button.getAttribute('data-val') || true;
28124 var sceneIds = gd._fullLayout._subplots.gl3d || [];
28125 var layoutUpdate = {};
28126
28127 var parts = attr.split('.');
28128
28129 for(var i = 0; i < sceneIds.length; i++) {
28130 layoutUpdate[sceneIds[i] + '.' + parts[1]] = val;
28131 }
28132
28133 // for multi-type subplots
28134 var val2d = (val === 'pan') ? val : 'zoom';
28135 layoutUpdate.dragmode = val2d;
28136
28137 Registry.call('_guiRelayout', gd, layoutUpdate);
28138}
28139
28140modeBarButtons.resetCameraDefault3d = {
28141 name: 'resetCameraDefault3d',
28142 title: function(gd) { return _(gd, 'Reset camera to default'); },
28143 attr: 'resetDefault',
28144 icon: Icons.home,
28145 click: handleCamera3d
28146};
28147
28148modeBarButtons.resetCameraLastSave3d = {
28149 name: 'resetCameraLastSave3d',
28150 title: function(gd) { return _(gd, 'Reset camera to last save'); },
28151 attr: 'resetLastSave',
28152 icon: Icons.movie,
28153 click: handleCamera3d
28154};
28155
28156function handleCamera3d(gd, ev) {
28157 var button = ev.currentTarget;
28158 var attr = button.getAttribute('data-attr');
28159 var fullLayout = gd._fullLayout;
28160 var sceneIds = fullLayout._subplots.gl3d || [];
28161 var aobj = {};
28162
28163 for(var i = 0; i < sceneIds.length; i++) {
28164 var sceneId = sceneIds[i];
28165 var camera = sceneId + '.camera';
28166 var aspectratio = sceneId + '.aspectratio';
28167 var aspectmode = sceneId + '.aspectmode';
28168 var scene = fullLayout[sceneId]._scene;
28169 var didUpdate;
28170
28171 if(attr === 'resetLastSave') {
28172 aobj[camera + '.up'] = scene.viewInitial.up;
28173 aobj[camera + '.eye'] = scene.viewInitial.eye;
28174 aobj[camera + '.center'] = scene.viewInitial.center;
28175 didUpdate = true;
28176 } else if(attr === 'resetDefault') {
28177 aobj[camera + '.up'] = null;
28178 aobj[camera + '.eye'] = null;
28179 aobj[camera + '.center'] = null;
28180 didUpdate = true;
28181 }
28182
28183 if(didUpdate) {
28184 aobj[aspectratio + '.x'] = scene.viewInitial.aspectratio.x;
28185 aobj[aspectratio + '.y'] = scene.viewInitial.aspectratio.y;
28186 aobj[aspectratio + '.z'] = scene.viewInitial.aspectratio.z;
28187 aobj[aspectmode] = scene.viewInitial.aspectmode;
28188 }
28189 }
28190
28191 Registry.call('_guiRelayout', gd, aobj);
28192}
28193
28194modeBarButtons.hoverClosest3d = {
28195 name: 'hoverClosest3d',
28196 title: function(gd) { return _(gd, 'Toggle show closest data on hover'); },
28197 attr: 'hovermode',
28198 val: null,
28199 toggle: true,
28200 icon: Icons.tooltip_basic,
28201 gravity: 'ne',
28202 click: handleHover3d
28203};
28204
28205function getNextHover3d(gd, ev) {
28206 var button = ev.currentTarget;
28207 var val = button._previousVal;
28208 var fullLayout = gd._fullLayout;
28209 var sceneIds = fullLayout._subplots.gl3d || [];
28210
28211 var axes = ['xaxis', 'yaxis', 'zaxis'];
28212
28213 // initialize 'current spike' object to be stored in the DOM
28214 var currentSpikes = {};
28215 var layoutUpdate = {};
28216
28217 if(val) {
28218 layoutUpdate = val;
28219 button._previousVal = null;
28220 } else {
28221 for(var i = 0; i < sceneIds.length; i++) {
28222 var sceneId = sceneIds[i];
28223 var sceneLayout = fullLayout[sceneId];
28224
28225 var hovermodeAStr = sceneId + '.hovermode';
28226 currentSpikes[hovermodeAStr] = sceneLayout.hovermode;
28227 layoutUpdate[hovermodeAStr] = false;
28228
28229 // copy all the current spike attrs
28230 for(var j = 0; j < 3; j++) {
28231 var axis = axes[j];
28232 var spikeAStr = sceneId + '.' + axis + '.showspikes';
28233 layoutUpdate[spikeAStr] = false;
28234 currentSpikes[spikeAStr] = sceneLayout[axis].showspikes;
28235 }
28236 }
28237
28238 button._previousVal = currentSpikes;
28239 }
28240 return layoutUpdate;
28241}
28242
28243function handleHover3d(gd, ev) {
28244 var layoutUpdate = getNextHover3d(gd, ev);
28245 Registry.call('_guiRelayout', gd, layoutUpdate);
28246}
28247
28248modeBarButtons.zoomInGeo = {
28249 name: 'zoomInGeo',
28250 title: function(gd) { return _(gd, 'Zoom in'); },
28251 attr: 'zoom',
28252 val: 'in',
28253 icon: Icons.zoom_plus,
28254 click: handleGeo
28255};
28256
28257modeBarButtons.zoomOutGeo = {
28258 name: 'zoomOutGeo',
28259 title: function(gd) { return _(gd, 'Zoom out'); },
28260 attr: 'zoom',
28261 val: 'out',
28262 icon: Icons.zoom_minus,
28263 click: handleGeo
28264};
28265
28266modeBarButtons.resetGeo = {
28267 name: 'resetGeo',
28268 title: function(gd) { return _(gd, 'Reset'); },
28269 attr: 'reset',
28270 val: null,
28271 icon: Icons.autoscale,
28272 click: handleGeo
28273};
28274
28275modeBarButtons.hoverClosestGeo = {
28276 name: 'hoverClosestGeo',
28277 title: function(gd) { return _(gd, 'Toggle show closest data on hover'); },
28278 attr: 'hovermode',
28279 val: null,
28280 toggle: true,
28281 icon: Icons.tooltip_basic,
28282 gravity: 'ne',
28283 click: toggleHover
28284};
28285
28286function handleGeo(gd, ev) {
28287 var button = ev.currentTarget;
28288 var attr = button.getAttribute('data-attr');
28289 var val = button.getAttribute('data-val') || true;
28290 var fullLayout = gd._fullLayout;
28291 var geoIds = fullLayout._subplots.geo || [];
28292
28293 for(var i = 0; i < geoIds.length; i++) {
28294 var id = geoIds[i];
28295 var geoLayout = fullLayout[id];
28296
28297 if(attr === 'zoom') {
28298 var scale = geoLayout.projection.scale;
28299 var newScale = (val === 'in') ? 2 * scale : 0.5 * scale;
28300
28301 Registry.call('_guiRelayout', gd, id + '.projection.scale', newScale);
28302 }
28303 }
28304
28305 if(attr === 'reset') {
28306 resetView(gd, 'geo');
28307 }
28308}
28309
28310modeBarButtons.hoverClosestGl2d = {
28311 name: 'hoverClosestGl2d',
28312 title: function(gd) { return _(gd, 'Toggle show closest data on hover'); },
28313 attr: 'hovermode',
28314 val: null,
28315 toggle: true,
28316 icon: Icons.tooltip_basic,
28317 gravity: 'ne',
28318 click: toggleHover
28319};
28320
28321modeBarButtons.hoverClosestPie = {
28322 name: 'hoverClosestPie',
28323 title: function(gd) { return _(gd, 'Toggle show closest data on hover'); },
28324 attr: 'hovermode',
28325 val: 'closest',
28326 icon: Icons.tooltip_basic,
28327 gravity: 'ne',
28328 click: toggleHover
28329};
28330
28331function getNextHover(gd) {
28332 var fullLayout = gd._fullLayout;
28333
28334 if(fullLayout.hovermode) return false;
28335
28336 if(fullLayout._has('cartesian')) {
28337 return fullLayout._isHoriz ? 'y' : 'x';
28338 }
28339 return 'closest';
28340}
28341
28342function toggleHover(gd) {
28343 var newHover = getNextHover(gd);
28344 Registry.call('_guiRelayout', gd, 'hovermode', newHover);
28345}
28346
28347modeBarButtons.resetViewSankey = {
28348 name: 'resetSankeyGroup',
28349 title: function(gd) { return _(gd, 'Reset view'); },
28350 icon: Icons.home,
28351 click: function(gd) {
28352 var aObj = {
28353 'node.groups': [],
28354 'node.x': [],
28355 'node.y': []
28356 };
28357 for(var i = 0; i < gd._fullData.length; i++) {
28358 var viewInitial = gd._fullData[i]._viewInitial;
28359 aObj['node.groups'].push(viewInitial.node.groups.slice());
28360 aObj['node.x'].push(viewInitial.node.x.slice());
28361 aObj['node.y'].push(viewInitial.node.y.slice());
28362 }
28363 Registry.call('restyle', gd, aObj);
28364 }
28365};
28366
28367// buttons when more then one plot types are present
28368
28369modeBarButtons.toggleHover = {
28370 name: 'toggleHover',
28371 title: function(gd) { return _(gd, 'Toggle show closest data on hover'); },
28372 attr: 'hovermode',
28373 val: null,
28374 toggle: true,
28375 icon: Icons.tooltip_basic,
28376 gravity: 'ne',
28377 click: function(gd, ev) {
28378 var layoutUpdate = getNextHover3d(gd, ev);
28379 layoutUpdate.hovermode = getNextHover(gd);
28380
28381 Registry.call('_guiRelayout', gd, layoutUpdate);
28382 }
28383};
28384
28385modeBarButtons.resetViews = {
28386 name: 'resetViews',
28387 title: function(gd) { return _(gd, 'Reset views'); },
28388 icon: Icons.home,
28389 click: function(gd, ev) {
28390 var button = ev.currentTarget;
28391
28392 button.setAttribute('data-attr', 'zoom');
28393 button.setAttribute('data-val', 'reset');
28394 handleCartesian(gd, ev);
28395
28396 button.setAttribute('data-attr', 'resetLastSave');
28397 handleCamera3d(gd, ev);
28398
28399 resetView(gd, 'geo');
28400 resetView(gd, 'mapbox');
28401 }
28402};
28403
28404modeBarButtons.toggleSpikelines = {
28405 name: 'toggleSpikelines',
28406 title: function(gd) { return _(gd, 'Toggle Spike Lines'); },
28407 icon: Icons.spikeline,
28408 attr: '_cartesianSpikesEnabled',
28409 val: 'on',
28410 click: function(gd) {
28411 var fullLayout = gd._fullLayout;
28412 var allSpikesEnabled = fullLayout._cartesianSpikesEnabled;
28413
28414 fullLayout._cartesianSpikesEnabled = allSpikesEnabled === 'on' ? 'off' : 'on';
28415 Registry.call('_guiRelayout', gd, setSpikelineVisibility(gd));
28416 }
28417};
28418
28419function setSpikelineVisibility(gd) {
28420 var fullLayout = gd._fullLayout;
28421 var areSpikesOn = fullLayout._cartesianSpikesEnabled === 'on';
28422 var axList = axisIds.list(gd, null, true);
28423 var aobj = {};
28424
28425 for(var i = 0; i < axList.length; i++) {
28426 var ax = axList[i];
28427 aobj[ax._name + '.showspikes'] = areSpikesOn ? true : ax._showSpikeInitial;
28428 }
28429
28430 return aobj;
28431}
28432
28433modeBarButtons.resetViewMapbox = {
28434 name: 'resetViewMapbox',
28435 title: function(gd) { return _(gd, 'Reset view'); },
28436 attr: 'reset',
28437 icon: Icons.home,
28438 click: function(gd) {
28439 resetView(gd, 'mapbox');
28440 }
28441};
28442
28443modeBarButtons.zoomInMapbox = {
28444 name: 'zoomInMapbox',
28445 title: function(gd) { return _(gd, 'Zoom in'); },
28446 attr: 'zoom',
28447 val: 'in',
28448 icon: Icons.zoom_plus,
28449 click: handleMapboxZoom
28450};
28451
28452modeBarButtons.zoomOutMapbox = {
28453 name: 'zoomOutMapbox',
28454 title: function(gd) { return _(gd, 'Zoom out'); },
28455 attr: 'zoom',
28456 val: 'out',
28457 icon: Icons.zoom_minus,
28458 click: handleMapboxZoom
28459};
28460
28461function handleMapboxZoom(gd, ev) {
28462 var button = ev.currentTarget;
28463 var val = button.getAttribute('data-val');
28464 var fullLayout = gd._fullLayout;
28465 var subplotIds = fullLayout._subplots.mapbox || [];
28466 var scalar = 1.05;
28467 var aObj = {};
28468
28469 for(var i = 0; i < subplotIds.length; i++) {
28470 var id = subplotIds[i];
28471 var current = fullLayout[id].zoom;
28472 var next = (val === 'in') ? scalar * current : current / scalar;
28473 aObj[id + '.zoom'] = next;
28474 }
28475
28476 Registry.call('_guiRelayout', gd, aObj);
28477}
28478
28479function resetView(gd, subplotType) {
28480 var fullLayout = gd._fullLayout;
28481 var subplotIds = fullLayout._subplots[subplotType] || [];
28482 var aObj = {};
28483
28484 for(var i = 0; i < subplotIds.length; i++) {
28485 var id = subplotIds[i];
28486 var subplotObj = fullLayout[id]._subplot;
28487 var viewInitial = subplotObj.viewInitial;
28488 var viewKeys = Object.keys(viewInitial);
28489
28490 for(var j = 0; j < viewKeys.length; j++) {
28491 var key = viewKeys[j];
28492 aObj[id + '.' + key] = viewInitial[key];
28493 }
28494 }
28495
28496 Registry.call('_guiRelayout', gd, aObj);
28497}
28498
28499},{"../../fonts/ploticon":162,"../../lib":178,"../../plots/cartesian/axis_ids":225,"../../plots/plots":256,"../../registry":269,"../shapes/draw":133}],112:[function(_dereq_,module,exports){
28500/**
28501* Copyright 2012-2020, Plotly, Inc.
28502* All rights reserved.
28503*
28504* This source code is licensed under the MIT license found in the
28505* LICENSE file in the root directory of this source tree.
28506*/
28507
28508
28509'use strict';
28510
28511exports.manage = _dereq_('./manage');
28512
28513},{"./manage":113}],113:[function(_dereq_,module,exports){
28514/**
28515* Copyright 2012-2020, Plotly, Inc.
28516* All rights reserved.
28517*
28518* This source code is licensed under the MIT license found in the
28519* LICENSE file in the root directory of this source tree.
28520*/
28521
28522
28523'use strict';
28524
28525var axisIds = _dereq_('../../plots/cartesian/axis_ids');
28526var scatterSubTypes = _dereq_('../../traces/scatter/subtypes');
28527var Registry = _dereq_('../../registry');
28528var isUnifiedHover = _dereq_('../fx/helpers').isUnifiedHover;
28529
28530var createModeBar = _dereq_('./modebar');
28531var modeBarButtons = _dereq_('./buttons');
28532
28533/**
28534 * ModeBar wrapper around 'create' and 'update',
28535 * chooses buttons to pass to ModeBar constructor based on
28536 * plot type and plot config.
28537 *
28538 * @param {object} gd main plot object
28539 *
28540 */
28541module.exports = function manageModeBar(gd) {
28542 var fullLayout = gd._fullLayout;
28543 var context = gd._context;
28544 var modeBar = fullLayout._modeBar;
28545
28546 if(!context.displayModeBar && !context.watermark) {
28547 if(modeBar) {
28548 modeBar.destroy();
28549 delete fullLayout._modeBar;
28550 }
28551 return;
28552 }
28553
28554 if(!Array.isArray(context.modeBarButtonsToRemove)) {
28555 throw new Error([
28556 '*modeBarButtonsToRemove* configuration options',
28557 'must be an array.'
28558 ].join(' '));
28559 }
28560
28561 if(!Array.isArray(context.modeBarButtonsToAdd)) {
28562 throw new Error([
28563 '*modeBarButtonsToAdd* configuration options',
28564 'must be an array.'
28565 ].join(' '));
28566 }
28567
28568 var customButtons = context.modeBarButtons;
28569 var buttonGroups;
28570
28571 if(Array.isArray(customButtons) && customButtons.length) {
28572 buttonGroups = fillCustomButton(customButtons);
28573 } else if(!context.displayModeBar && context.watermark) {
28574 buttonGroups = [];
28575 } else {
28576 buttonGroups = getButtonGroups(gd);
28577 }
28578
28579 if(modeBar) modeBar.update(gd, buttonGroups);
28580 else fullLayout._modeBar = createModeBar(gd, buttonGroups);
28581};
28582
28583var DRAW_MODES = [
28584 'drawline',
28585 'drawopenpath',
28586 'drawclosedpath',
28587 'drawcircle',
28588 'drawrect',
28589 'eraseshape'
28590];
28591
28592// logic behind which buttons are displayed by default
28593function getButtonGroups(gd) {
28594 var fullLayout = gd._fullLayout;
28595 var fullData = gd._fullData;
28596 var context = gd._context;
28597 var buttonsToRemove = context.modeBarButtonsToRemove;
28598 var buttonsToAdd = context.modeBarButtonsToAdd;
28599
28600 var hasCartesian = fullLayout._has('cartesian');
28601 var hasGL3D = fullLayout._has('gl3d');
28602 var hasGeo = fullLayout._has('geo');
28603 var hasPie = fullLayout._has('pie');
28604 var hasFunnelarea = fullLayout._has('funnelarea');
28605 var hasGL2D = fullLayout._has('gl2d');
28606 var hasTernary = fullLayout._has('ternary');
28607 var hasMapbox = fullLayout._has('mapbox');
28608 var hasPolar = fullLayout._has('polar');
28609 var hasSankey = fullLayout._has('sankey');
28610 var allAxesFixed = areAllAxesFixed(fullLayout);
28611 var hasUnifiedHoverLabel = isUnifiedHover(fullLayout.hovermode);
28612
28613 var groups = [];
28614
28615 function addGroup(newGroup) {
28616 if(!newGroup.length) return;
28617
28618 var out = [];
28619
28620 for(var i = 0; i < newGroup.length; i++) {
28621 var button = newGroup[i];
28622 if(buttonsToRemove.indexOf(button) !== -1) continue;
28623 out.push(modeBarButtons[button]);
28624 }
28625
28626 groups.push(out);
28627 }
28628
28629 // buttons common to all plot types
28630 var commonGroup = ['toImage'];
28631 if(context.showEditInChartStudio) commonGroup.push('editInChartStudio');
28632 else if(context.showSendToCloud) commonGroup.push('sendDataToCloud');
28633 addGroup(commonGroup);
28634
28635 var zoomGroup = [];
28636 var hoverGroup = [];
28637 var resetGroup = [];
28638 var dragModeGroup = [];
28639
28640 if((hasCartesian || hasGL2D || hasPie || hasFunnelarea || hasTernary) + hasGeo + hasGL3D + hasMapbox + hasPolar > 1) {
28641 // graphs with more than one plot types get 'union buttons'
28642 // which reset the view or toggle hover labels across all subplots.
28643 hoverGroup = ['toggleHover'];
28644 resetGroup = ['resetViews'];
28645 } else if(hasGeo) {
28646 zoomGroup = ['zoomInGeo', 'zoomOutGeo'];
28647 hoverGroup = ['hoverClosestGeo'];
28648 resetGroup = ['resetGeo'];
28649 } else if(hasGL3D) {
28650 hoverGroup = ['hoverClosest3d'];
28651 resetGroup = ['resetCameraDefault3d', 'resetCameraLastSave3d'];
28652 } else if(hasMapbox) {
28653 zoomGroup = ['zoomInMapbox', 'zoomOutMapbox'];
28654 hoverGroup = ['toggleHover'];
28655 resetGroup = ['resetViewMapbox'];
28656 } else if(hasGL2D) {
28657 hoverGroup = ['hoverClosestGl2d'];
28658 } else if(hasPie) {
28659 hoverGroup = ['hoverClosestPie'];
28660 } else if(hasSankey) {
28661 hoverGroup = ['hoverClosestCartesian', 'hoverCompareCartesian'];
28662 resetGroup = ['resetViewSankey'];
28663 } else { // hasPolar, hasTernary
28664 // always show at least one hover icon.
28665 hoverGroup = ['toggleHover'];
28666 }
28667 // if we have cartesian, allow switching between closest and compare
28668 // regardless of what other types are on the plot, since they'll all
28669 // just treat any truthy hovermode as 'closest'
28670 if(hasCartesian) {
28671 hoverGroup = ['toggleSpikelines', 'hoverClosestCartesian', 'hoverCompareCartesian'];
28672 }
28673 if(hasNoHover(fullData) || hasUnifiedHoverLabel) {
28674 hoverGroup = [];
28675 }
28676
28677 if((hasCartesian || hasGL2D) && !allAxesFixed) {
28678 zoomGroup = ['zoomIn2d', 'zoomOut2d', 'autoScale2d'];
28679 if(resetGroup[0] !== 'resetViews') resetGroup = ['resetScale2d'];
28680 }
28681
28682 if(hasGL3D) {
28683 dragModeGroup = ['zoom3d', 'pan3d', 'orbitRotation', 'tableRotation'];
28684 } else if(((hasCartesian || hasGL2D) && !allAxesFixed) || hasTernary) {
28685 dragModeGroup = ['zoom2d', 'pan2d'];
28686 } else if(hasMapbox || hasGeo) {
28687 dragModeGroup = ['pan2d'];
28688 } else if(hasPolar) {
28689 dragModeGroup = ['zoom2d'];
28690 }
28691 if(isSelectable(fullData)) {
28692 dragModeGroup.push('select2d', 'lasso2d');
28693 }
28694
28695 // accept pre-defined buttons as string
28696 if(Array.isArray(buttonsToAdd)) {
28697 var newList = [];
28698 for(var i = 0; i < buttonsToAdd.length; i++) {
28699 var b = buttonsToAdd[i];
28700 if(typeof b === 'string') {
28701 if(DRAW_MODES.indexOf(b) !== -1) {
28702 if(
28703 fullLayout._has('mapbox') || // draw shapes in paper coordinate (could be improved in future to support data coordinate, when there is no pitch)
28704 fullLayout._has('cartesian') // draw shapes in data coordinate
28705 ) {
28706 dragModeGroup.push(b);
28707 }
28708 }
28709 } else newList.push(b);
28710 }
28711 buttonsToAdd = newList;
28712 }
28713
28714 addGroup(dragModeGroup);
28715 addGroup(zoomGroup.concat(resetGroup));
28716 addGroup(hoverGroup);
28717
28718 return appendButtonsToGroups(groups, buttonsToAdd);
28719}
28720
28721function areAllAxesFixed(fullLayout) {
28722 var axList = axisIds.list({_fullLayout: fullLayout}, null, true);
28723
28724 for(var i = 0; i < axList.length; i++) {
28725 if(!axList[i].fixedrange) {
28726 return false;
28727 }
28728 }
28729
28730 return true;
28731}
28732
28733// look for traces that support selection
28734// to be updated as we add more selectPoints handlers
28735function isSelectable(fullData) {
28736 var selectable = false;
28737
28738 for(var i = 0; i < fullData.length; i++) {
28739 if(selectable) break;
28740
28741 var trace = fullData[i];
28742
28743 if(!trace._module || !trace._module.selectPoints) continue;
28744
28745 if(Registry.traceIs(trace, 'scatter-like')) {
28746 if(scatterSubTypes.hasMarkers(trace) || scatterSubTypes.hasText(trace)) {
28747 selectable = true;
28748 }
28749 } else if(Registry.traceIs(trace, 'box-violin')) {
28750 if(trace.boxpoints === 'all' || trace.points === 'all') {
28751 selectable = true;
28752 }
28753 } else {
28754 // assume that in general if the trace module has selectPoints,
28755 // then it's selectable. Scatter is an exception to this because it must
28756 // have markers or text, not just be a scatter type.
28757
28758 selectable = true;
28759 }
28760 }
28761
28762 return selectable;
28763}
28764
28765// check whether all trace are 'noHover'
28766function hasNoHover(fullData) {
28767 for(var i = 0; i < fullData.length; i++) {
28768 if(!Registry.traceIs(fullData[i], 'noHover')) return false;
28769 }
28770 return true;
28771}
28772
28773function appendButtonsToGroups(groups, buttons) {
28774 if(buttons.length) {
28775 if(Array.isArray(buttons[0])) {
28776 for(var i = 0; i < buttons.length; i++) {
28777 groups.push(buttons[i]);
28778 }
28779 } else groups.push(buttons);
28780 }
28781
28782 return groups;
28783}
28784
28785// fill in custom buttons referring to default mode bar buttons
28786function fillCustomButton(customButtons) {
28787 for(var i = 0; i < customButtons.length; i++) {
28788 var buttonGroup = customButtons[i];
28789
28790 for(var j = 0; j < buttonGroup.length; j++) {
28791 var button = buttonGroup[j];
28792
28793 if(typeof button === 'string') {
28794 if(modeBarButtons[button] !== undefined) {
28795 customButtons[i][j] = modeBarButtons[button];
28796 } else {
28797 throw new Error([
28798 '*modeBarButtons* configuration options',
28799 'invalid button name'
28800 ].join(' '));
28801 }
28802 }
28803 }
28804 }
28805
28806 return customButtons;
28807}
28808
28809},{"../../plots/cartesian/axis_ids":225,"../../registry":269,"../../traces/scatter/subtypes":413,"../fx/helpers":88,"./buttons":111,"./modebar":114}],114:[function(_dereq_,module,exports){
28810/**
28811* Copyright 2012-2020, Plotly, Inc.
28812* All rights reserved.
28813*
28814* This source code is licensed under the MIT license found in the
28815* LICENSE file in the root directory of this source tree.
28816*/
28817
28818
28819'use strict';
28820
28821var d3 = _dereq_('d3');
28822var isNumeric = _dereq_('fast-isnumeric');
28823
28824var Lib = _dereq_('../../lib');
28825var Icons = _dereq_('../../fonts/ploticon');
28826var Parser = new DOMParser();
28827
28828/**
28829 * UI controller for interactive plots
28830 * @Class
28831 * @Param {object} opts
28832 * @Param {object} opts.buttons nested arrays of grouped buttons config objects
28833 * @Param {object} opts.container container div to append modeBar
28834 * @Param {object} opts.graphInfo primary plot object containing data and layout
28835 */
28836function ModeBar(opts) {
28837 this.container = opts.container;
28838 this.element = document.createElement('div');
28839
28840 this.update(opts.graphInfo, opts.buttons);
28841
28842 this.container.appendChild(this.element);
28843}
28844
28845var proto = ModeBar.prototype;
28846
28847/**
28848 * Update modeBar (buttons and logo)
28849 *
28850 * @param {object} graphInfo primary plot object containing data and layout
28851 * @param {array of arrays} buttons nested arrays of grouped buttons to initialize
28852 *
28853 */
28854proto.update = function(graphInfo, buttons) {
28855 this.graphInfo = graphInfo;
28856
28857 var context = this.graphInfo._context;
28858 var fullLayout = this.graphInfo._fullLayout;
28859 var modeBarId = 'modebar-' + fullLayout._uid;
28860
28861 this.element.setAttribute('id', modeBarId);
28862 this._uid = modeBarId;
28863
28864 this.element.className = 'modebar';
28865 if(context.displayModeBar === 'hover') this.element.className += ' modebar--hover ease-bg';
28866
28867 if(fullLayout.modebar.orientation === 'v') {
28868 this.element.className += ' vertical';
28869 buttons = buttons.reverse();
28870 }
28871
28872 var style = fullLayout.modebar;
28873 var bgSelector = context.displayModeBar === 'hover' ? '.js-plotly-plot .plotly:hover ' : '';
28874
28875 Lib.deleteRelatedStyleRule(modeBarId);
28876 Lib.addRelatedStyleRule(modeBarId, bgSelector + '#' + modeBarId + ' .modebar-group', 'background-color: ' + style.bgcolor);
28877 Lib.addRelatedStyleRule(modeBarId, '#' + modeBarId + ' .modebar-btn .icon path', 'fill: ' + style.color);
28878 Lib.addRelatedStyleRule(modeBarId, '#' + modeBarId + ' .modebar-btn:hover .icon path', 'fill: ' + style.activecolor);
28879 Lib.addRelatedStyleRule(modeBarId, '#' + modeBarId + ' .modebar-btn.active .icon path', 'fill: ' + style.activecolor);
28880
28881 // if buttons or logo have changed, redraw modebar interior
28882 var needsNewButtons = !this.hasButtons(buttons);
28883 var needsNewLogo = (this.hasLogo !== context.displaylogo);
28884 var needsNewLocale = (this.locale !== context.locale);
28885
28886 this.locale = context.locale;
28887
28888 if(needsNewButtons || needsNewLogo || needsNewLocale) {
28889 this.removeAllButtons();
28890
28891 this.updateButtons(buttons);
28892
28893 if(context.watermark || context.displaylogo) {
28894 var logoGroup = this.getLogo();
28895 if(context.watermark) {
28896 logoGroup.className = logoGroup.className + ' watermark';
28897 }
28898
28899 if(fullLayout.modebar.orientation === 'v') {
28900 this.element.insertBefore(logoGroup, this.element.childNodes[0]);
28901 } else {
28902 this.element.appendChild(logoGroup);
28903 }
28904
28905 this.hasLogo = true;
28906 }
28907 }
28908
28909 this.updateActiveButton();
28910};
28911
28912proto.updateButtons = function(buttons) {
28913 var _this = this;
28914
28915 this.buttons = buttons;
28916 this.buttonElements = [];
28917 this.buttonsNames = [];
28918
28919 this.buttons.forEach(function(buttonGroup) {
28920 var group = _this.createGroup();
28921
28922 buttonGroup.forEach(function(buttonConfig) {
28923 var buttonName = buttonConfig.name;
28924 if(!buttonName) {
28925 throw new Error('must provide button \'name\' in button config');
28926 }
28927 if(_this.buttonsNames.indexOf(buttonName) !== -1) {
28928 throw new Error('button name \'' + buttonName + '\' is taken');
28929 }
28930 _this.buttonsNames.push(buttonName);
28931
28932 var button = _this.createButton(buttonConfig);
28933 _this.buttonElements.push(button);
28934 group.appendChild(button);
28935 });
28936
28937 _this.element.appendChild(group);
28938 });
28939};
28940
28941/**
28942 * Empty div for containing a group of buttons
28943 * @Return {HTMLelement}
28944 */
28945proto.createGroup = function() {
28946 var group = document.createElement('div');
28947 group.className = 'modebar-group';
28948 return group;
28949};
28950
28951/**
28952 * Create a new button div and set constant and configurable attributes
28953 * @Param {object} config (see ./buttons.js for more info)
28954 * @Return {HTMLelement}
28955 */
28956proto.createButton = function(config) {
28957 var _this = this;
28958 var button = document.createElement('a');
28959
28960 button.setAttribute('rel', 'tooltip');
28961 button.className = 'modebar-btn';
28962
28963 var title = config.title;
28964 if(title === undefined) title = config.name;
28965 // for localization: allow title to be a callable that takes gd as arg
28966 else if(typeof title === 'function') title = title(this.graphInfo);
28967
28968 if(title || title === 0) button.setAttribute('data-title', title);
28969
28970 if(config.attr !== undefined) button.setAttribute('data-attr', config.attr);
28971
28972 var val = config.val;
28973 if(val !== undefined) {
28974 if(typeof val === 'function') val = val(this.graphInfo);
28975 button.setAttribute('data-val', val);
28976 }
28977
28978 var click = config.click;
28979 if(typeof click !== 'function') {
28980 throw new Error('must provide button \'click\' function in button config');
28981 } else {
28982 button.addEventListener('click', function(ev) {
28983 config.click(_this.graphInfo, ev);
28984
28985 // only needed for 'hoverClosestGeo' which does not call relayout
28986 _this.updateActiveButton(ev.currentTarget);
28987 });
28988 }
28989
28990 button.setAttribute('data-toggle', config.toggle || false);
28991 if(config.toggle) d3.select(button).classed('active', true);
28992
28993 var icon = config.icon;
28994 if(typeof icon === 'function') {
28995 button.appendChild(icon());
28996 } else {
28997 button.appendChild(this.createIcon(icon || Icons.question));
28998 }
28999 button.setAttribute('data-gravity', config.gravity || 'n');
29000
29001 return button;
29002};
29003
29004/**
29005 * Add an icon to a button
29006 * @Param {object} thisIcon
29007 * @Param {number} thisIcon.width
29008 * @Param {string} thisIcon.path
29009 * @Param {string} thisIcon.color
29010 * @Return {HTMLelement}
29011 */
29012proto.createIcon = function(thisIcon) {
29013 var iconHeight = isNumeric(thisIcon.height) ?
29014 Number(thisIcon.height) :
29015 thisIcon.ascent - thisIcon.descent;
29016 var svgNS = 'http://www.w3.org/2000/svg';
29017 var icon;
29018
29019 if(thisIcon.path) {
29020 icon = document.createElementNS(svgNS, 'svg');
29021 icon.setAttribute('viewBox', [0, 0, thisIcon.width, iconHeight].join(' '));
29022 icon.setAttribute('class', 'icon');
29023
29024 var path = document.createElementNS(svgNS, 'path');
29025 path.setAttribute('d', thisIcon.path);
29026
29027 if(thisIcon.transform) {
29028 path.setAttribute('transform', thisIcon.transform);
29029 } else if(thisIcon.ascent !== undefined) {
29030 // Legacy icon transform calculation
29031 path.setAttribute('transform', 'matrix(1 0 0 -1 0 ' + thisIcon.ascent + ')');
29032 }
29033
29034 icon.appendChild(path);
29035 }
29036
29037 if(thisIcon.svg) {
29038 var svgDoc = Parser.parseFromString(thisIcon.svg, 'application/xml');
29039 icon = svgDoc.childNodes[0];
29040 }
29041
29042 icon.setAttribute('height', '1em');
29043 icon.setAttribute('width', '1em');
29044
29045 return icon;
29046};
29047
29048/**
29049 * Updates active button with attribute specified in layout
29050 * @Param {object} graphInfo plot object containing data and layout
29051 * @Return {HTMLelement}
29052 */
29053proto.updateActiveButton = function(buttonClicked) {
29054 var fullLayout = this.graphInfo._fullLayout;
29055 var dataAttrClicked = (buttonClicked !== undefined) ?
29056 buttonClicked.getAttribute('data-attr') :
29057 null;
29058
29059 this.buttonElements.forEach(function(button) {
29060 var thisval = button.getAttribute('data-val') || true;
29061 var dataAttr = button.getAttribute('data-attr');
29062 var isToggleButton = (button.getAttribute('data-toggle') === 'true');
29063 var button3 = d3.select(button);
29064
29065 // Use 'data-toggle' and 'buttonClicked' to toggle buttons
29066 // that have no one-to-one equivalent in fullLayout
29067 if(isToggleButton) {
29068 if(dataAttr === dataAttrClicked) {
29069 button3.classed('active', !button3.classed('active'));
29070 }
29071 } else {
29072 var val = (dataAttr === null) ?
29073 dataAttr :
29074 Lib.nestedProperty(fullLayout, dataAttr).get();
29075
29076 button3.classed('active', val === thisval);
29077 }
29078 });
29079};
29080
29081/**
29082 * Check if modeBar is configured as button configuration argument
29083 *
29084 * @Param {object} buttons 2d array of grouped button config objects
29085 * @Return {boolean}
29086 */
29087proto.hasButtons = function(buttons) {
29088 var currentButtons = this.buttons;
29089
29090 if(!currentButtons) return false;
29091
29092 if(buttons.length !== currentButtons.length) return false;
29093
29094 for(var i = 0; i < buttons.length; ++i) {
29095 if(buttons[i].length !== currentButtons[i].length) return false;
29096 for(var j = 0; j < buttons[i].length; j++) {
29097 if(buttons[i][j].name !== currentButtons[i][j].name) return false;
29098 }
29099 }
29100
29101 return true;
29102};
29103
29104/**
29105 * @return {HTMLDivElement} The logo image wrapped in a group
29106 */
29107proto.getLogo = function() {
29108 var group = this.createGroup();
29109 var a = document.createElement('a');
29110
29111 a.href = 'https://plotly.com/';
29112 a.target = '_blank';
29113 a.setAttribute('data-title', Lib._(this.graphInfo, 'Produced with Plotly'));
29114 a.className = 'modebar-btn plotlyjsicon modebar-btn--logo';
29115
29116 a.appendChild(this.createIcon(Icons.newplotlylogo));
29117
29118 group.appendChild(a);
29119 return group;
29120};
29121
29122proto.removeAllButtons = function() {
29123 while(this.element.firstChild) {
29124 this.element.removeChild(this.element.firstChild);
29125 }
29126
29127 this.hasLogo = false;
29128};
29129
29130proto.destroy = function() {
29131 Lib.removeElement(this.container.querySelector('.modebar'));
29132 Lib.deleteRelatedStyleRule(this._uid);
29133};
29134
29135function createModeBar(gd, buttons) {
29136 var fullLayout = gd._fullLayout;
29137
29138 var modeBar = new ModeBar({
29139 graphInfo: gd,
29140 container: fullLayout._modebardiv.node(),
29141 buttons: buttons
29142 });
29143
29144 if(fullLayout._privateplot) {
29145 d3.select(modeBar.element).append('span')
29146 .classed('badge-private float--left', true)
29147 .text('PRIVATE');
29148 }
29149
29150 return modeBar;
29151}
29152
29153module.exports = createModeBar;
29154
29155},{"../../fonts/ploticon":162,"../../lib":178,"d3":16,"fast-isnumeric":18}],115:[function(_dereq_,module,exports){
29156/**
29157* Copyright 2012-2020, Plotly, Inc.
29158* All rights reserved.
29159*
29160* This source code is licensed under the MIT license found in the
29161* LICENSE file in the root directory of this source tree.
29162*/
29163
29164'use strict';
29165
29166var fontAttrs = _dereq_('../../plots/font_attributes');
29167var colorAttrs = _dereq_('../color/attributes');
29168var templatedArray = _dereq_('../../plot_api/plot_template').templatedArray;
29169
29170var buttonAttrs = templatedArray('button', {
29171 visible: {
29172 valType: 'boolean',
29173
29174 dflt: true,
29175 editType: 'plot',
29176
29177 },
29178 step: {
29179 valType: 'enumerated',
29180
29181 values: ['month', 'year', 'day', 'hour', 'minute', 'second', 'all'],
29182 dflt: 'month',
29183 editType: 'plot',
29184
29185 },
29186 stepmode: {
29187 valType: 'enumerated',
29188
29189 values: ['backward', 'todate'],
29190 dflt: 'backward',
29191 editType: 'plot',
29192
29193 },
29194 count: {
29195 valType: 'number',
29196
29197 min: 0,
29198 dflt: 1,
29199 editType: 'plot',
29200
29201 },
29202 label: {
29203 valType: 'string',
29204
29205 editType: 'plot',
29206
29207 },
29208 editType: 'plot',
29209
29210});
29211
29212module.exports = {
29213 visible: {
29214 valType: 'boolean',
29215
29216 editType: 'plot',
29217
29218 },
29219
29220 buttons: buttonAttrs,
29221
29222 x: {
29223 valType: 'number',
29224 min: -2,
29225 max: 3,
29226
29227 editType: 'plot',
29228
29229 },
29230 xanchor: {
29231 valType: 'enumerated',
29232 values: ['auto', 'left', 'center', 'right'],
29233 dflt: 'left',
29234
29235 editType: 'plot',
29236
29237 },
29238 y: {
29239 valType: 'number',
29240 min: -2,
29241 max: 3,
29242
29243 editType: 'plot',
29244
29245 },
29246 yanchor: {
29247 valType: 'enumerated',
29248 values: ['auto', 'top', 'middle', 'bottom'],
29249 dflt: 'bottom',
29250
29251 editType: 'plot',
29252
29253 },
29254
29255 font: fontAttrs({
29256 editType: 'plot',
29257
29258 }),
29259
29260 bgcolor: {
29261 valType: 'color',
29262 dflt: colorAttrs.lightLine,
29263
29264 editType: 'plot',
29265
29266 },
29267 activecolor: {
29268 valType: 'color',
29269
29270 editType: 'plot',
29271
29272 },
29273 bordercolor: {
29274 valType: 'color',
29275 dflt: colorAttrs.defaultLine,
29276
29277 editType: 'plot',
29278
29279 },
29280 borderwidth: {
29281 valType: 'number',
29282 min: 0,
29283 dflt: 0,
29284
29285 editType: 'plot',
29286
29287 },
29288 editType: 'plot'
29289};
29290
29291},{"../../plot_api/plot_template":212,"../../plots/font_attributes":250,"../color/attributes":51}],116:[function(_dereq_,module,exports){
29292/**
29293* Copyright 2012-2020, Plotly, Inc.
29294* All rights reserved.
29295*
29296* This source code is licensed under the MIT license found in the
29297* LICENSE file in the root directory of this source tree.
29298*/
29299
29300'use strict';
29301
29302
29303module.exports = {
29304
29305 // 'y' position pad above counter axis domain
29306 yPad: 0.02,
29307
29308 // minimum button width (regardless of text size)
29309 minButtonWidth: 30,
29310
29311 // buttons rect radii
29312 rx: 3,
29313 ry: 3,
29314
29315 // light fraction used to compute the 'activecolor' default
29316 lightAmount: 25,
29317 darkAmount: 10
29318};
29319
29320},{}],117:[function(_dereq_,module,exports){
29321/**
29322* Copyright 2012-2020, Plotly, Inc.
29323* All rights reserved.
29324*
29325* This source code is licensed under the MIT license found in the
29326* LICENSE file in the root directory of this source tree.
29327*/
29328
29329'use strict';
29330
29331var Lib = _dereq_('../../lib');
29332var Color = _dereq_('../color');
29333var Template = _dereq_('../../plot_api/plot_template');
29334var handleArrayContainerDefaults = _dereq_('../../plots/array_container_defaults');
29335
29336var attributes = _dereq_('./attributes');
29337var constants = _dereq_('./constants');
29338
29339
29340module.exports = function handleDefaults(containerIn, containerOut, layout, counterAxes, calendar) {
29341 var selectorIn = containerIn.rangeselector || {};
29342 var selectorOut = Template.newContainer(containerOut, 'rangeselector');
29343
29344 function coerce(attr, dflt) {
29345 return Lib.coerce(selectorIn, selectorOut, attributes, attr, dflt);
29346 }
29347
29348 var buttons = handleArrayContainerDefaults(selectorIn, selectorOut, {
29349 name: 'buttons',
29350 handleItemDefaults: buttonDefaults,
29351 calendar: calendar
29352 });
29353
29354 var visible = coerce('visible', buttons.length > 0);
29355 if(visible) {
29356 var posDflt = getPosDflt(containerOut, layout, counterAxes);
29357 coerce('x', posDflt[0]);
29358 coerce('y', posDflt[1]);
29359 Lib.noneOrAll(containerIn, containerOut, ['x', 'y']);
29360
29361 coerce('xanchor');
29362 coerce('yanchor');
29363
29364 Lib.coerceFont(coerce, 'font', layout.font);
29365
29366 var bgColor = coerce('bgcolor');
29367 coerce('activecolor', Color.contrast(bgColor, constants.lightAmount, constants.darkAmount));
29368 coerce('bordercolor');
29369 coerce('borderwidth');
29370 }
29371};
29372
29373function buttonDefaults(buttonIn, buttonOut, selectorOut, opts) {
29374 var calendar = opts.calendar;
29375
29376 function coerce(attr, dflt) {
29377 return Lib.coerce(buttonIn, buttonOut, attributes.buttons, attr, dflt);
29378 }
29379
29380 var visible = coerce('visible');
29381
29382 if(visible) {
29383 var step = coerce('step');
29384 if(step !== 'all') {
29385 if(calendar && calendar !== 'gregorian' && (step === 'month' || step === 'year')) {
29386 buttonOut.stepmode = 'backward';
29387 } else {
29388 coerce('stepmode');
29389 }
29390
29391 coerce('count');
29392 }
29393
29394 coerce('label');
29395 }
29396}
29397
29398function getPosDflt(containerOut, layout, counterAxes) {
29399 var anchoredList = counterAxes.filter(function(ax) {
29400 return layout[ax].anchor === containerOut._id;
29401 });
29402
29403 var posY = 0;
29404 for(var i = 0; i < anchoredList.length; i++) {
29405 var domain = layout[anchoredList[i]].domain;
29406 if(domain) posY = Math.max(domain[1], posY);
29407 }
29408
29409 return [containerOut.domain[0], posY + constants.yPad];
29410}
29411
29412},{"../../lib":178,"../../plot_api/plot_template":212,"../../plots/array_container_defaults":218,"../color":52,"./attributes":115,"./constants":116}],118:[function(_dereq_,module,exports){
29413/**
29414* Copyright 2012-2020, Plotly, Inc.
29415* All rights reserved.
29416*
29417* This source code is licensed under the MIT license found in the
29418* LICENSE file in the root directory of this source tree.
29419*/
29420
29421'use strict';
29422
29423var d3 = _dereq_('d3');
29424
29425var Registry = _dereq_('../../registry');
29426var Plots = _dereq_('../../plots/plots');
29427var Color = _dereq_('../color');
29428var Drawing = _dereq_('../drawing');
29429var Lib = _dereq_('../../lib');
29430var svgTextUtils = _dereq_('../../lib/svg_text_utils');
29431var axisIds = _dereq_('../../plots/cartesian/axis_ids');
29432
29433var alignmentConstants = _dereq_('../../constants/alignment');
29434var LINE_SPACING = alignmentConstants.LINE_SPACING;
29435var FROM_TL = alignmentConstants.FROM_TL;
29436var FROM_BR = alignmentConstants.FROM_BR;
29437
29438var constants = _dereq_('./constants');
29439var getUpdateObject = _dereq_('./get_update_object');
29440
29441module.exports = function draw(gd) {
29442 var fullLayout = gd._fullLayout;
29443
29444 var selectors = fullLayout._infolayer.selectAll('.rangeselector')
29445 .data(makeSelectorData(gd), selectorKeyFunc);
29446
29447 selectors.enter().append('g')
29448 .classed('rangeselector', true);
29449
29450 selectors.exit().remove();
29451
29452 selectors.style({
29453 cursor: 'pointer',
29454 'pointer-events': 'all'
29455 });
29456
29457 selectors.each(function(d) {
29458 var selector = d3.select(this);
29459 var axisLayout = d;
29460 var selectorLayout = axisLayout.rangeselector;
29461
29462 var buttons = selector.selectAll('g.button')
29463 .data(Lib.filterVisible(selectorLayout.buttons));
29464
29465 buttons.enter().append('g')
29466 .classed('button', true);
29467
29468 buttons.exit().remove();
29469
29470 buttons.each(function(d) {
29471 var button = d3.select(this);
29472 var update = getUpdateObject(axisLayout, d);
29473
29474 d._isActive = isActive(axisLayout, d, update);
29475
29476 button.call(drawButtonRect, selectorLayout, d);
29477 button.call(drawButtonText, selectorLayout, d, gd);
29478
29479 button.on('click', function() {
29480 if(gd._dragged) return;
29481
29482 Registry.call('_guiRelayout', gd, update);
29483 });
29484
29485 button.on('mouseover', function() {
29486 d._isHovered = true;
29487 button.call(drawButtonRect, selectorLayout, d);
29488 });
29489
29490 button.on('mouseout', function() {
29491 d._isHovered = false;
29492 button.call(drawButtonRect, selectorLayout, d);
29493 });
29494 });
29495
29496 reposition(gd, buttons, selectorLayout, axisLayout._name, selector);
29497 });
29498};
29499
29500function makeSelectorData(gd) {
29501 var axes = axisIds.list(gd, 'x', true);
29502 var data = [];
29503
29504 for(var i = 0; i < axes.length; i++) {
29505 var axis = axes[i];
29506
29507 if(axis.rangeselector && axis.rangeselector.visible) {
29508 data.push(axis);
29509 }
29510 }
29511
29512 return data;
29513}
29514
29515function selectorKeyFunc(d) {
29516 return d._id;
29517}
29518
29519function isActive(axisLayout, opts, update) {
29520 if(opts.step === 'all') {
29521 return axisLayout.autorange === true;
29522 } else {
29523 var keys = Object.keys(update);
29524
29525 return (
29526 axisLayout.range[0] === update[keys[0]] &&
29527 axisLayout.range[1] === update[keys[1]]
29528 );
29529 }
29530}
29531
29532function drawButtonRect(button, selectorLayout, d) {
29533 var rect = Lib.ensureSingle(button, 'rect', 'selector-rect', function(s) {
29534 s.attr('shape-rendering', 'crispEdges');
29535 });
29536
29537 rect.attr({
29538 'rx': constants.rx,
29539 'ry': constants.ry
29540 });
29541
29542 rect.call(Color.stroke, selectorLayout.bordercolor)
29543 .call(Color.fill, getFillColor(selectorLayout, d))
29544 .style('stroke-width', selectorLayout.borderwidth + 'px');
29545}
29546
29547function getFillColor(selectorLayout, d) {
29548 return (d._isActive || d._isHovered) ?
29549 selectorLayout.activecolor :
29550 selectorLayout.bgcolor;
29551}
29552
29553function drawButtonText(button, selectorLayout, d, gd) {
29554 function textLayout(s) {
29555 svgTextUtils.convertToTspans(s, gd);
29556 }
29557
29558 var text = Lib.ensureSingle(button, 'text', 'selector-text', function(s) {
29559 s.classed('user-select-none', true)
29560 .attr('text-anchor', 'middle');
29561 });
29562
29563 text.call(Drawing.font, selectorLayout.font)
29564 .text(getLabel(d, gd._fullLayout._meta))
29565 .call(textLayout);
29566}
29567
29568function getLabel(opts, _meta) {
29569 if(opts.label) {
29570 return _meta ?
29571 Lib.templateString(opts.label, _meta) :
29572 opts.label;
29573 }
29574
29575 if(opts.step === 'all') return 'all';
29576
29577 return opts.count + opts.step.charAt(0);
29578}
29579
29580function reposition(gd, buttons, opts, axName, selector) {
29581 var width = 0;
29582 var height = 0;
29583
29584 var borderWidth = opts.borderwidth;
29585
29586 buttons.each(function() {
29587 var button = d3.select(this);
29588 var text = button.select('.selector-text');
29589
29590 var tHeight = opts.font.size * LINE_SPACING;
29591 var hEff = Math.max(tHeight * svgTextUtils.lineCount(text), 16) + 3;
29592
29593 height = Math.max(height, hEff);
29594 });
29595
29596 buttons.each(function() {
29597 var button = d3.select(this);
29598 var rect = button.select('.selector-rect');
29599 var text = button.select('.selector-text');
29600
29601 var tWidth = text.node() && Drawing.bBox(text.node()).width;
29602 var tHeight = opts.font.size * LINE_SPACING;
29603 var tLines = svgTextUtils.lineCount(text);
29604
29605 var wEff = Math.max(tWidth + 10, constants.minButtonWidth);
29606
29607 // TODO add MathJax support
29608
29609 // TODO add buttongap attribute
29610
29611 button.attr('transform', 'translate(' +
29612 (borderWidth + width) + ',' + borderWidth +
29613 ')');
29614
29615 rect.attr({
29616 x: 0,
29617 y: 0,
29618 width: wEff,
29619 height: height
29620 });
29621
29622 svgTextUtils.positionText(text, wEff / 2,
29623 height / 2 - ((tLines - 1) * tHeight / 2) + 3);
29624
29625 width += wEff + 5;
29626 });
29627
29628 var graphSize = gd._fullLayout._size;
29629 var lx = graphSize.l + graphSize.w * opts.x;
29630 var ly = graphSize.t + graphSize.h * (1 - opts.y);
29631
29632 var xanchor = 'left';
29633 if(Lib.isRightAnchor(opts)) {
29634 lx -= width;
29635 xanchor = 'right';
29636 }
29637 if(Lib.isCenterAnchor(opts)) {
29638 lx -= width / 2;
29639 xanchor = 'center';
29640 }
29641
29642 var yanchor = 'top';
29643 if(Lib.isBottomAnchor(opts)) {
29644 ly -= height;
29645 yanchor = 'bottom';
29646 }
29647 if(Lib.isMiddleAnchor(opts)) {
29648 ly -= height / 2;
29649 yanchor = 'middle';
29650 }
29651
29652 width = Math.ceil(width);
29653 height = Math.ceil(height);
29654 lx = Math.round(lx);
29655 ly = Math.round(ly);
29656
29657 Plots.autoMargin(gd, axName + '-range-selector', {
29658 x: opts.x,
29659 y: opts.y,
29660 l: width * FROM_TL[xanchor],
29661 r: width * FROM_BR[xanchor],
29662 b: height * FROM_BR[yanchor],
29663 t: height * FROM_TL[yanchor]
29664 });
29665
29666 selector.attr('transform', 'translate(' + lx + ',' + ly + ')');
29667}
29668
29669},{"../../constants/alignment":154,"../../lib":178,"../../lib/svg_text_utils":199,"../../plots/cartesian/axis_ids":225,"../../plots/plots":256,"../../registry":269,"../color":52,"../drawing":74,"./constants":116,"./get_update_object":119,"d3":16}],119:[function(_dereq_,module,exports){
29670/**
29671* Copyright 2012-2020, Plotly, Inc.
29672* All rights reserved.
29673*
29674* This source code is licensed under the MIT license found in the
29675* LICENSE file in the root directory of this source tree.
29676*/
29677
29678
29679'use strict';
29680
29681var d3 = _dereq_('d3');
29682
29683module.exports = function getUpdateObject(axisLayout, buttonLayout) {
29684 var axName = axisLayout._name;
29685 var update = {};
29686
29687 if(buttonLayout.step === 'all') {
29688 update[axName + '.autorange'] = true;
29689 } else {
29690 var xrange = getXRange(axisLayout, buttonLayout);
29691
29692 update[axName + '.range[0]'] = xrange[0];
29693 update[axName + '.range[1]'] = xrange[1];
29694 }
29695
29696 return update;
29697};
29698
29699function getXRange(axisLayout, buttonLayout) {
29700 var currentRange = axisLayout.range;
29701 var base = new Date(axisLayout.r2l(currentRange[1]));
29702 var step = buttonLayout.step;
29703 var count = buttonLayout.count;
29704 var range0;
29705
29706 switch(buttonLayout.stepmode) {
29707 case 'backward':
29708 range0 = axisLayout.l2r(+d3.time[step].utc.offset(base, -count));
29709 break;
29710
29711 case 'todate':
29712 var base2 = d3.time[step].utc.offset(base, -count);
29713
29714 range0 = axisLayout.l2r(+d3.time[step].utc.ceil(base2));
29715 break;
29716 }
29717
29718 var range1 = currentRange[1];
29719
29720 return [range0, range1];
29721}
29722
29723},{"d3":16}],120:[function(_dereq_,module,exports){
29724/**
29725* Copyright 2012-2020, Plotly, Inc.
29726* All rights reserved.
29727*
29728* This source code is licensed under the MIT license found in the
29729* LICENSE file in the root directory of this source tree.
29730*/
29731
29732'use strict';
29733
29734module.exports = {
29735 moduleType: 'component',
29736 name: 'rangeselector',
29737
29738 schema: {
29739 subplots: {
29740 xaxis: {rangeselector: _dereq_('./attributes')}
29741 }
29742 },
29743
29744 layoutAttributes: _dereq_('./attributes'),
29745 handleDefaults: _dereq_('./defaults'),
29746
29747 draw: _dereq_('./draw')
29748};
29749
29750},{"./attributes":115,"./defaults":117,"./draw":118}],121:[function(_dereq_,module,exports){
29751/**
29752* Copyright 2012-2020, Plotly, Inc.
29753* All rights reserved.
29754*
29755* This source code is licensed under the MIT license found in the
29756* LICENSE file in the root directory of this source tree.
29757*/
29758
29759'use strict';
29760
29761var colorAttributes = _dereq_('../color/attributes');
29762
29763module.exports = {
29764 bgcolor: {
29765 valType: 'color',
29766 dflt: colorAttributes.background,
29767
29768 editType: 'plot',
29769
29770 },
29771 bordercolor: {
29772 valType: 'color',
29773 dflt: colorAttributes.defaultLine,
29774
29775 editType: 'plot',
29776
29777 },
29778 borderwidth: {
29779 valType: 'integer',
29780 dflt: 0,
29781 min: 0,
29782
29783 editType: 'plot',
29784
29785 },
29786 autorange: {
29787 valType: 'boolean',
29788 dflt: true,
29789
29790 editType: 'calc',
29791 impliedEdits: {'range[0]': undefined, 'range[1]': undefined},
29792
29793 },
29794 range: {
29795 valType: 'info_array',
29796
29797 items: [
29798 {valType: 'any', editType: 'calc', impliedEdits: {'^autorange': false}},
29799 {valType: 'any', editType: 'calc', impliedEdits: {'^autorange': false}}
29800 ],
29801 editType: 'calc',
29802 impliedEdits: {'autorange': false},
29803
29804 },
29805 thickness: {
29806 valType: 'number',
29807 dflt: 0.15,
29808 min: 0,
29809 max: 1,
29810
29811 editType: 'plot',
29812
29813 },
29814 visible: {
29815 valType: 'boolean',
29816 dflt: true,
29817
29818 editType: 'calc',
29819
29820 },
29821 editType: 'calc'
29822};
29823
29824},{"../color/attributes":51}],122:[function(_dereq_,module,exports){
29825/**
29826* Copyright 2012-2020, Plotly, Inc.
29827* All rights reserved.
29828*
29829* This source code is licensed under the MIT license found in the
29830* LICENSE file in the root directory of this source tree.
29831*/
29832
29833'use strict';
29834
29835var listAxes = _dereq_('../../plots/cartesian/axis_ids').list;
29836var getAutoRange = _dereq_('../../plots/cartesian/autorange').getAutoRange;
29837var constants = _dereq_('./constants');
29838
29839module.exports = function calcAutorange(gd) {
29840 var axes = listAxes(gd, 'x', true);
29841
29842 // Compute new slider range using axis autorange if necessary.
29843 //
29844 // Copy back range to input range slider container to skip
29845 // this step in subsequent draw calls.
29846
29847 for(var i = 0; i < axes.length; i++) {
29848 var ax = axes[i];
29849 var opts = ax[constants.name];
29850
29851 if(opts && opts.visible && opts.autorange) {
29852 opts._input.autorange = true;
29853 opts._input.range = opts.range = getAutoRange(gd, ax);
29854 }
29855 }
29856};
29857
29858},{"../../plots/cartesian/autorange":221,"../../plots/cartesian/axis_ids":225,"./constants":123}],123:[function(_dereq_,module,exports){
29859/**
29860* Copyright 2012-2020, Plotly, Inc.
29861* All rights reserved.
29862*
29863* This source code is licensed under the MIT license found in the
29864* LICENSE file in the root directory of this source tree.
29865*/
29866
29867'use strict';
29868
29869module.exports = {
29870
29871 // attribute container name
29872 name: 'rangeslider',
29873
29874 // class names
29875
29876 containerClassName: 'rangeslider-container',
29877 bgClassName: 'rangeslider-bg',
29878 rangePlotClassName: 'rangeslider-rangeplot',
29879
29880 maskMinClassName: 'rangeslider-mask-min',
29881 maskMaxClassName: 'rangeslider-mask-max',
29882 slideBoxClassName: 'rangeslider-slidebox',
29883
29884 grabberMinClassName: 'rangeslider-grabber-min',
29885 grabAreaMinClassName: 'rangeslider-grabarea-min',
29886 handleMinClassName: 'rangeslider-handle-min',
29887
29888 grabberMaxClassName: 'rangeslider-grabber-max',
29889 grabAreaMaxClassName: 'rangeslider-grabarea-max',
29890 handleMaxClassName: 'rangeslider-handle-max',
29891
29892 maskMinOppAxisClassName: 'rangeslider-mask-min-opp-axis',
29893 maskMaxOppAxisClassName: 'rangeslider-mask-max-opp-axis',
29894
29895 // style constants
29896
29897 maskColor: 'rgba(0,0,0,0.4)',
29898 maskOppAxisColor: 'rgba(0,0,0,0.2)',
29899
29900 slideBoxFill: 'transparent',
29901 slideBoxCursor: 'ew-resize',
29902
29903 grabAreaFill: 'transparent',
29904 grabAreaCursor: 'col-resize',
29905 grabAreaWidth: 10,
29906
29907 handleWidth: 4,
29908 handleRadius: 1,
29909 handleStrokeWidth: 1,
29910
29911 extraPad: 15
29912};
29913
29914},{}],124:[function(_dereq_,module,exports){
29915/**
29916* Copyright 2012-2020, Plotly, Inc.
29917* All rights reserved.
29918*
29919* This source code is licensed under the MIT license found in the
29920* LICENSE file in the root directory of this source tree.
29921*/
29922
29923'use strict';
29924
29925var Lib = _dereq_('../../lib');
29926var Template = _dereq_('../../plot_api/plot_template');
29927var axisIds = _dereq_('../../plots/cartesian/axis_ids');
29928
29929var attributes = _dereq_('./attributes');
29930var oppAxisAttrs = _dereq_('./oppaxis_attributes');
29931
29932module.exports = function handleDefaults(layoutIn, layoutOut, axName) {
29933 var axIn = layoutIn[axName];
29934 var axOut = layoutOut[axName];
29935
29936 if(!(axIn.rangeslider || layoutOut._requestRangeslider[axOut._id])) return;
29937
29938 // not super proud of this (maybe store _ in axis object instead
29939 if(!Lib.isPlainObject(axIn.rangeslider)) {
29940 axIn.rangeslider = {};
29941 }
29942
29943 var containerIn = axIn.rangeslider;
29944 var containerOut = Template.newContainer(axOut, 'rangeslider');
29945
29946 function coerce(attr, dflt) {
29947 return Lib.coerce(containerIn, containerOut, attributes, attr, dflt);
29948 }
29949
29950 var rangeContainerIn, rangeContainerOut;
29951 function coerceRange(attr, dflt) {
29952 return Lib.coerce(rangeContainerIn, rangeContainerOut, oppAxisAttrs, attr, dflt);
29953 }
29954
29955 var visible = coerce('visible');
29956 if(!visible) return;
29957
29958 coerce('bgcolor', layoutOut.plot_bgcolor);
29959 coerce('bordercolor');
29960 coerce('borderwidth');
29961 coerce('thickness');
29962
29963 coerce('autorange', !axOut.isValidRange(containerIn.range));
29964 coerce('range');
29965
29966 var subplots = layoutOut._subplots;
29967 if(subplots) {
29968 var yIds = subplots.cartesian
29969 .filter(function(subplotId) {
29970 return subplotId.substr(0, subplotId.indexOf('y')) === axisIds.name2id(axName);
29971 })
29972 .map(function(subplotId) {
29973 return subplotId.substr(subplotId.indexOf('y'), subplotId.length);
29974 });
29975 var yNames = Lib.simpleMap(yIds, axisIds.id2name);
29976 for(var i = 0; i < yNames.length; i++) {
29977 var yName = yNames[i];
29978
29979 rangeContainerIn = containerIn[yName] || {};
29980 rangeContainerOut = Template.newContainer(containerOut, yName, 'yaxis');
29981
29982 var yAxOut = layoutOut[yName];
29983
29984 var rangemodeDflt;
29985 if(rangeContainerIn.range && yAxOut.isValidRange(rangeContainerIn.range)) {
29986 rangemodeDflt = 'fixed';
29987 }
29988
29989 var rangeMode = coerceRange('rangemode', rangemodeDflt);
29990 if(rangeMode !== 'match') {
29991 coerceRange('range', yAxOut.range.slice());
29992 }
29993 }
29994 }
29995
29996 // to map back range slider (auto) range
29997 containerOut._input = containerIn;
29998};
29999
30000},{"../../lib":178,"../../plot_api/plot_template":212,"../../plots/cartesian/axis_ids":225,"./attributes":121,"./oppaxis_attributes":128}],125:[function(_dereq_,module,exports){
30001/**
30002* Copyright 2012-2020, Plotly, Inc.
30003* All rights reserved.
30004*
30005* This source code is licensed under the MIT license found in the
30006* LICENSE file in the root directory of this source tree.
30007*/
30008
30009'use strict';
30010
30011var d3 = _dereq_('d3');
30012
30013var Registry = _dereq_('../../registry');
30014var Plots = _dereq_('../../plots/plots');
30015
30016var Lib = _dereq_('../../lib');
30017var Drawing = _dereq_('../drawing');
30018var Color = _dereq_('../color');
30019var Titles = _dereq_('../titles');
30020
30021var Cartesian = _dereq_('../../plots/cartesian');
30022var axisIDs = _dereq_('../../plots/cartesian/axis_ids');
30023
30024var dragElement = _dereq_('../dragelement');
30025var setCursor = _dereq_('../../lib/setcursor');
30026
30027var constants = _dereq_('./constants');
30028
30029module.exports = function(gd) {
30030 var fullLayout = gd._fullLayout;
30031 var rangeSliderData = fullLayout._rangeSliderData;
30032 for(var i = 0; i < rangeSliderData.length; i++) {
30033 var opts = rangeSliderData[i][constants.name];
30034 // fullLayout._uid may not exist when we call makeData
30035 opts._clipId = opts._id + '-' + fullLayout._uid;
30036 }
30037
30038 /*
30039 * <g container />
30040 * <rect bg />
30041 * < .... range plot />
30042 * <rect mask-min />
30043 * <rect mask-max />
30044 * <rect slidebox />
30045 * <g grabber-min />
30046 * <rect handle-min />
30047 * <rect grabare-min />
30048 * <g grabber-max />
30049 * <rect handle-max />
30050 * <rect grabare-max />
30051 *
30052 * ...
30053 */
30054
30055 function keyFunction(axisOpts) {
30056 return axisOpts._name;
30057 }
30058
30059 var rangeSliders = fullLayout._infolayer
30060 .selectAll('g.' + constants.containerClassName)
30061 .data(rangeSliderData, keyFunction);
30062
30063 // remove exiting sliders and their corresponding clip paths
30064 rangeSliders.exit().each(function(axisOpts) {
30065 var opts = axisOpts[constants.name];
30066 fullLayout._topdefs.select('#' + opts._clipId).remove();
30067 }).remove();
30068
30069 // return early if no range slider is visible
30070 if(rangeSliderData.length === 0) return;
30071
30072 rangeSliders.enter().append('g')
30073 .classed(constants.containerClassName, true)
30074 .attr('pointer-events', 'all');
30075
30076 // for all present range sliders
30077 rangeSliders.each(function(axisOpts) {
30078 var rangeSlider = d3.select(this);
30079 var opts = axisOpts[constants.name];
30080 var oppAxisOpts = fullLayout[axisIDs.id2name(axisOpts.anchor)];
30081 var oppAxisRangeOpts = opts[axisIDs.id2name(axisOpts.anchor)];
30082
30083 // update range
30084 // Expand slider range to the axis range
30085 if(opts.range) {
30086 var rng = Lib.simpleMap(opts.range, axisOpts.r2l);
30087 var axRng = Lib.simpleMap(axisOpts.range, axisOpts.r2l);
30088 var newRng;
30089
30090 if(axRng[0] < axRng[1]) {
30091 newRng = [
30092 Math.min(rng[0], axRng[0]),
30093 Math.max(rng[1], axRng[1])
30094 ];
30095 } else {
30096 newRng = [
30097 Math.max(rng[0], axRng[0]),
30098 Math.min(rng[1], axRng[1])
30099 ];
30100 }
30101
30102 opts.range = opts._input.range = Lib.simpleMap(newRng, axisOpts.l2r);
30103 }
30104
30105 axisOpts.cleanRange('rangeslider.range');
30106
30107 // update range slider dimensions
30108
30109 var gs = fullLayout._size;
30110 var domain = axisOpts.domain;
30111
30112 opts._width = gs.w * (domain[1] - domain[0]);
30113
30114 var x = Math.round(gs.l + (gs.w * domain[0]));
30115
30116 var y = Math.round(
30117 gs.t + gs.h * (1 - axisOpts._counterDomainMin) +
30118 (axisOpts.side === 'bottom' ? axisOpts._depth : 0) +
30119 opts._offsetShift + constants.extraPad
30120 );
30121
30122 rangeSlider.attr('transform', 'translate(' + x + ',' + y + ')');
30123
30124 // update data <--> pixel coordinate conversion methods
30125
30126 opts._rl = Lib.simpleMap(opts.range, axisOpts.r2l);
30127 var rl0 = opts._rl[0];
30128 var rl1 = opts._rl[1];
30129 var drl = rl1 - rl0;
30130
30131 opts.p2d = function(v) {
30132 return (v / opts._width) * drl + rl0;
30133 };
30134
30135 opts.d2p = function(v) {
30136 return (v - rl0) / drl * opts._width;
30137 };
30138
30139 if(axisOpts.rangebreaks) {
30140 var rsBreaks = axisOpts.locateBreaks(rl0, rl1);
30141
30142 if(rsBreaks.length) {
30143 var j, brk;
30144
30145 var lBreaks = 0;
30146 for(j = 0; j < rsBreaks.length; j++) {
30147 brk = rsBreaks[j];
30148 lBreaks += (brk.max - brk.min);
30149 }
30150
30151 // TODO fix for reversed-range axes !!!
30152
30153 // compute slope and piecewise offsets
30154 var m2 = opts._width / (rl1 - rl0 - lBreaks);
30155 var _B = [-m2 * rl0];
30156 for(j = 0; j < rsBreaks.length; j++) {
30157 brk = rsBreaks[j];
30158 _B.push(_B[_B.length - 1] - m2 * (brk.max - brk.min));
30159 }
30160
30161 opts.d2p = function(v) {
30162 var b = _B[0];
30163 for(var j = 0; j < rsBreaks.length; j++) {
30164 var brk = rsBreaks[j];
30165 if(v >= brk.max) b = _B[j + 1];
30166 else if(v < brk.min) break;
30167 }
30168 return b + m2 * v;
30169 };
30170
30171 // fill pixel (i.e. 'p') min/max here,
30172 // to not have to loop through the _rangebreaks twice during `p2d`
30173 for(j = 0; j < rsBreaks.length; j++) {
30174 brk = rsBreaks[j];
30175 brk.pmin = opts.d2p(brk.min);
30176 brk.pmax = opts.d2p(brk.max);
30177 }
30178
30179 opts.p2d = function(v) {
30180 var b = _B[0];
30181 for(var j = 0; j < rsBreaks.length; j++) {
30182 var brk = rsBreaks[j];
30183 if(v >= brk.pmax) b = _B[j + 1];
30184 else if(v < brk.pmin) break;
30185 }
30186 return (v - b) / m2;
30187 };
30188 }
30189 }
30190
30191 if(oppAxisRangeOpts.rangemode !== 'match') {
30192 var range0OppAxis = oppAxisOpts.r2l(oppAxisRangeOpts.range[0]);
30193 var range1OppAxis = oppAxisOpts.r2l(oppAxisRangeOpts.range[1]);
30194 var distOppAxis = range1OppAxis - range0OppAxis;
30195
30196 opts.d2pOppAxis = function(v) {
30197 return (v - range0OppAxis) / distOppAxis * opts._height;
30198 };
30199 }
30200
30201 // update inner nodes
30202
30203 rangeSlider
30204 .call(drawBg, gd, axisOpts, opts)
30205 .call(addClipPath, gd, axisOpts, opts)
30206 .call(drawRangePlot, gd, axisOpts, opts)
30207 .call(drawMasks, gd, axisOpts, opts, oppAxisRangeOpts)
30208 .call(drawSlideBox, gd, axisOpts, opts)
30209 .call(drawGrabbers, gd, axisOpts, opts);
30210
30211 // setup drag element
30212 setupDragElement(rangeSlider, gd, axisOpts, opts);
30213
30214 // update current range
30215 setPixelRange(rangeSlider, gd, axisOpts, opts, oppAxisOpts, oppAxisRangeOpts);
30216
30217 // title goes next to range slider instead of tick labels, so
30218 // just take it over and draw it from here
30219 if(axisOpts.side === 'bottom') {
30220 Titles.draw(gd, axisOpts._id + 'title', {
30221 propContainer: axisOpts,
30222 propName: axisOpts._name + '.title',
30223 placeholder: fullLayout._dfltTitle.x,
30224 attributes: {
30225 x: axisOpts._offset + axisOpts._length / 2,
30226 y: y + opts._height + opts._offsetShift + 10 + 1.5 * axisOpts.title.font.size,
30227 'text-anchor': 'middle'
30228 }
30229 });
30230 }
30231 });
30232};
30233
30234function setupDragElement(rangeSlider, gd, axisOpts, opts) {
30235 var slideBox = rangeSlider.select('rect.' + constants.slideBoxClassName).node();
30236 var grabAreaMin = rangeSlider.select('rect.' + constants.grabAreaMinClassName).node();
30237 var grabAreaMax = rangeSlider.select('rect.' + constants.grabAreaMaxClassName).node();
30238
30239 rangeSlider.on('mousedown', function() {
30240 var event = d3.event;
30241 var target = event.target;
30242 var startX = event.clientX;
30243 var offsetX = startX - rangeSlider.node().getBoundingClientRect().left;
30244 var minVal = opts.d2p(axisOpts._rl[0]);
30245 var maxVal = opts.d2p(axisOpts._rl[1]);
30246
30247 var dragCover = dragElement.coverSlip();
30248
30249 dragCover.addEventListener('mousemove', mouseMove);
30250 dragCover.addEventListener('mouseup', mouseUp);
30251
30252 function mouseMove(e) {
30253 var delta = +e.clientX - startX;
30254 var pixelMin, pixelMax, cursor;
30255
30256 switch(target) {
30257 case slideBox:
30258 cursor = 'ew-resize';
30259 pixelMin = minVal + delta;
30260 pixelMax = maxVal + delta;
30261 break;
30262
30263 case grabAreaMin:
30264 cursor = 'col-resize';
30265 pixelMin = minVal + delta;
30266 pixelMax = maxVal;
30267 break;
30268
30269 case grabAreaMax:
30270 cursor = 'col-resize';
30271 pixelMin = minVal;
30272 pixelMax = maxVal + delta;
30273 break;
30274
30275 default:
30276 cursor = 'ew-resize';
30277 pixelMin = offsetX;
30278 pixelMax = offsetX + delta;
30279 break;
30280 }
30281
30282 if(pixelMax < pixelMin) {
30283 var tmp = pixelMax;
30284 pixelMax = pixelMin;
30285 pixelMin = tmp;
30286 }
30287
30288 opts._pixelMin = pixelMin;
30289 opts._pixelMax = pixelMax;
30290
30291 setCursor(d3.select(dragCover), cursor);
30292 setDataRange(rangeSlider, gd, axisOpts, opts);
30293 }
30294
30295 function mouseUp() {
30296 dragCover.removeEventListener('mousemove', mouseMove);
30297 dragCover.removeEventListener('mouseup', mouseUp);
30298 Lib.removeElement(dragCover);
30299 }
30300 });
30301}
30302
30303function setDataRange(rangeSlider, gd, axisOpts, opts) {
30304 function clamp(v) {
30305 return axisOpts.l2r(Lib.constrain(v, opts._rl[0], opts._rl[1]));
30306 }
30307
30308 var dataMin = clamp(opts.p2d(opts._pixelMin));
30309 var dataMax = clamp(opts.p2d(opts._pixelMax));
30310
30311 window.requestAnimationFrame(function() {
30312 Registry.call('_guiRelayout', gd, axisOpts._name + '.range', [dataMin, dataMax]);
30313 });
30314}
30315
30316function setPixelRange(rangeSlider, gd, axisOpts, opts, oppAxisOpts, oppAxisRangeOpts) {
30317 var hw2 = constants.handleWidth / 2;
30318
30319 function clamp(v) {
30320 return Lib.constrain(v, 0, opts._width);
30321 }
30322
30323 function clampOppAxis(v) {
30324 return Lib.constrain(v, 0, opts._height);
30325 }
30326
30327 function clampHandle(v) {
30328 return Lib.constrain(v, -hw2, opts._width + hw2);
30329 }
30330
30331 var pixelMin = clamp(opts.d2p(axisOpts._rl[0]));
30332 var pixelMax = clamp(opts.d2p(axisOpts._rl[1]));
30333
30334 rangeSlider.select('rect.' + constants.slideBoxClassName)
30335 .attr('x', pixelMin)
30336 .attr('width', pixelMax - pixelMin);
30337
30338 rangeSlider.select('rect.' + constants.maskMinClassName)
30339 .attr('width', pixelMin);
30340
30341 rangeSlider.select('rect.' + constants.maskMaxClassName)
30342 .attr('x', pixelMax)
30343 .attr('width', opts._width - pixelMax);
30344
30345 if(oppAxisRangeOpts.rangemode !== 'match') {
30346 var pixelMinOppAxis = opts._height - clampOppAxis(opts.d2pOppAxis(oppAxisOpts._rl[1]));
30347 var pixelMaxOppAxis = opts._height - clampOppAxis(opts.d2pOppAxis(oppAxisOpts._rl[0]));
30348
30349 rangeSlider.select('rect.' + constants.maskMinOppAxisClassName)
30350 .attr('x', pixelMin)
30351 .attr('height', pixelMinOppAxis)
30352 .attr('width', pixelMax - pixelMin);
30353
30354 rangeSlider.select('rect.' + constants.maskMaxOppAxisClassName)
30355 .attr('x', pixelMin)
30356 .attr('y', pixelMaxOppAxis)
30357 .attr('height', opts._height - pixelMaxOppAxis)
30358 .attr('width', pixelMax - pixelMin);
30359
30360 rangeSlider.select('rect.' + constants.slideBoxClassName)
30361 .attr('y', pixelMinOppAxis)
30362 .attr('height', pixelMaxOppAxis - pixelMinOppAxis);
30363 }
30364
30365 // add offset for crispier corners
30366 // https://github.com/plotly/plotly.js/pull/1409
30367 var offset = 0.5;
30368
30369 var xMin = Math.round(clampHandle(pixelMin - hw2)) - offset;
30370 var xMax = Math.round(clampHandle(pixelMax - hw2)) + offset;
30371
30372 rangeSlider.select('g.' + constants.grabberMinClassName)
30373 .attr('transform', 'translate(' + xMin + ',' + offset + ')');
30374
30375 rangeSlider.select('g.' + constants.grabberMaxClassName)
30376 .attr('transform', 'translate(' + xMax + ',' + offset + ')');
30377}
30378
30379function drawBg(rangeSlider, gd, axisOpts, opts) {
30380 var bg = Lib.ensureSingle(rangeSlider, 'rect', constants.bgClassName, function(s) {
30381 s.attr({
30382 x: 0,
30383 y: 0,
30384 'shape-rendering': 'crispEdges'
30385 });
30386 });
30387
30388 var borderCorrect = (opts.borderwidth % 2) === 0 ?
30389 opts.borderwidth :
30390 opts.borderwidth - 1;
30391
30392 var offsetShift = -opts._offsetShift;
30393 var lw = Drawing.crispRound(gd, opts.borderwidth);
30394
30395 bg.attr({
30396 width: opts._width + borderCorrect,
30397 height: opts._height + borderCorrect,
30398 transform: 'translate(' + offsetShift + ',' + offsetShift + ')',
30399 fill: opts.bgcolor,
30400 stroke: opts.bordercolor,
30401 'stroke-width': lw
30402 });
30403}
30404
30405function addClipPath(rangeSlider, gd, axisOpts, opts) {
30406 var fullLayout = gd._fullLayout;
30407
30408 var clipPath = Lib.ensureSingleById(fullLayout._topdefs, 'clipPath', opts._clipId, function(s) {
30409 s.append('rect').attr({ x: 0, y: 0 });
30410 });
30411
30412 clipPath.select('rect').attr({
30413 width: opts._width,
30414 height: opts._height
30415 });
30416}
30417
30418function drawRangePlot(rangeSlider, gd, axisOpts, opts) {
30419 var calcData = gd.calcdata;
30420
30421 var rangePlots = rangeSlider.selectAll('g.' + constants.rangePlotClassName)
30422 .data(axisOpts._subplotsWith, Lib.identity);
30423
30424 rangePlots.enter().append('g')
30425 .attr('class', function(id) { return constants.rangePlotClassName + ' ' + id; })
30426 .call(Drawing.setClipUrl, opts._clipId, gd);
30427
30428 rangePlots.order();
30429
30430 rangePlots.exit().remove();
30431
30432 var mainplotinfo;
30433
30434 rangePlots.each(function(id, i) {
30435 var plotgroup = d3.select(this);
30436 var isMainPlot = (i === 0);
30437
30438 var oppAxisOpts = axisIDs.getFromId(gd, id, 'y');
30439 var oppAxisName = oppAxisOpts._name;
30440 var oppAxisRangeOpts = opts[oppAxisName];
30441
30442 var mockFigure = {
30443 data: [],
30444 layout: {
30445 xaxis: {
30446 type: axisOpts.type,
30447 domain: [0, 1],
30448 range: opts.range.slice(),
30449 calendar: axisOpts.calendar
30450 },
30451 width: opts._width,
30452 height: opts._height,
30453 margin: { t: 0, b: 0, l: 0, r: 0 }
30454 },
30455 _context: gd._context
30456 };
30457
30458 if(axisOpts.rangebreaks) {
30459 mockFigure.layout.xaxis.rangebreaks = axisOpts.rangebreaks;
30460 }
30461
30462 mockFigure.layout[oppAxisName] = {
30463 type: oppAxisOpts.type,
30464 domain: [0, 1],
30465 range: oppAxisRangeOpts.rangemode !== 'match' ? oppAxisRangeOpts.range.slice() : oppAxisOpts.range.slice(),
30466 calendar: oppAxisOpts.calendar
30467 };
30468
30469 if(oppAxisOpts.rangebreaks) {
30470 mockFigure.layout[oppAxisName].rangebreaks = oppAxisOpts.rangebreaks;
30471 }
30472
30473 Plots.supplyDefaults(mockFigure);
30474
30475 var xa = mockFigure._fullLayout.xaxis;
30476 var ya = mockFigure._fullLayout[oppAxisName];
30477
30478 xa.clearCalc();
30479 xa.setScale();
30480 ya.clearCalc();
30481 ya.setScale();
30482
30483 var plotinfo = {
30484 id: id,
30485 plotgroup: plotgroup,
30486 xaxis: xa,
30487 yaxis: ya,
30488 isRangePlot: true
30489 };
30490
30491 if(isMainPlot) mainplotinfo = plotinfo;
30492 else {
30493 plotinfo.mainplot = 'xy';
30494 plotinfo.mainplotinfo = mainplotinfo;
30495 }
30496
30497 Cartesian.rangePlot(gd, plotinfo, filterRangePlotCalcData(calcData, id));
30498 });
30499}
30500
30501function filterRangePlotCalcData(calcData, subplotId) {
30502 var out = [];
30503
30504 for(var i = 0; i < calcData.length; i++) {
30505 var calcTrace = calcData[i];
30506 var trace = calcTrace[0].trace;
30507
30508 if(trace.xaxis + trace.yaxis === subplotId) {
30509 out.push(calcTrace);
30510 }
30511 }
30512
30513 return out;
30514}
30515
30516function drawMasks(rangeSlider, gd, axisOpts, opts, oppAxisRangeOpts) {
30517 var maskMin = Lib.ensureSingle(rangeSlider, 'rect', constants.maskMinClassName, function(s) {
30518 s.attr({
30519 x: 0,
30520 y: 0,
30521 'shape-rendering': 'crispEdges'
30522 });
30523 });
30524
30525 maskMin
30526 .attr('height', opts._height)
30527 .call(Color.fill, constants.maskColor);
30528
30529 var maskMax = Lib.ensureSingle(rangeSlider, 'rect', constants.maskMaxClassName, function(s) {
30530 s.attr({
30531 y: 0,
30532 'shape-rendering': 'crispEdges'
30533 });
30534 });
30535
30536 maskMax
30537 .attr('height', opts._height)
30538 .call(Color.fill, constants.maskColor);
30539
30540 // masks used for oppAxis zoom
30541 if(oppAxisRangeOpts.rangemode !== 'match') {
30542 var maskMinOppAxis = Lib.ensureSingle(rangeSlider, 'rect', constants.maskMinOppAxisClassName, function(s) {
30543 s.attr({
30544 y: 0,
30545 'shape-rendering': 'crispEdges'
30546 });
30547 });
30548
30549 maskMinOppAxis
30550 .attr('width', opts._width)
30551 .call(Color.fill, constants.maskOppAxisColor);
30552
30553 var maskMaxOppAxis = Lib.ensureSingle(rangeSlider, 'rect', constants.maskMaxOppAxisClassName, function(s) {
30554 s.attr({
30555 y: 0,
30556 'shape-rendering': 'crispEdges'
30557 });
30558 });
30559
30560 maskMaxOppAxis
30561 .attr('width', opts._width)
30562 .style('border-top', constants.maskOppBorder)
30563 .call(Color.fill, constants.maskOppAxisColor);
30564 }
30565}
30566
30567function drawSlideBox(rangeSlider, gd, axisOpts, opts) {
30568 if(gd._context.staticPlot) return;
30569
30570 var slideBox = Lib.ensureSingle(rangeSlider, 'rect', constants.slideBoxClassName, function(s) {
30571 s.attr({
30572 y: 0,
30573 cursor: constants.slideBoxCursor,
30574 'shape-rendering': 'crispEdges'
30575 });
30576 });
30577
30578 slideBox.attr({
30579 height: opts._height,
30580 fill: constants.slideBoxFill
30581 });
30582}
30583
30584function drawGrabbers(rangeSlider, gd, axisOpts, opts) {
30585 // <g grabber />
30586 var grabberMin = Lib.ensureSingle(rangeSlider, 'g', constants.grabberMinClassName);
30587 var grabberMax = Lib.ensureSingle(rangeSlider, 'g', constants.grabberMaxClassName);
30588
30589 // <g handle />
30590 var handleFixAttrs = {
30591 x: 0,
30592 width: constants.handleWidth,
30593 rx: constants.handleRadius,
30594 fill: Color.background,
30595 stroke: Color.defaultLine,
30596 'stroke-width': constants.handleStrokeWidth,
30597 'shape-rendering': 'crispEdges'
30598 };
30599 var handleDynamicAttrs = {
30600 y: Math.round(opts._height / 4),
30601 height: Math.round(opts._height / 2),
30602 };
30603 var handleMin = Lib.ensureSingle(grabberMin, 'rect', constants.handleMinClassName, function(s) {
30604 s.attr(handleFixAttrs);
30605 });
30606 handleMin.attr(handleDynamicAttrs);
30607
30608 var handleMax = Lib.ensureSingle(grabberMax, 'rect', constants.handleMaxClassName, function(s) {
30609 s.attr(handleFixAttrs);
30610 });
30611 handleMax.attr(handleDynamicAttrs);
30612
30613 // <g grabarea />
30614 if(gd._context.staticPlot) return;
30615
30616 var grabAreaFixAttrs = {
30617 width: constants.grabAreaWidth,
30618 x: 0,
30619 y: 0,
30620 fill: constants.grabAreaFill,
30621 cursor: constants.grabAreaCursor
30622 };
30623
30624 var grabAreaMin = Lib.ensureSingle(grabberMin, 'rect', constants.grabAreaMinClassName, function(s) {
30625 s.attr(grabAreaFixAttrs);
30626 });
30627 grabAreaMin.attr('height', opts._height);
30628
30629 var grabAreaMax = Lib.ensureSingle(grabberMax, 'rect', constants.grabAreaMaxClassName, function(s) {
30630 s.attr(grabAreaFixAttrs);
30631 });
30632 grabAreaMax.attr('height', opts._height);
30633}
30634
30635},{"../../lib":178,"../../lib/setcursor":197,"../../plots/cartesian":235,"../../plots/cartesian/axis_ids":225,"../../plots/plots":256,"../../registry":269,"../color":52,"../dragelement":71,"../drawing":74,"../titles":147,"./constants":123,"d3":16}],126:[function(_dereq_,module,exports){
30636/**
30637* Copyright 2012-2020, Plotly, Inc.
30638* All rights reserved.
30639*
30640* This source code is licensed under the MIT license found in the
30641* LICENSE file in the root directory of this source tree.
30642*/
30643
30644'use strict';
30645
30646var axisIDs = _dereq_('../../plots/cartesian/axis_ids');
30647var svgTextUtils = _dereq_('../../lib/svg_text_utils');
30648var constants = _dereq_('./constants');
30649var LINE_SPACING = _dereq_('../../constants/alignment').LINE_SPACING;
30650var name = constants.name;
30651
30652function isVisible(ax) {
30653 var rangeSlider = ax && ax[name];
30654 return rangeSlider && rangeSlider.visible;
30655}
30656exports.isVisible = isVisible;
30657
30658exports.makeData = function(fullLayout) {
30659 var axes = axisIDs.list({ _fullLayout: fullLayout }, 'x', true);
30660 var margin = fullLayout.margin;
30661 var rangeSliderData = [];
30662
30663 if(!fullLayout._has('gl2d')) {
30664 for(var i = 0; i < axes.length; i++) {
30665 var ax = axes[i];
30666
30667 if(isVisible(ax)) {
30668 rangeSliderData.push(ax);
30669
30670 var opts = ax[name];
30671 opts._id = name + ax._id;
30672 opts._height = (fullLayout.height - margin.b - margin.t) * opts.thickness;
30673 opts._offsetShift = Math.floor(opts.borderwidth / 2);
30674 }
30675 }
30676 }
30677
30678 fullLayout._rangeSliderData = rangeSliderData;
30679};
30680
30681exports.autoMarginOpts = function(gd, ax) {
30682 var fullLayout = gd._fullLayout;
30683 var opts = ax[name];
30684 var axLetter = ax._id.charAt(0);
30685
30686 var bottomDepth = 0;
30687 var titleHeight = 0;
30688 if(ax.side === 'bottom') {
30689 bottomDepth = ax._depth;
30690 if(ax.title.text !== fullLayout._dfltTitle[axLetter]) {
30691 // as in rangeslider/draw.js
30692 titleHeight = 1.5 * ax.title.font.size + 10 + opts._offsetShift;
30693 // multi-line extra bump
30694 var extraLines = (ax.title.text.match(svgTextUtils.BR_TAG_ALL) || []).length;
30695 titleHeight += extraLines * ax.title.font.size * LINE_SPACING;
30696 }
30697 }
30698
30699 return {
30700 x: 0,
30701 y: ax._counterDomainMin,
30702 l: 0,
30703 r: 0,
30704 t: 0,
30705 b: opts._height + bottomDepth + Math.max(fullLayout.margin.b, titleHeight),
30706 pad: constants.extraPad + opts._offsetShift * 2
30707 };
30708};
30709
30710},{"../../constants/alignment":154,"../../lib/svg_text_utils":199,"../../plots/cartesian/axis_ids":225,"./constants":123}],127:[function(_dereq_,module,exports){
30711/**
30712* Copyright 2012-2020, Plotly, Inc.
30713* All rights reserved.
30714*
30715* This source code is licensed under the MIT license found in the
30716* LICENSE file in the root directory of this source tree.
30717*/
30718
30719'use strict';
30720
30721var Lib = _dereq_('../../lib');
30722var attrs = _dereq_('./attributes');
30723var oppAxisAttrs = _dereq_('./oppaxis_attributes');
30724var helpers = _dereq_('./helpers');
30725
30726module.exports = {
30727 moduleType: 'component',
30728 name: 'rangeslider',
30729
30730 schema: {
30731 subplots: {
30732 xaxis: {
30733 rangeslider: Lib.extendFlat({}, attrs, {
30734 yaxis: oppAxisAttrs
30735 })
30736 }
30737 }
30738 },
30739
30740 layoutAttributes: _dereq_('./attributes'),
30741 handleDefaults: _dereq_('./defaults'),
30742 calcAutorange: _dereq_('./calc_autorange'),
30743 draw: _dereq_('./draw'),
30744 isVisible: helpers.isVisible,
30745 makeData: helpers.makeData,
30746 autoMarginOpts: helpers.autoMarginOpts
30747};
30748
30749},{"../../lib":178,"./attributes":121,"./calc_autorange":122,"./defaults":124,"./draw":125,"./helpers":126,"./oppaxis_attributes":128}],128:[function(_dereq_,module,exports){
30750/**
30751* Copyright 2012-2020, Plotly, Inc.
30752* All rights reserved.
30753*
30754* This source code is licensed under the MIT license found in the
30755* LICENSE file in the root directory of this source tree.
30756*/
30757
30758'use strict';
30759
30760module.exports = {
30761 // not really a 'subplot' attribute container,
30762 // but this is the flag we use to denote attributes that
30763 // support yaxis, yaxis2, yaxis3, ... counters
30764 _isSubplotObj: true,
30765
30766 rangemode: {
30767 valType: 'enumerated',
30768 values: ['auto', 'fixed', 'match'],
30769 dflt: 'match',
30770
30771 editType: 'calc',
30772
30773 },
30774 range: {
30775 valType: 'info_array',
30776
30777 items: [
30778 {valType: 'any', editType: 'plot'},
30779 {valType: 'any', editType: 'plot'}
30780 ],
30781 editType: 'plot',
30782
30783 },
30784 editType: 'calc'
30785};
30786
30787},{}],129:[function(_dereq_,module,exports){
30788/**
30789* Copyright 2012-2020, Plotly, Inc.
30790* All rights reserved.
30791*
30792* This source code is licensed under the MIT license found in the
30793* LICENSE file in the root directory of this source tree.
30794*/
30795
30796'use strict';
30797
30798var annAttrs = _dereq_('../annotations/attributes');
30799var scatterLineAttrs = _dereq_('../../traces/scatter/attributes').line;
30800var dash = _dereq_('../drawing/attributes').dash;
30801var extendFlat = _dereq_('../../lib/extend').extendFlat;
30802var templatedArray = _dereq_('../../plot_api/plot_template').templatedArray;
30803
30804module.exports = templatedArray('shape', {
30805 visible: {
30806 valType: 'boolean',
30807
30808 dflt: true,
30809 editType: 'calc+arraydraw',
30810
30811 },
30812
30813 type: {
30814 valType: 'enumerated',
30815 values: ['circle', 'rect', 'path', 'line'],
30816
30817 editType: 'calc+arraydraw',
30818
30819 },
30820
30821 layer: {
30822 valType: 'enumerated',
30823 values: ['below', 'above'],
30824 dflt: 'above',
30825
30826 editType: 'arraydraw',
30827
30828 },
30829
30830 xref: extendFlat({}, annAttrs.xref, {
30831
30832 }),
30833 xsizemode: {
30834 valType: 'enumerated',
30835 values: ['scaled', 'pixel'],
30836 dflt: 'scaled',
30837
30838 editType: 'calc+arraydraw',
30839
30840 },
30841 xanchor: {
30842 valType: 'any',
30843
30844 editType: 'calc+arraydraw',
30845
30846 },
30847 x0: {
30848 valType: 'any',
30849
30850 editType: 'calc+arraydraw',
30851
30852 },
30853 x1: {
30854 valType: 'any',
30855
30856 editType: 'calc+arraydraw',
30857
30858 },
30859
30860 yref: extendFlat({}, annAttrs.yref, {
30861
30862 }),
30863 ysizemode: {
30864 valType: 'enumerated',
30865 values: ['scaled', 'pixel'],
30866 dflt: 'scaled',
30867
30868 editType: 'calc+arraydraw',
30869
30870 },
30871 yanchor: {
30872 valType: 'any',
30873
30874 editType: 'calc+arraydraw',
30875
30876 },
30877 y0: {
30878 valType: 'any',
30879
30880 editType: 'calc+arraydraw',
30881
30882 },
30883 y1: {
30884 valType: 'any',
30885
30886 editType: 'calc+arraydraw',
30887
30888 },
30889
30890 path: {
30891 valType: 'string',
30892
30893 editType: 'calc+arraydraw',
30894
30895 },
30896
30897 opacity: {
30898 valType: 'number',
30899 min: 0,
30900 max: 1,
30901 dflt: 1,
30902
30903 editType: 'arraydraw',
30904
30905 },
30906 line: {
30907 color: extendFlat({}, scatterLineAttrs.color, {editType: 'arraydraw'}),
30908 width: extendFlat({}, scatterLineAttrs.width, {editType: 'calc+arraydraw'}),
30909 dash: extendFlat({}, dash, {editType: 'arraydraw'}),
30910
30911 editType: 'calc+arraydraw'
30912 },
30913 fillcolor: {
30914 valType: 'color',
30915 dflt: 'rgba(0,0,0,0)',
30916
30917 editType: 'arraydraw',
30918
30919 },
30920 fillrule: {
30921 valType: 'enumerated',
30922 values: ['evenodd', 'nonzero'],
30923 dflt: 'evenodd',
30924
30925 editType: 'arraydraw',
30926
30927 },
30928 editable: {
30929 valType: 'boolean',
30930
30931 dflt: false,
30932 editType: 'calc+arraydraw',
30933
30934 },
30935
30936 editType: 'arraydraw'
30937});
30938
30939},{"../../lib/extend":173,"../../plot_api/plot_template":212,"../../traces/scatter/attributes":389,"../annotations/attributes":37,"../drawing/attributes":73}],130:[function(_dereq_,module,exports){
30940/**
30941* Copyright 2012-2020, Plotly, Inc.
30942* All rights reserved.
30943*
30944* This source code is licensed under the MIT license found in the
30945* LICENSE file in the root directory of this source tree.
30946*/
30947
30948'use strict';
30949
30950var Lib = _dereq_('../../lib');
30951var Axes = _dereq_('../../plots/cartesian/axes');
30952
30953var constants = _dereq_('./constants');
30954var helpers = _dereq_('./helpers');
30955
30956
30957module.exports = function calcAutorange(gd) {
30958 var fullLayout = gd._fullLayout;
30959 var shapeList = Lib.filterVisible(fullLayout.shapes);
30960
30961 if(!shapeList.length || !gd._fullData.length) return;
30962
30963 for(var i = 0; i < shapeList.length; i++) {
30964 var shape = shapeList[i];
30965 shape._extremes = {};
30966
30967 var ax, bounds;
30968
30969 if(shape.xref !== 'paper') {
30970 var vx0 = shape.xsizemode === 'pixel' ? shape.xanchor : shape.x0;
30971 var vx1 = shape.xsizemode === 'pixel' ? shape.xanchor : shape.x1;
30972 ax = Axes.getFromId(gd, shape.xref);
30973
30974 bounds = shapeBounds(ax, vx0, vx1, shape.path, constants.paramIsX);
30975 if(bounds) {
30976 shape._extremes[ax._id] = Axes.findExtremes(ax, bounds, calcXPaddingOptions(shape));
30977 }
30978 }
30979
30980 if(shape.yref !== 'paper') {
30981 var vy0 = shape.ysizemode === 'pixel' ? shape.yanchor : shape.y0;
30982 var vy1 = shape.ysizemode === 'pixel' ? shape.yanchor : shape.y1;
30983 ax = Axes.getFromId(gd, shape.yref);
30984
30985 bounds = shapeBounds(ax, vy0, vy1, shape.path, constants.paramIsY);
30986 if(bounds) {
30987 shape._extremes[ax._id] = Axes.findExtremes(ax, bounds, calcYPaddingOptions(shape));
30988 }
30989 }
30990 }
30991};
30992
30993function calcXPaddingOptions(shape) {
30994 return calcPaddingOptions(shape.line.width, shape.xsizemode, shape.x0, shape.x1, shape.path, false);
30995}
30996
30997function calcYPaddingOptions(shape) {
30998 return calcPaddingOptions(shape.line.width, shape.ysizemode, shape.y0, shape.y1, shape.path, true);
30999}
31000
31001function calcPaddingOptions(lineWidth, sizeMode, v0, v1, path, isYAxis) {
31002 var ppad = lineWidth / 2;
31003 var axisDirectionReverted = isYAxis;
31004
31005 if(sizeMode === 'pixel') {
31006 var coords = path ?
31007 helpers.extractPathCoords(path, isYAxis ? constants.paramIsY : constants.paramIsX) :
31008 [v0, v1];
31009 var maxValue = Lib.aggNums(Math.max, null, coords);
31010 var minValue = Lib.aggNums(Math.min, null, coords);
31011 var beforePad = minValue < 0 ? Math.abs(minValue) + ppad : ppad;
31012 var afterPad = maxValue > 0 ? maxValue + ppad : ppad;
31013
31014 return {
31015 ppad: ppad,
31016 ppadplus: axisDirectionReverted ? beforePad : afterPad,
31017 ppadminus: axisDirectionReverted ? afterPad : beforePad
31018 };
31019 } else {
31020 return {ppad: ppad};
31021 }
31022}
31023
31024function shapeBounds(ax, v0, v1, path, paramsToUse) {
31025 var convertVal = (ax.type === 'category' || ax.type === 'multicategory') ? ax.r2c : ax.d2c;
31026
31027 if(v0 !== undefined) return [convertVal(v0), convertVal(v1)];
31028 if(!path) return;
31029
31030 var min = Infinity;
31031 var max = -Infinity;
31032 var segments = path.match(constants.segmentRE);
31033 var i;
31034 var segment;
31035 var drawnParam;
31036 var params;
31037 var val;
31038
31039 if(ax.type === 'date') convertVal = helpers.decodeDate(convertVal);
31040
31041 for(i = 0; i < segments.length; i++) {
31042 segment = segments[i];
31043 drawnParam = paramsToUse[segment.charAt(0)].drawn;
31044 if(drawnParam === undefined) continue;
31045
31046 params = segments[i].substr(1).match(constants.paramRE);
31047 if(!params || params.length < drawnParam) continue;
31048
31049 val = convertVal(params[drawnParam]);
31050 if(val < min) min = val;
31051 if(val > max) max = val;
31052 }
31053 if(max >= min) return [min, max];
31054}
31055
31056},{"../../lib":178,"../../plots/cartesian/axes":222,"./constants":131,"./helpers":140}],131:[function(_dereq_,module,exports){
31057/**
31058* Copyright 2012-2020, Plotly, Inc.
31059* All rights reserved.
31060*
31061* This source code is licensed under the MIT license found in the
31062* LICENSE file in the root directory of this source tree.
31063*/
31064
31065
31066'use strict';
31067
31068
31069module.exports = {
31070 segmentRE: /[MLHVQCTSZ][^MLHVQCTSZ]*/g,
31071 paramRE: /[^\s,]+/g,
31072
31073 // which numbers in each path segment are x (or y) values
31074 // drawn is which param is a drawn point, as opposed to a
31075 // control point (which doesn't count toward autorange.
31076 // TODO: this means curved paths could extend beyond the
31077 // autorange bounds. This is a bit tricky to get right
31078 // unless we revert to bounding boxes, but perhaps there's
31079 // a calculation we could do...)
31080 paramIsX: {
31081 M: {0: true, drawn: 0},
31082 L: {0: true, drawn: 0},
31083 H: {0: true, drawn: 0},
31084 V: {},
31085 Q: {0: true, 2: true, drawn: 2},
31086 C: {0: true, 2: true, 4: true, drawn: 4},
31087 T: {0: true, drawn: 0},
31088 S: {0: true, 2: true, drawn: 2},
31089 // A: {0: true, 5: true},
31090 Z: {}
31091 },
31092
31093 paramIsY: {
31094 M: {1: true, drawn: 1},
31095 L: {1: true, drawn: 1},
31096 H: {},
31097 V: {0: true, drawn: 0},
31098 Q: {1: true, 3: true, drawn: 3},
31099 C: {1: true, 3: true, 5: true, drawn: 5},
31100 T: {1: true, drawn: 1},
31101 S: {1: true, 3: true, drawn: 5},
31102 // A: {1: true, 6: true},
31103 Z: {}
31104 },
31105
31106 numParams: {
31107 M: 2,
31108 L: 2,
31109 H: 1,
31110 V: 1,
31111 Q: 4,
31112 C: 6,
31113 T: 2,
31114 S: 4,
31115 // A: 7,
31116 Z: 0
31117 }
31118};
31119
31120},{}],132:[function(_dereq_,module,exports){
31121/**
31122* Copyright 2012-2020, Plotly, Inc.
31123* All rights reserved.
31124*
31125* This source code is licensed under the MIT license found in the
31126* LICENSE file in the root directory of this source tree.
31127*/
31128
31129
31130'use strict';
31131
31132var Lib = _dereq_('../../lib');
31133var Axes = _dereq_('../../plots/cartesian/axes');
31134var handleArrayContainerDefaults = _dereq_('../../plots/array_container_defaults');
31135
31136var attributes = _dereq_('./attributes');
31137var helpers = _dereq_('./helpers');
31138
31139
31140module.exports = function supplyLayoutDefaults(layoutIn, layoutOut) {
31141 handleArrayContainerDefaults(layoutIn, layoutOut, {
31142 name: 'shapes',
31143 handleItemDefaults: handleShapeDefaults
31144 });
31145};
31146
31147function handleShapeDefaults(shapeIn, shapeOut, fullLayout) {
31148 function coerce(attr, dflt) {
31149 return Lib.coerce(shapeIn, shapeOut, attributes, attr, dflt);
31150 }
31151
31152 var visible = coerce('visible');
31153 if(!visible) return;
31154
31155 var path = coerce('path');
31156 var dfltType = path ? 'path' : 'rect';
31157 var shapeType = coerce('type', dfltType);
31158 if(shapeOut.type !== 'path') delete shapeOut.path;
31159
31160 coerce('editable');
31161 coerce('layer');
31162 coerce('opacity');
31163 coerce('fillcolor');
31164 coerce('fillrule');
31165 var lineWidth = coerce('line.width');
31166 if(lineWidth) {
31167 coerce('line.color');
31168 coerce('line.dash');
31169 }
31170
31171 var xSizeMode = coerce('xsizemode');
31172 var ySizeMode = coerce('ysizemode');
31173
31174 // positioning
31175 var axLetters = ['x', 'y'];
31176 for(var i = 0; i < 2; i++) {
31177 var axLetter = axLetters[i];
31178 var attrAnchor = axLetter + 'anchor';
31179 var sizeMode = axLetter === 'x' ? xSizeMode : ySizeMode;
31180 var gdMock = {_fullLayout: fullLayout};
31181 var ax;
31182 var pos2r;
31183 var r2pos;
31184
31185 // xref, yref
31186 var axRef = Axes.coerceRef(shapeIn, shapeOut, gdMock, axLetter, '', 'paper');
31187
31188 if(axRef !== 'paper') {
31189 ax = Axes.getFromId(gdMock, axRef);
31190 ax._shapeIndices.push(shapeOut._index);
31191 r2pos = helpers.rangeToShapePosition(ax);
31192 pos2r = helpers.shapePositionToRange(ax);
31193 } else {
31194 pos2r = r2pos = Lib.identity;
31195 }
31196
31197 // Coerce x0, x1, y0, y1
31198 if(shapeType !== 'path') {
31199 var dflt0 = 0.25;
31200 var dflt1 = 0.75;
31201
31202 // hack until V2.0 when log has regular range behavior - make it look like other
31203 // ranges to send to coerce, then put it back after
31204 // this is all to give reasonable default position behavior on log axes, which is
31205 // a pretty unimportant edge case so we could just ignore this.
31206 var attr0 = axLetter + '0';
31207 var attr1 = axLetter + '1';
31208 var in0 = shapeIn[attr0];
31209 var in1 = shapeIn[attr1];
31210 shapeIn[attr0] = pos2r(shapeIn[attr0], true);
31211 shapeIn[attr1] = pos2r(shapeIn[attr1], true);
31212
31213 if(sizeMode === 'pixel') {
31214 coerce(attr0, 0);
31215 coerce(attr1, 10);
31216 } else {
31217 Axes.coercePosition(shapeOut, gdMock, coerce, axRef, attr0, dflt0);
31218 Axes.coercePosition(shapeOut, gdMock, coerce, axRef, attr1, dflt1);
31219 }
31220
31221 // hack part 2
31222 shapeOut[attr0] = r2pos(shapeOut[attr0]);
31223 shapeOut[attr1] = r2pos(shapeOut[attr1]);
31224 shapeIn[attr0] = in0;
31225 shapeIn[attr1] = in1;
31226 }
31227
31228 // Coerce xanchor and yanchor
31229 if(sizeMode === 'pixel') {
31230 // Hack for log axis described above
31231 var inAnchor = shapeIn[attrAnchor];
31232 shapeIn[attrAnchor] = pos2r(shapeIn[attrAnchor], true);
31233
31234 Axes.coercePosition(shapeOut, gdMock, coerce, axRef, attrAnchor, 0.25);
31235
31236 // Hack part 2
31237 shapeOut[attrAnchor] = r2pos(shapeOut[attrAnchor]);
31238 shapeIn[attrAnchor] = inAnchor;
31239 }
31240 }
31241
31242 if(shapeType === 'path') {
31243 coerce('path');
31244 } else {
31245 Lib.noneOrAll(shapeIn, shapeOut, ['x0', 'x1', 'y0', 'y1']);
31246 }
31247}
31248
31249},{"../../lib":178,"../../plots/array_container_defaults":218,"../../plots/cartesian/axes":222,"./attributes":129,"./helpers":140}],133:[function(_dereq_,module,exports){
31250/**
31251* Copyright 2012-2020, Plotly, Inc.
31252* All rights reserved.
31253*
31254* This source code is licensed under the MIT license found in the
31255* LICENSE file in the root directory of this source tree.
31256*/
31257
31258
31259'use strict';
31260
31261var Registry = _dereq_('../../registry');
31262var Lib = _dereq_('../../lib');
31263var Axes = _dereq_('../../plots/cartesian/axes');
31264
31265var readPaths = _dereq_('./draw_newshape/helpers').readPaths;
31266var displayOutlines = _dereq_('./draw_newshape/display_outlines');
31267
31268var clearOutlineControllers = _dereq_('../../plots/cartesian/handle_outline').clearOutlineControllers;
31269
31270var Color = _dereq_('../color');
31271var Drawing = _dereq_('../drawing');
31272var arrayEditor = _dereq_('../../plot_api/plot_template').arrayEditor;
31273
31274var dragElement = _dereq_('../dragelement');
31275var setCursor = _dereq_('../../lib/setcursor');
31276
31277var constants = _dereq_('./constants');
31278var helpers = _dereq_('./helpers');
31279
31280
31281// Shapes are stored in gd.layout.shapes, an array of objects
31282// index can point to one item in this array,
31283// or non-numeric to simply add a new one
31284// or -1 to modify all existing
31285// opt can be the full options object, or one key (to be set to value)
31286// or undefined to simply redraw
31287// if opt is blank, val can be 'add' or a full options object to add a new
31288// annotation at that point in the array, or 'remove' to delete this one
31289
31290module.exports = {
31291 draw: draw,
31292 drawOne: drawOne,
31293 eraseActiveShape: eraseActiveShape
31294};
31295
31296function draw(gd) {
31297 var fullLayout = gd._fullLayout;
31298
31299 // Remove previous shapes before drawing new in shapes in fullLayout.shapes
31300 fullLayout._shapeUpperLayer.selectAll('path').remove();
31301 fullLayout._shapeLowerLayer.selectAll('path').remove();
31302
31303 for(var k in fullLayout._plots) {
31304 var shapelayer = fullLayout._plots[k].shapelayer;
31305 if(shapelayer) shapelayer.selectAll('path').remove();
31306 }
31307
31308 for(var i = 0; i < fullLayout.shapes.length; i++) {
31309 if(fullLayout.shapes[i].visible) {
31310 drawOne(gd, i);
31311 }
31312 }
31313
31314 // may need to resurrect this if we put text (LaTeX) in shapes
31315 // return Plots.previousPromises(gd);
31316}
31317
31318function shouldSkipEdits(gd) {
31319 return !!gd._fullLayout._drawing;
31320}
31321
31322function couldHaveActiveShape(gd) {
31323 // for now keep config.editable: true as it was before shape-drawing PR
31324 return !gd._context.edits.shapePosition;
31325}
31326
31327function drawOne(gd, index) {
31328 // remove the existing shape if there is one.
31329 // because indices can change, we need to look in all shape layers
31330 gd._fullLayout._paperdiv
31331 .selectAll('.shapelayer [data-index="' + index + '"]')
31332 .remove();
31333
31334 var o = helpers.makeOptionsAndPlotinfo(gd, index);
31335 var options = o.options;
31336 var plotinfo = o.plotinfo;
31337
31338 // this shape is gone - quit now after deleting it
31339 // TODO: use d3 idioms instead of deleting and redrawing every time
31340 if(!options._input || options.visible === false) return;
31341
31342 if(options.layer !== 'below') {
31343 drawShape(gd._fullLayout._shapeUpperLayer);
31344 } else if(options.xref === 'paper' || options.yref === 'paper') {
31345 drawShape(gd._fullLayout._shapeLowerLayer);
31346 } else {
31347 if(plotinfo._hadPlotinfo) {
31348 var mainPlot = plotinfo.mainplotinfo || plotinfo;
31349 drawShape(mainPlot.shapelayer);
31350 } else {
31351 // Fall back to _shapeLowerLayer in case the requested subplot doesn't exist.
31352 // This can happen if you reference the shape to an x / y axis combination
31353 // that doesn't have any data on it (and layer is below)
31354 drawShape(gd._fullLayout._shapeLowerLayer);
31355 }
31356 }
31357
31358 function drawShape(shapeLayer) {
31359 var d = getPathString(gd, options);
31360 var attrs = {
31361 'data-index': index,
31362 'fill-rule': options.fillrule,
31363 d: d
31364 };
31365
31366 var opacity = options.opacity;
31367 var fillColor = options.fillcolor;
31368 var lineColor = options.line.width ? options.line.color : 'rgba(0,0,0,0)';
31369 var lineWidth = options.line.width;
31370 var lineDash = options.line.dash;
31371 if(!lineWidth && options.editable === true) {
31372 // ensure invisible border to activate the shape
31373 lineWidth = 5;
31374 lineDash = 'solid';
31375 }
31376
31377 var isOpen = d[d.length - 1] !== 'Z';
31378
31379 var isActiveShape = couldHaveActiveShape(gd) &&
31380 options.editable && gd._fullLayout._activeShapeIndex === index;
31381
31382 if(isActiveShape) {
31383 fillColor = isOpen ? 'rgba(0,0,0,0)' :
31384 gd._fullLayout.activeshape.fillcolor;
31385
31386 opacity = gd._fullLayout.activeshape.opacity;
31387 }
31388
31389 var path = shapeLayer.append('path')
31390 .attr(attrs)
31391 .style('opacity', opacity)
31392 .call(Color.stroke, lineColor)
31393 .call(Color.fill, fillColor)
31394 .call(Drawing.dashLine, lineDash, lineWidth);
31395
31396 setClipPath(path, gd, options);
31397
31398 var editHelpers;
31399 if(isActiveShape || gd._context.edits.shapePosition) editHelpers = arrayEditor(gd.layout, 'shapes', options);
31400
31401 if(isActiveShape) {
31402 path.style({
31403 'cursor': 'move',
31404 });
31405
31406 var dragOptions = {
31407 element: path.node(),
31408 plotinfo: plotinfo,
31409 gd: gd,
31410 editHelpers: editHelpers,
31411 isActiveShape: true // i.e. to enable controllers
31412 };
31413
31414 var polygons = readPaths(d, gd);
31415 // display polygons on the screen
31416 displayOutlines(polygons, path, dragOptions);
31417 } else {
31418 if(gd._context.edits.shapePosition) {
31419 setupDragElement(gd, path, options, index, shapeLayer, editHelpers);
31420 } else if(options.editable === true) {
31421 path.style('pointer-events',
31422 (isOpen || Color.opacity(fillColor) * opacity <= 0.5) ? 'stroke' : 'all'
31423 );
31424 }
31425 }
31426
31427 path.node().addEventListener('click', function() { return activateShape(gd, path); });
31428 }
31429}
31430
31431function setClipPath(shapePath, gd, shapeOptions) {
31432 // note that for layer="below" the clipAxes can be different from the
31433 // subplot we're drawing this in. This could cause problems if the shape
31434 // spans two subplots. See https://github.com/plotly/plotly.js/issues/1452
31435 var clipAxes = (shapeOptions.xref + shapeOptions.yref).replace(/paper/g, '');
31436
31437 Drawing.setClipUrl(
31438 shapePath,
31439 clipAxes ? 'clip' + gd._fullLayout._uid + clipAxes : null,
31440 gd
31441 );
31442}
31443
31444function setupDragElement(gd, shapePath, shapeOptions, index, shapeLayer, editHelpers) {
31445 var MINWIDTH = 10;
31446 var MINHEIGHT = 10;
31447
31448 var xPixelSized = shapeOptions.xsizemode === 'pixel';
31449 var yPixelSized = shapeOptions.ysizemode === 'pixel';
31450 var isLine = shapeOptions.type === 'line';
31451 var isPath = shapeOptions.type === 'path';
31452
31453 var modifyItem = editHelpers.modifyItem;
31454
31455 var x0, y0, x1, y1, xAnchor, yAnchor;
31456 var n0, s0, w0, e0, optN, optS, optW, optE;
31457 var pathIn;
31458
31459 // setup conversion functions
31460 var xa = Axes.getFromId(gd, shapeOptions.xref);
31461 var ya = Axes.getFromId(gd, shapeOptions.yref);
31462 var x2p = helpers.getDataToPixel(gd, xa);
31463 var y2p = helpers.getDataToPixel(gd, ya, true);
31464 var p2x = helpers.getPixelToData(gd, xa);
31465 var p2y = helpers.getPixelToData(gd, ya, true);
31466
31467 var sensoryElement = obtainSensoryElement();
31468 var dragOptions = {
31469 element: sensoryElement.node(),
31470 gd: gd,
31471 prepFn: startDrag,
31472 doneFn: endDrag,
31473 clickFn: abortDrag
31474 };
31475 var dragMode;
31476
31477 dragElement.init(dragOptions);
31478
31479 sensoryElement.node().onmousemove = updateDragMode;
31480
31481 function obtainSensoryElement() {
31482 return isLine ? createLineDragHandles() : shapePath;
31483 }
31484
31485 function createLineDragHandles() {
31486 var minSensoryWidth = 10;
31487 var sensoryWidth = Math.max(shapeOptions.line.width, minSensoryWidth);
31488
31489 // Helper shapes group
31490 // Note that by setting the `data-index` attr, it is ensured that
31491 // the helper group is purged in this modules `draw` function
31492 var g = shapeLayer.append('g')
31493 .attr('data-index', index);
31494
31495 // Helper path for moving
31496 g.append('path')
31497 .attr('d', shapePath.attr('d'))
31498 .style({
31499 'cursor': 'move',
31500 'stroke-width': sensoryWidth,
31501 'stroke-opacity': '0' // ensure not visible
31502 });
31503
31504 // Helper circles for resizing
31505 var circleStyle = {
31506 'fill-opacity': '0' // ensure not visible
31507 };
31508 var circleRadius = Math.max(sensoryWidth / 2, minSensoryWidth);
31509
31510 g.append('circle')
31511 .attr({
31512 'data-line-point': 'start-point',
31513 'cx': xPixelSized ? x2p(shapeOptions.xanchor) + shapeOptions.x0 : x2p(shapeOptions.x0),
31514 'cy': yPixelSized ? y2p(shapeOptions.yanchor) - shapeOptions.y0 : y2p(shapeOptions.y0),
31515 'r': circleRadius
31516 })
31517 .style(circleStyle)
31518 .classed('cursor-grab', true);
31519
31520 g.append('circle')
31521 .attr({
31522 'data-line-point': 'end-point',
31523 'cx': xPixelSized ? x2p(shapeOptions.xanchor) + shapeOptions.x1 : x2p(shapeOptions.x1),
31524 'cy': yPixelSized ? y2p(shapeOptions.yanchor) - shapeOptions.y1 : y2p(shapeOptions.y1),
31525 'r': circleRadius
31526 })
31527 .style(circleStyle)
31528 .classed('cursor-grab', true);
31529
31530 return g;
31531 }
31532
31533 function updateDragMode(evt) {
31534 if(shouldSkipEdits(gd)) {
31535 dragMode = null;
31536 return;
31537 }
31538
31539 if(isLine) {
31540 if(evt.target.tagName === 'path') {
31541 dragMode = 'move';
31542 } else {
31543 dragMode = evt.target.attributes['data-line-point'].value === 'start-point' ?
31544 'resize-over-start-point' : 'resize-over-end-point';
31545 }
31546 } else {
31547 // element might not be on screen at time of setup,
31548 // so obtain bounding box here
31549 var dragBBox = dragOptions.element.getBoundingClientRect();
31550
31551 // choose 'move' or 'resize'
31552 // based on initial position of cursor within the drag element
31553 var w = dragBBox.right - dragBBox.left;
31554 var h = dragBBox.bottom - dragBBox.top;
31555 var x = evt.clientX - dragBBox.left;
31556 var y = evt.clientY - dragBBox.top;
31557 var cursor = (!isPath && w > MINWIDTH && h > MINHEIGHT && !evt.shiftKey) ?
31558 dragElement.getCursor(x / w, 1 - y / h) :
31559 'move';
31560
31561 setCursor(shapePath, cursor);
31562
31563 // possible values 'move', 'sw', 'w', 'se', 'e', 'ne', 'n', 'nw' and 'w'
31564 dragMode = cursor.split('-')[0];
31565 }
31566 }
31567
31568 function startDrag(evt) {
31569 if(shouldSkipEdits(gd)) return;
31570
31571 // setup update strings and initial values
31572 if(xPixelSized) {
31573 xAnchor = x2p(shapeOptions.xanchor);
31574 }
31575 if(yPixelSized) {
31576 yAnchor = y2p(shapeOptions.yanchor);
31577 }
31578
31579 if(shapeOptions.type === 'path') {
31580 pathIn = shapeOptions.path;
31581 } else {
31582 x0 = xPixelSized ? shapeOptions.x0 : x2p(shapeOptions.x0);
31583 y0 = yPixelSized ? shapeOptions.y0 : y2p(shapeOptions.y0);
31584 x1 = xPixelSized ? shapeOptions.x1 : x2p(shapeOptions.x1);
31585 y1 = yPixelSized ? shapeOptions.y1 : y2p(shapeOptions.y1);
31586 }
31587
31588 if(x0 < x1) {
31589 w0 = x0;
31590 optW = 'x0';
31591 e0 = x1;
31592 optE = 'x1';
31593 } else {
31594 w0 = x1;
31595 optW = 'x1';
31596 e0 = x0;
31597 optE = 'x0';
31598 }
31599
31600 // For fixed size shapes take opposing direction of y-axis into account.
31601 // Hint: For data sized shapes this is done by the y2p function.
31602 if((!yPixelSized && y0 < y1) || (yPixelSized && y0 > y1)) {
31603 n0 = y0;
31604 optN = 'y0';
31605 s0 = y1;
31606 optS = 'y1';
31607 } else {
31608 n0 = y1;
31609 optN = 'y1';
31610 s0 = y0;
31611 optS = 'y0';
31612 }
31613
31614 // setup dragMode and the corresponding handler
31615 updateDragMode(evt);
31616 renderVisualCues(shapeLayer, shapeOptions);
31617 deactivateClipPathTemporarily(shapePath, shapeOptions, gd);
31618 dragOptions.moveFn = (dragMode === 'move') ? moveShape : resizeShape;
31619 dragOptions.altKey = evt.altKey;
31620 }
31621
31622 function endDrag() {
31623 if(shouldSkipEdits(gd)) return;
31624
31625 setCursor(shapePath);
31626 removeVisualCues(shapeLayer);
31627
31628 // Don't rely on clipPath being activated during re-layout
31629 setClipPath(shapePath, gd, shapeOptions);
31630 Registry.call('_guiRelayout', gd, editHelpers.getUpdateObj());
31631 }
31632
31633 function abortDrag() {
31634 if(shouldSkipEdits(gd)) return;
31635
31636 removeVisualCues(shapeLayer);
31637 }
31638
31639 function moveShape(dx, dy) {
31640 if(shapeOptions.type === 'path') {
31641 var noOp = function(coord) { return coord; };
31642 var moveX = noOp;
31643 var moveY = noOp;
31644
31645 if(xPixelSized) {
31646 modifyItem('xanchor', shapeOptions.xanchor = p2x(xAnchor + dx));
31647 } else {
31648 moveX = function moveX(x) { return p2x(x2p(x) + dx); };
31649 if(xa && xa.type === 'date') moveX = helpers.encodeDate(moveX);
31650 }
31651
31652 if(yPixelSized) {
31653 modifyItem('yanchor', shapeOptions.yanchor = p2y(yAnchor + dy));
31654 } else {
31655 moveY = function moveY(y) { return p2y(y2p(y) + dy); };
31656 if(ya && ya.type === 'date') moveY = helpers.encodeDate(moveY);
31657 }
31658
31659 modifyItem('path', shapeOptions.path = movePath(pathIn, moveX, moveY));
31660 } else {
31661 if(xPixelSized) {
31662 modifyItem('xanchor', shapeOptions.xanchor = p2x(xAnchor + dx));
31663 } else {
31664 modifyItem('x0', shapeOptions.x0 = p2x(x0 + dx));
31665 modifyItem('x1', shapeOptions.x1 = p2x(x1 + dx));
31666 }
31667
31668 if(yPixelSized) {
31669 modifyItem('yanchor', shapeOptions.yanchor = p2y(yAnchor + dy));
31670 } else {
31671 modifyItem('y0', shapeOptions.y0 = p2y(y0 + dy));
31672 modifyItem('y1', shapeOptions.y1 = p2y(y1 + dy));
31673 }
31674 }
31675
31676 shapePath.attr('d', getPathString(gd, shapeOptions));
31677 renderVisualCues(shapeLayer, shapeOptions);
31678 }
31679
31680 function resizeShape(dx, dy) {
31681 if(isPath) {
31682 // TODO: implement path resize, don't forget to update dragMode code
31683 var noOp = function(coord) { return coord; };
31684 var moveX = noOp;
31685 var moveY = noOp;
31686
31687 if(xPixelSized) {
31688 modifyItem('xanchor', shapeOptions.xanchor = p2x(xAnchor + dx));
31689 } else {
31690 moveX = function moveX(x) { return p2x(x2p(x) + dx); };
31691 if(xa && xa.type === 'date') moveX = helpers.encodeDate(moveX);
31692 }
31693
31694 if(yPixelSized) {
31695 modifyItem('yanchor', shapeOptions.yanchor = p2y(yAnchor + dy));
31696 } else {
31697 moveY = function moveY(y) { return p2y(y2p(y) + dy); };
31698 if(ya && ya.type === 'date') moveY = helpers.encodeDate(moveY);
31699 }
31700
31701 modifyItem('path', shapeOptions.path = movePath(pathIn, moveX, moveY));
31702 } else if(isLine) {
31703 if(dragMode === 'resize-over-start-point') {
31704 var newX0 = x0 + dx;
31705 var newY0 = yPixelSized ? y0 - dy : y0 + dy;
31706 modifyItem('x0', shapeOptions.x0 = xPixelSized ? newX0 : p2x(newX0));
31707 modifyItem('y0', shapeOptions.y0 = yPixelSized ? newY0 : p2y(newY0));
31708 } else if(dragMode === 'resize-over-end-point') {
31709 var newX1 = x1 + dx;
31710 var newY1 = yPixelSized ? y1 - dy : y1 + dy;
31711 modifyItem('x1', shapeOptions.x1 = xPixelSized ? newX1 : p2x(newX1));
31712 modifyItem('y1', shapeOptions.y1 = yPixelSized ? newY1 : p2y(newY1));
31713 }
31714 } else {
31715 var has = function(str) { return dragMode.indexOf(str) !== -1; };
31716 var hasN = has('n');
31717 var hasS = has('s');
31718 var hasW = has('w');
31719 var hasE = has('e');
31720
31721 var newN = hasN ? n0 + dy : n0;
31722 var newS = hasS ? s0 + dy : s0;
31723 var newW = hasW ? w0 + dx : w0;
31724 var newE = hasE ? e0 + dx : e0;
31725
31726 if(yPixelSized) {
31727 // Do things in opposing direction for y-axis.
31728 // Hint: for data-sized shapes the reversal of axis direction is done in p2y.
31729 if(hasN) newN = n0 - dy;
31730 if(hasS) newS = s0 - dy;
31731 }
31732
31733 // Update shape eventually. Again, be aware of the
31734 // opposing direction of the y-axis of fixed size shapes.
31735 if(
31736 (!yPixelSized && newS - newN > MINHEIGHT) ||
31737 (yPixelSized && newN - newS > MINHEIGHT)
31738 ) {
31739 modifyItem(optN, shapeOptions[optN] = yPixelSized ? newN : p2y(newN));
31740 modifyItem(optS, shapeOptions[optS] = yPixelSized ? newS : p2y(newS));
31741 }
31742 if(newE - newW > MINWIDTH) {
31743 modifyItem(optW, shapeOptions[optW] = xPixelSized ? newW : p2x(newW));
31744 modifyItem(optE, shapeOptions[optE] = xPixelSized ? newE : p2x(newE));
31745 }
31746 }
31747
31748 shapePath.attr('d', getPathString(gd, shapeOptions));
31749 renderVisualCues(shapeLayer, shapeOptions);
31750 }
31751
31752 function renderVisualCues(shapeLayer, shapeOptions) {
31753 if(xPixelSized || yPixelSized) {
31754 renderAnchor();
31755 }
31756
31757 function renderAnchor() {
31758 var isNotPath = shapeOptions.type !== 'path';
31759
31760 // d3 join with dummy data to satisfy d3 data-binding
31761 var visualCues = shapeLayer.selectAll('.visual-cue').data([0]);
31762
31763 // Enter
31764 var strokeWidth = 1;
31765 visualCues.enter()
31766 .append('path')
31767 .attr({
31768 'fill': '#fff',
31769 'fill-rule': 'evenodd',
31770 'stroke': '#000',
31771 'stroke-width': strokeWidth
31772 })
31773 .classed('visual-cue', true);
31774
31775 // Update
31776 var posX = x2p(
31777 xPixelSized ?
31778 shapeOptions.xanchor :
31779 Lib.midRange(
31780 isNotPath ?
31781 [shapeOptions.x0, shapeOptions.x1] :
31782 helpers.extractPathCoords(shapeOptions.path, constants.paramIsX))
31783 );
31784 var posY = y2p(
31785 yPixelSized ?
31786 shapeOptions.yanchor :
31787 Lib.midRange(
31788 isNotPath ?
31789 [shapeOptions.y0, shapeOptions.y1] :
31790 helpers.extractPathCoords(shapeOptions.path, constants.paramIsY))
31791 );
31792
31793 posX = helpers.roundPositionForSharpStrokeRendering(posX, strokeWidth);
31794 posY = helpers.roundPositionForSharpStrokeRendering(posY, strokeWidth);
31795
31796 if(xPixelSized && yPixelSized) {
31797 var crossPath = 'M' + (posX - 1 - strokeWidth) + ',' + (posY - 1 - strokeWidth) +
31798 'h-8v2h8 v8h2v-8 h8v-2h-8 v-8h-2 Z';
31799 visualCues.attr('d', crossPath);
31800 } else if(xPixelSized) {
31801 var vBarPath = 'M' + (posX - 1 - strokeWidth) + ',' + (posY - 9 - strokeWidth) +
31802 'v18 h2 v-18 Z';
31803 visualCues.attr('d', vBarPath);
31804 } else {
31805 var hBarPath = 'M' + (posX - 9 - strokeWidth) + ',' + (posY - 1 - strokeWidth) +
31806 'h18 v2 h-18 Z';
31807 visualCues.attr('d', hBarPath);
31808 }
31809 }
31810 }
31811
31812 function removeVisualCues(shapeLayer) {
31813 shapeLayer.selectAll('.visual-cue').remove();
31814 }
31815
31816 function deactivateClipPathTemporarily(shapePath, shapeOptions, gd) {
31817 var xref = shapeOptions.xref;
31818 var yref = shapeOptions.yref;
31819 var xa = Axes.getFromId(gd, xref);
31820 var ya = Axes.getFromId(gd, yref);
31821
31822 var clipAxes = '';
31823 if(xref !== 'paper' && !xa.autorange) clipAxes += xref;
31824 if(yref !== 'paper' && !ya.autorange) clipAxes += yref;
31825
31826 Drawing.setClipUrl(
31827 shapePath,
31828 clipAxes ? 'clip' + gd._fullLayout._uid + clipAxes : null,
31829 gd
31830 );
31831 }
31832}
31833
31834function getPathString(gd, options) {
31835 var type = options.type;
31836 var xa = Axes.getFromId(gd, options.xref);
31837 var ya = Axes.getFromId(gd, options.yref);
31838 var gs = gd._fullLayout._size;
31839 var x2r, x2p, y2r, y2p;
31840 var x0, x1, y0, y1;
31841
31842 if(xa) {
31843 x2r = helpers.shapePositionToRange(xa);
31844 x2p = function(v) { return xa._offset + xa.r2p(x2r(v, true)); };
31845 } else {
31846 x2p = function(v) { return gs.l + gs.w * v; };
31847 }
31848
31849 if(ya) {
31850 y2r = helpers.shapePositionToRange(ya);
31851 y2p = function(v) { return ya._offset + ya.r2p(y2r(v, true)); };
31852 } else {
31853 y2p = function(v) { return gs.t + gs.h * (1 - v); };
31854 }
31855
31856 if(type === 'path') {
31857 if(xa && xa.type === 'date') x2p = helpers.decodeDate(x2p);
31858 if(ya && ya.type === 'date') y2p = helpers.decodeDate(y2p);
31859 return convertPath(options, x2p, y2p);
31860 }
31861
31862 if(options.xsizemode === 'pixel') {
31863 var xAnchorPos = x2p(options.xanchor);
31864 x0 = xAnchorPos + options.x0;
31865 x1 = xAnchorPos + options.x1;
31866 } else {
31867 x0 = x2p(options.x0);
31868 x1 = x2p(options.x1);
31869 }
31870
31871 if(options.ysizemode === 'pixel') {
31872 var yAnchorPos = y2p(options.yanchor);
31873 y0 = yAnchorPos - options.y0;
31874 y1 = yAnchorPos - options.y1;
31875 } else {
31876 y0 = y2p(options.y0);
31877 y1 = y2p(options.y1);
31878 }
31879
31880 if(type === 'line') return 'M' + x0 + ',' + y0 + 'L' + x1 + ',' + y1;
31881 if(type === 'rect') return 'M' + x0 + ',' + y0 + 'H' + x1 + 'V' + y1 + 'H' + x0 + 'Z';
31882
31883 // circle
31884 var cx = (x0 + x1) / 2;
31885 var cy = (y0 + y1) / 2;
31886 var rx = Math.abs(cx - x0);
31887 var ry = Math.abs(cy - y0);
31888 var rArc = 'A' + rx + ',' + ry;
31889 var rightPt = (cx + rx) + ',' + cy;
31890 var topPt = cx + ',' + (cy - ry);
31891 return 'M' + rightPt + rArc + ' 0 1,1 ' + topPt +
31892 rArc + ' 0 0,1 ' + rightPt + 'Z';
31893}
31894
31895
31896function convertPath(options, x2p, y2p) {
31897 var pathIn = options.path;
31898 var xSizemode = options.xsizemode;
31899 var ySizemode = options.ysizemode;
31900 var xAnchor = options.xanchor;
31901 var yAnchor = options.yanchor;
31902
31903 return pathIn.replace(constants.segmentRE, function(segment) {
31904 var paramNumber = 0;
31905 var segmentType = segment.charAt(0);
31906 var xParams = constants.paramIsX[segmentType];
31907 var yParams = constants.paramIsY[segmentType];
31908 var nParams = constants.numParams[segmentType];
31909
31910 var paramString = segment.substr(1).replace(constants.paramRE, function(param) {
31911 if(xParams[paramNumber]) {
31912 if(xSizemode === 'pixel') param = x2p(xAnchor) + Number(param);
31913 else param = x2p(param);
31914 } else if(yParams[paramNumber]) {
31915 if(ySizemode === 'pixel') param = y2p(yAnchor) - Number(param);
31916 else param = y2p(param);
31917 }
31918 paramNumber++;
31919
31920 if(paramNumber > nParams) param = 'X';
31921 return param;
31922 });
31923
31924 if(paramNumber > nParams) {
31925 paramString = paramString.replace(/[\s,]*X.*/, '');
31926 Lib.log('Ignoring extra params in segment ' + segment);
31927 }
31928
31929 return segmentType + paramString;
31930 });
31931}
31932
31933function movePath(pathIn, moveX, moveY) {
31934 return pathIn.replace(constants.segmentRE, function(segment) {
31935 var paramNumber = 0;
31936 var segmentType = segment.charAt(0);
31937 var xParams = constants.paramIsX[segmentType];
31938 var yParams = constants.paramIsY[segmentType];
31939 var nParams = constants.numParams[segmentType];
31940
31941 var paramString = segment.substr(1).replace(constants.paramRE, function(param) {
31942 if(paramNumber >= nParams) return param;
31943
31944 if(xParams[paramNumber]) param = moveX(param);
31945 else if(yParams[paramNumber]) param = moveY(param);
31946
31947 paramNumber++;
31948
31949 return param;
31950 });
31951
31952 return segmentType + paramString;
31953 });
31954}
31955
31956function activateShape(gd, path) {
31957 if(!couldHaveActiveShape(gd)) return;
31958
31959 var element = path.node();
31960 var id = +element.getAttribute('data-index');
31961 if(id >= 0) {
31962 // deactivate if already active
31963 if(id === gd._fullLayout._activeShapeIndex) {
31964 deactivateShape(gd);
31965 return;
31966 }
31967
31968 gd._fullLayout._activeShapeIndex = id;
31969 gd._fullLayout._deactivateShape = deactivateShape;
31970 draw(gd);
31971 }
31972}
31973
31974function deactivateShape(gd) {
31975 if(!couldHaveActiveShape(gd)) return;
31976
31977 var id = gd._fullLayout._activeShapeIndex;
31978 if(id >= 0) {
31979 clearOutlineControllers(gd);
31980 delete gd._fullLayout._activeShapeIndex;
31981 draw(gd);
31982 }
31983}
31984
31985function eraseActiveShape(gd) {
31986 if(!couldHaveActiveShape(gd)) return;
31987
31988 clearOutlineControllers(gd);
31989
31990 var id = gd._fullLayout._activeShapeIndex;
31991 var shapes = (gd.layout || {}).shapes || [];
31992 if(id < shapes.length) {
31993 var newShapes = [];
31994 for(var q = 0; q < shapes.length; q++) {
31995 if(q !== id) {
31996 newShapes.push(shapes[q]);
31997 }
31998 }
31999
32000 delete gd._fullLayout._activeShapeIndex;
32001
32002 Registry.call('_guiRelayout', gd, {
32003 shapes: newShapes
32004 });
32005 }
32006}
32007
32008},{"../../lib":178,"../../lib/setcursor":197,"../../plot_api/plot_template":212,"../../plots/cartesian/axes":222,"../../plots/cartesian/handle_outline":232,"../../registry":269,"../color":52,"../dragelement":71,"../drawing":74,"./constants":131,"./draw_newshape/display_outlines":137,"./draw_newshape/helpers":138,"./helpers":140}],134:[function(_dereq_,module,exports){
32009/**
32010* Copyright 2012-2020, Plotly, Inc.
32011* All rights reserved.
32012*
32013* This source code is licensed under the MIT license found in the
32014* LICENSE file in the root directory of this source tree.
32015*/
32016
32017'use strict';
32018
32019var dash = _dereq_('../../drawing/attributes').dash;
32020var extendFlat = _dereq_('../../../lib/extend').extendFlat;
32021
32022module.exports = {
32023 newshape: {
32024 line: {
32025 color: {
32026 valType: 'color',
32027 editType: 'none',
32028
32029
32030 },
32031 width: {
32032 valType: 'number',
32033 min: 0,
32034 dflt: 4,
32035
32036 editType: 'none',
32037
32038 },
32039 dash: extendFlat({}, dash, {
32040 dflt: 'solid',
32041 editType: 'none'
32042 }),
32043
32044 editType: 'none'
32045 },
32046 fillcolor: {
32047 valType: 'color',
32048 dflt: 'rgba(0,0,0,0)',
32049
32050 editType: 'none',
32051
32052 },
32053 fillrule: {
32054 valType: 'enumerated',
32055 values: ['evenodd', 'nonzero'],
32056 dflt: 'evenodd',
32057
32058 editType: 'none',
32059
32060 },
32061 opacity: {
32062 valType: 'number',
32063 min: 0,
32064 max: 1,
32065 dflt: 1,
32066
32067 editType: 'none',
32068
32069 },
32070 layer: {
32071 valType: 'enumerated',
32072 values: ['below', 'above'],
32073 dflt: 'above',
32074
32075 editType: 'none',
32076
32077 },
32078 drawdirection: {
32079 valType: 'enumerated',
32080
32081 values: ['ortho', 'horizontal', 'vertical', 'diagonal'],
32082 dflt: 'diagonal',
32083 editType: 'none',
32084
32085 },
32086
32087 editType: 'none'
32088 },
32089
32090 activeshape: {
32091 fillcolor: {
32092 valType: 'color',
32093 dflt: 'rgb(255,0,255)',
32094
32095 editType: 'none',
32096
32097 },
32098 opacity: {
32099 valType: 'number',
32100 min: 0,
32101 max: 1,
32102 dflt: 0.5,
32103
32104 editType: 'none',
32105
32106 },
32107 editType: 'none'
32108 }
32109};
32110
32111},{"../../../lib/extend":173,"../../drawing/attributes":73}],135:[function(_dereq_,module,exports){
32112/**
32113* Copyright 2012-2020, Plotly, Inc.
32114* All rights reserved.
32115*
32116* This source code is licensed under the MIT license found in the
32117* LICENSE file in the root directory of this source tree.
32118*/
32119
32120'use strict';
32121
32122var CIRCLE_SIDES = 32; // should be divisible by 4
32123
32124module.exports = {
32125 CIRCLE_SIDES: CIRCLE_SIDES,
32126 i000: 0,
32127 i090: CIRCLE_SIDES / 4,
32128 i180: CIRCLE_SIDES / 2,
32129 i270: CIRCLE_SIDES / 4 * 3,
32130 cos45: Math.cos(Math.PI / 4),
32131 sin45: Math.sin(Math.PI / 4),
32132 SQRT2: Math.sqrt(2)
32133};
32134
32135},{}],136:[function(_dereq_,module,exports){
32136/**
32137* Copyright 2012-2020, Plotly, Inc.
32138* All rights reserved.
32139*
32140* This source code is licensed under the MIT license found in the
32141* LICENSE file in the root directory of this source tree.
32142*/
32143
32144
32145'use strict';
32146
32147var Color = _dereq_('../../color');
32148
32149
32150module.exports = function supplyDrawNewShapeDefaults(layoutIn, layoutOut, coerce) {
32151 coerce('newshape.drawdirection');
32152 coerce('newshape.layer');
32153 coerce('newshape.fillcolor');
32154 coerce('newshape.fillrule');
32155 coerce('newshape.opacity');
32156 var newshapeLineWidth = coerce('newshape.line.width');
32157 if(newshapeLineWidth) {
32158 var bgcolor = (layoutIn || {}).plot_bgcolor || '#FFF';
32159 coerce('newshape.line.color', Color.contrast(bgcolor));
32160 coerce('newshape.line.dash');
32161 }
32162
32163 coerce('activeshape.fillcolor');
32164 coerce('activeshape.opacity');
32165};
32166
32167},{"../../color":52}],137:[function(_dereq_,module,exports){
32168/**
32169* Copyright 2012-2020, Plotly, Inc.
32170* All rights reserved.
32171*
32172* This source code is licensed under the MIT license found in the
32173* LICENSE file in the root directory of this source tree.
32174*/
32175
32176
32177'use strict';
32178
32179var dragElement = _dereq_('../../dragelement');
32180var dragHelpers = _dereq_('../../dragelement/helpers');
32181var drawMode = dragHelpers.drawMode;
32182
32183var Registry = _dereq_('../../../registry');
32184
32185var constants = _dereq_('./constants');
32186var i000 = constants.i000;
32187var i090 = constants.i090;
32188var i180 = constants.i180;
32189var i270 = constants.i270;
32190
32191var handleOutline = _dereq_('../../../plots/cartesian/handle_outline');
32192var clearOutlineControllers = handleOutline.clearOutlineControllers;
32193
32194var helpers = _dereq_('./helpers');
32195var pointsShapeRectangle = helpers.pointsShapeRectangle;
32196var pointsShapeEllipse = helpers.pointsShapeEllipse;
32197var writePaths = helpers.writePaths;
32198var newShapes = _dereq_('./newshapes');
32199
32200module.exports = function displayOutlines(polygons, outlines, dragOptions, nCalls) {
32201 if(!nCalls) nCalls = 0;
32202
32203 var gd = dragOptions.gd;
32204
32205 function redraw() {
32206 // recursive call
32207 displayOutlines(polygons, outlines, dragOptions, nCalls++);
32208
32209 if(pointsShapeEllipse(polygons[0])) {
32210 update({redrawing: true});
32211 }
32212 }
32213
32214 function update(opts) {
32215 dragOptions.isActiveShape = false; // i.e. to disable controllers
32216
32217 var updateObject = newShapes(outlines, dragOptions);
32218 if(Object.keys(updateObject).length) {
32219 Registry.call((opts || {}).redrawing ? 'relayout' : '_guiRelayout', gd, updateObject);
32220 }
32221 }
32222
32223
32224 var isActiveShape = dragOptions.isActiveShape;
32225 var fullLayout = gd._fullLayout;
32226 var zoomLayer = fullLayout._zoomlayer;
32227
32228 var dragmode = dragOptions.dragmode;
32229 var isDrawMode = drawMode(dragmode);
32230
32231 if(isDrawMode) gd._fullLayout._drawing = true;
32232 else if(gd._fullLayout._activeShapeIndex >= 0) clearOutlineControllers(gd);
32233
32234 // make outline
32235 outlines.attr('d', writePaths(polygons));
32236
32237 // add controllers
32238 var vertexDragOptions;
32239 var shapeDragOptions;
32240 var indexI; // cell index
32241 var indexJ; // vertex or cell-controller index
32242 var copyPolygons;
32243
32244 if(isActiveShape && !nCalls) {
32245 copyPolygons = recordPositions([], polygons);
32246
32247 var g = zoomLayer.append('g').attr('class', 'outline-controllers');
32248 addVertexControllers(g);
32249 addShapeControllers();
32250 }
32251
32252 function startDragVertex(evt) {
32253 indexI = +evt.srcElement.getAttribute('data-i');
32254 indexJ = +evt.srcElement.getAttribute('data-j');
32255
32256 vertexDragOptions[indexI][indexJ].moveFn = moveVertexController;
32257 }
32258
32259 function moveVertexController(dx, dy) {
32260 if(!polygons.length) return;
32261
32262 var x0 = copyPolygons[indexI][indexJ][1];
32263 var y0 = copyPolygons[indexI][indexJ][2];
32264
32265 var cell = polygons[indexI];
32266 var len = cell.length;
32267 if(pointsShapeRectangle(cell)) {
32268 for(var q = 0; q < len; q++) {
32269 if(q === indexJ) continue;
32270
32271 // move other corners of rectangle
32272 var pos = cell[q];
32273
32274 if(pos[1] === cell[indexJ][1]) {
32275 pos[1] = x0 + dx;
32276 }
32277
32278 if(pos[2] === cell[indexJ][2]) {
32279 pos[2] = y0 + dy;
32280 }
32281 }
32282 // move the corner
32283 cell[indexJ][1] = x0 + dx;
32284 cell[indexJ][2] = y0 + dy;
32285
32286 if(!pointsShapeRectangle(cell)) {
32287 // reject result to rectangles with ensure areas
32288 for(var j = 0; j < len; j++) {
32289 for(var k = 0; k < cell[j].length; k++) {
32290 cell[j][k] = copyPolygons[indexI][j][k];
32291 }
32292 }
32293 }
32294 } else { // other polylines
32295 cell[indexJ][1] = x0 + dx;
32296 cell[indexJ][2] = y0 + dy;
32297 }
32298
32299 redraw();
32300 }
32301
32302 function endDragVertexController() {
32303 update();
32304 }
32305
32306 function removeVertex() {
32307 if(!polygons.length) return;
32308 if(!polygons[indexI]) return;
32309 if(!polygons[indexI].length) return;
32310
32311 var newPolygon = [];
32312 for(var j = 0; j < polygons[indexI].length; j++) {
32313 if(j !== indexJ) {
32314 newPolygon.push(
32315 polygons[indexI][j]
32316 );
32317 }
32318 }
32319
32320 if(newPolygon.length > 1 && !(
32321 newPolygon.length === 2 && newPolygon[1][0] === 'Z')
32322 ) {
32323 if(indexJ === 0) {
32324 newPolygon[0][0] = 'M';
32325 }
32326
32327 polygons[indexI] = newPolygon;
32328
32329 redraw();
32330 update();
32331 }
32332 }
32333
32334 function clickVertexController(numClicks, evt) {
32335 if(numClicks === 2) {
32336 indexI = +evt.srcElement.getAttribute('data-i');
32337 indexJ = +evt.srcElement.getAttribute('data-j');
32338
32339 var cell = polygons[indexI];
32340 if(
32341 !pointsShapeRectangle(cell) &&
32342 !pointsShapeEllipse(cell)
32343 ) {
32344 removeVertex();
32345 }
32346 }
32347 }
32348
32349 function addVertexControllers(g) {
32350 vertexDragOptions = [];
32351
32352 for(var i = 0; i < polygons.length; i++) {
32353 var cell = polygons[i];
32354
32355 var onRect = pointsShapeRectangle(cell);
32356 var onEllipse = !onRect && pointsShapeEllipse(cell);
32357
32358 vertexDragOptions[i] = [];
32359 for(var j = 0; j < cell.length; j++) {
32360 if(cell[j][0] === 'Z') continue;
32361
32362 if(onEllipse &&
32363 j !== i000 &&
32364 j !== i090 &&
32365 j !== i180 &&
32366 j !== i270
32367 ) {
32368 continue;
32369 }
32370
32371 var x = cell[j][1];
32372 var y = cell[j][2];
32373
32374 var vertex = g.append('circle')
32375 .classed('cursor-grab', true)
32376 .attr('data-i', i)
32377 .attr('data-j', j)
32378 .attr('cx', x)
32379 .attr('cy', y)
32380 .attr('r', 4)
32381 .style({
32382 'mix-blend-mode': 'luminosity',
32383 fill: 'black',
32384 stroke: 'white',
32385 'stroke-width': 1
32386 });
32387
32388 vertexDragOptions[i][j] = {
32389 element: vertex.node(),
32390 gd: gd,
32391 prepFn: startDragVertex,
32392 doneFn: endDragVertexController,
32393 clickFn: clickVertexController
32394 };
32395
32396 dragElement.init(vertexDragOptions[i][j]);
32397 }
32398 }
32399 }
32400
32401 function moveShape(dx, dy) {
32402 if(!polygons.length) return;
32403
32404 for(var i = 0; i < polygons.length; i++) {
32405 for(var j = 0; j < polygons[i].length; j++) {
32406 for(var k = 0; k + 2 < polygons[i][j].length; k += 2) {
32407 polygons[i][j][k + 1] = copyPolygons[i][j][k + 1] + dx;
32408 polygons[i][j][k + 2] = copyPolygons[i][j][k + 2] + dy;
32409 }
32410 }
32411 }
32412 }
32413
32414 function moveShapeController(dx, dy) {
32415 moveShape(dx, dy);
32416
32417 redraw();
32418 }
32419
32420 function startDragShapeController(evt) {
32421 indexI = +evt.srcElement.getAttribute('data-i');
32422 if(!indexI) indexI = 0; // ensure non-existing move button get zero index
32423
32424 shapeDragOptions[indexI].moveFn = moveShapeController;
32425 }
32426
32427 function endDragShapeController() {
32428 update();
32429 }
32430
32431 function addShapeControllers() {
32432 shapeDragOptions = [];
32433
32434 if(!polygons.length) return;
32435
32436 var i = 0;
32437 shapeDragOptions[i] = {
32438 element: outlines[0][0],
32439 gd: gd,
32440 prepFn: startDragShapeController,
32441 doneFn: endDragShapeController
32442 };
32443
32444 dragElement.init(shapeDragOptions[i]);
32445 }
32446};
32447
32448function recordPositions(polygonsOut, polygonsIn) {
32449 for(var i = 0; i < polygonsIn.length; i++) {
32450 var cell = polygonsIn[i];
32451 polygonsOut[i] = [];
32452 for(var j = 0; j < cell.length; j++) {
32453 polygonsOut[i][j] = [];
32454 for(var k = 0; k < cell[j].length; k++) {
32455 polygonsOut[i][j][k] = cell[j][k];
32456 }
32457 }
32458 }
32459 return polygonsOut;
32460}
32461
32462},{"../../../plots/cartesian/handle_outline":232,"../../../registry":269,"../../dragelement":71,"../../dragelement/helpers":70,"./constants":135,"./helpers":138,"./newshapes":139}],138:[function(_dereq_,module,exports){
32463/**
32464* Copyright 2012-2020, Plotly, Inc.
32465* All rights reserved.
32466*
32467* This source code is licensed under the MIT license found in the
32468* LICENSE file in the root directory of this source tree.
32469*/
32470
32471
32472'use strict';
32473
32474var parseSvgPath = _dereq_('parse-svg-path');
32475
32476var constants = _dereq_('./constants');
32477var CIRCLE_SIDES = constants.CIRCLE_SIDES;
32478var SQRT2 = constants.SQRT2;
32479
32480var cartesianHelpers = _dereq_('../../../plots/cartesian/helpers');
32481var p2r = cartesianHelpers.p2r;
32482var r2p = cartesianHelpers.r2p;
32483
32484var iC = [0, 3, 4, 5, 6, 1, 2];
32485var iQS = [0, 3, 4, 1, 2];
32486
32487exports.writePaths = function(polygons) {
32488 var nI = polygons.length;
32489 if(!nI) return 'M0,0Z';
32490
32491 var str = '';
32492 for(var i = 0; i < nI; i++) {
32493 var nJ = polygons[i].length;
32494 for(var j = 0; j < nJ; j++) {
32495 var w = polygons[i][j][0];
32496 if(w === 'Z') {
32497 str += 'Z';
32498 } else {
32499 var nK = polygons[i][j].length;
32500 for(var k = 0; k < nK; k++) {
32501 var realK = k;
32502 if(w === 'Q' || w === 'S') {
32503 realK = iQS[k];
32504 } else if(w === 'C') {
32505 realK = iC[k];
32506 }
32507
32508 str += polygons[i][j][realK];
32509 if(k > 0 && k < nK - 1) {
32510 str += ',';
32511 }
32512 }
32513 }
32514 }
32515 }
32516
32517 return str;
32518};
32519
32520exports.readPaths = function(str, gd, plotinfo, isActiveShape) {
32521 var cmd = parseSvgPath(str);
32522
32523 var polys = [];
32524 var n = -1;
32525 var newPoly = function() {
32526 n++;
32527 polys[n] = [];
32528 };
32529
32530 var k;
32531 var x = 0;
32532 var y = 0;
32533 var initX;
32534 var initY;
32535 var recStart = function() {
32536 initX = x;
32537 initY = y;
32538 };
32539
32540 recStart();
32541 for(var i = 0; i < cmd.length; i++) {
32542 var newPos = [];
32543
32544 var x1, x2, y1, y2; // i.e. extra params for curves
32545
32546 var c = cmd[i][0];
32547 var w = c;
32548 switch(c) {
32549 case 'M':
32550 newPoly();
32551 x = +cmd[i][1];
32552 y = +cmd[i][2];
32553 newPos.push([w, x, y]);
32554
32555 recStart();
32556 break;
32557
32558 case 'Q':
32559 case 'S':
32560 x1 = +cmd[i][1];
32561 y1 = +cmd[i][2];
32562 x = +cmd[i][3];
32563 y = +cmd[i][4];
32564 newPos.push([w, x, y, x1, y1]); // -> iQS order
32565 break;
32566
32567 case 'C':
32568 x1 = +cmd[i][1];
32569 y1 = +cmd[i][2];
32570 x2 = +cmd[i][3];
32571 y2 = +cmd[i][4];
32572 x = +cmd[i][5];
32573 y = +cmd[i][6];
32574 newPos.push([w, x, y, x1, y1, x2, y2]); // -> iC order
32575 break;
32576
32577 case 'T':
32578 case 'L':
32579 x = +cmd[i][1];
32580 y = +cmd[i][2];
32581 newPos.push([w, x, y]);
32582 break;
32583
32584 case 'H':
32585 w = 'L'; // convert to line (for now)
32586 x = +cmd[i][1];
32587 newPos.push([w, x, y]);
32588 break;
32589
32590 case 'V':
32591 w = 'L'; // convert to line (for now)
32592 y = +cmd[i][1];
32593 newPos.push([w, x, y]);
32594 break;
32595
32596 case 'A':
32597 w = 'L'; // convert to line to handle circle
32598 var rx = +cmd[i][1];
32599 var ry = +cmd[i][2];
32600 if(!+cmd[i][4]) {
32601 rx = -rx;
32602 ry = -ry;
32603 }
32604
32605 var cenX = x - rx;
32606 var cenY = y;
32607 for(k = 1; k <= CIRCLE_SIDES / 2; k++) {
32608 var t = 2 * Math.PI * k / CIRCLE_SIDES;
32609 newPos.push([
32610 w,
32611 cenX + rx * Math.cos(t),
32612 cenY + ry * Math.sin(t)
32613 ]);
32614 }
32615 break;
32616
32617 case 'Z':
32618 if(x !== initX || y !== initY) {
32619 x = initX;
32620 y = initY;
32621 newPos.push([w, x, y]);
32622 }
32623 break;
32624 }
32625
32626 var domain = (plotinfo || {}).domain;
32627 var size = gd._fullLayout._size;
32628 var xPixelSized = plotinfo && plotinfo.xsizemode === 'pixel';
32629 var yPixelSized = plotinfo && plotinfo.ysizemode === 'pixel';
32630 var noOffset = isActiveShape === false;
32631
32632 for(var j = 0; j < newPos.length; j++) {
32633 for(k = 0; k + 2 < 7; k += 2) {
32634 var _x = newPos[j][k + 1];
32635 var _y = newPos[j][k + 2];
32636
32637 if(_x === undefined || _y === undefined) continue;
32638 // keep track of end point for Z
32639 x = _x;
32640 y = _y;
32641
32642 if(plotinfo) {
32643 if(plotinfo.xaxis && plotinfo.xaxis.p2r) {
32644 if(noOffset) _x -= plotinfo.xaxis._offset;
32645 if(xPixelSized) {
32646 _x = r2p(plotinfo.xaxis, plotinfo.xanchor) + _x;
32647 } else {
32648 _x = p2r(plotinfo.xaxis, _x);
32649 }
32650 } else {
32651 if(noOffset) _x -= size.l;
32652 if(domain) _x = domain.x[0] + _x / size.w;
32653 else _x = _x / size.w;
32654 }
32655
32656 if(plotinfo.yaxis && plotinfo.yaxis.p2r) {
32657 if(noOffset) _y -= plotinfo.yaxis._offset;
32658 if(yPixelSized) {
32659 _y = r2p(plotinfo.yaxis, plotinfo.yanchor) - _y;
32660 } else {
32661 _y = p2r(plotinfo.yaxis, _y);
32662 }
32663 } else {
32664 if(noOffset) _y -= size.t;
32665 if(domain) _y = domain.y[1] - _y / size.h;
32666 else _y = 1 - _y / size.h;
32667 }
32668 }
32669
32670 newPos[j][k + 1] = _x;
32671 newPos[j][k + 2] = _y;
32672 }
32673 polys[n].push(
32674 newPos[j].slice()
32675 );
32676 }
32677 }
32678
32679 return polys;
32680};
32681
32682function almostEq(a, b) {
32683 return Math.abs(a - b) <= 1e-6;
32684}
32685
32686function dist(a, b) {
32687 var dx = b[1] - a[1];
32688 var dy = b[2] - a[2];
32689 return Math.sqrt(
32690 dx * dx +
32691 dy * dy
32692 );
32693}
32694
32695exports.pointsShapeRectangle = function(cell) {
32696 var len = cell.length;
32697 if(len !== 5) return false;
32698
32699 for(var j = 1; j < 3; j++) {
32700 var e01 = cell[0][j] - cell[1][j];
32701 var e32 = cell[3][j] - cell[2][j];
32702
32703 if(!almostEq(e01, e32)) return false;
32704
32705 var e03 = cell[0][j] - cell[3][j];
32706 var e12 = cell[1][j] - cell[2][j];
32707 if(!almostEq(e03, e12)) return false;
32708 }
32709
32710 // N.B. rotated rectangles are not valid rects since rotation is not supported in shapes for now.
32711 if(
32712 !almostEq(cell[0][1], cell[1][1]) &&
32713 !almostEq(cell[0][1], cell[3][1])
32714 ) return false;
32715
32716 // reject cases with zero area
32717 return !!(
32718 dist(cell[0], cell[1]) *
32719 dist(cell[0], cell[3])
32720 );
32721};
32722
32723exports.pointsShapeEllipse = function(cell) {
32724 var len = cell.length;
32725 if(len !== CIRCLE_SIDES + 1) return false;
32726
32727 // opposite diagonals should be the same
32728 len = CIRCLE_SIDES;
32729 for(var i = 0; i < len; i++) {
32730 var k = (len * 2 - i) % len;
32731
32732 var k2 = (len / 2 + k) % len;
32733 var i2 = (len / 2 + i) % len;
32734
32735 if(!almostEq(
32736 dist(cell[i], cell[i2]),
32737 dist(cell[k], cell[k2])
32738 )) return false;
32739 }
32740 return true;
32741};
32742
32743exports.handleEllipse = function(isEllipse, start, end) {
32744 if(!isEllipse) return [start, end]; // i.e. case of line
32745
32746 var pos = exports.ellipseOver({
32747 x0: start[0],
32748 y0: start[1],
32749 x1: end[0],
32750 y1: end[1]
32751 });
32752
32753 var cx = (pos.x1 + pos.x0) / 2;
32754 var cy = (pos.y1 + pos.y0) / 2;
32755 var rx = (pos.x1 - pos.x0) / 2;
32756 var ry = (pos.y1 - pos.y0) / 2;
32757
32758 // make a circle when one dimension is zero
32759 if(!rx) rx = ry = ry / SQRT2;
32760 if(!ry) ry = rx = rx / SQRT2;
32761
32762 var cell = [];
32763 for(var i = 0; i < CIRCLE_SIDES; i++) {
32764 var t = i * 2 * Math.PI / CIRCLE_SIDES;
32765 cell.push([
32766 cx + rx * Math.cos(t),
32767 cy + ry * Math.sin(t),
32768 ]);
32769 }
32770 return cell;
32771};
32772
32773exports.ellipseOver = function(pos) {
32774 var x0 = pos.x0;
32775 var y0 = pos.y0;
32776 var x1 = pos.x1;
32777 var y1 = pos.y1;
32778
32779 var dx = x1 - x0;
32780 var dy = y1 - y0;
32781
32782 x0 -= dx;
32783 y0 -= dy;
32784
32785 var cx = (x0 + x1) / 2;
32786 var cy = (y0 + y1) / 2;
32787
32788 var scale = SQRT2;
32789 dx *= scale;
32790 dy *= scale;
32791
32792 return {
32793 x0: cx - dx,
32794 y0: cy - dy,
32795 x1: cx + dx,
32796 y1: cy + dy
32797 };
32798};
32799
32800},{"../../../plots/cartesian/helpers":233,"./constants":135,"parse-svg-path":25}],139:[function(_dereq_,module,exports){
32801/**
32802* Copyright 2012-2020, Plotly, Inc.
32803* All rights reserved.
32804*
32805* This source code is licensed under the MIT license found in the
32806* LICENSE file in the root directory of this source tree.
32807*/
32808
32809
32810'use strict';
32811
32812var dragHelpers = _dereq_('../../dragelement/helpers');
32813var drawMode = dragHelpers.drawMode;
32814var openMode = dragHelpers.openMode;
32815
32816var constants = _dereq_('./constants');
32817var i000 = constants.i000;
32818var i090 = constants.i090;
32819var i180 = constants.i180;
32820var i270 = constants.i270;
32821var cos45 = constants.cos45;
32822var sin45 = constants.sin45;
32823
32824var cartesianHelpers = _dereq_('../../../plots/cartesian/helpers');
32825var p2r = cartesianHelpers.p2r;
32826var r2p = cartesianHelpers.r2p;
32827
32828var handleOutline = _dereq_('../../../plots/cartesian/handle_outline');
32829var clearSelect = handleOutline.clearSelect;
32830
32831var helpers = _dereq_('./helpers');
32832var readPaths = helpers.readPaths;
32833var writePaths = helpers.writePaths;
32834var ellipseOver = helpers.ellipseOver;
32835
32836
32837module.exports = function newShapes(outlines, dragOptions) {
32838 if(!outlines.length) return;
32839 var e = outlines[0][0]; // pick first
32840 if(!e) return;
32841 var d = e.getAttribute('d');
32842
32843 var gd = dragOptions.gd;
32844 var drwStyle = gd._fullLayout.newshape;
32845
32846 var plotinfo = dragOptions.plotinfo;
32847 var xaxis = plotinfo.xaxis;
32848 var yaxis = plotinfo.yaxis;
32849 var xPaper = !!plotinfo.domain || !plotinfo.xaxis;
32850 var yPaper = !!plotinfo.domain || !plotinfo.yaxis;
32851
32852 var isActiveShape = dragOptions.isActiveShape;
32853 var dragmode = dragOptions.dragmode;
32854
32855 var shapes = (gd.layout || {}).shapes || [];
32856
32857 if(!drawMode(dragmode) && isActiveShape !== undefined) {
32858 var id = gd._fullLayout._activeShapeIndex;
32859 if(id < shapes.length) {
32860 switch(gd._fullLayout.shapes[id].type) {
32861 case 'rect':
32862 dragmode = 'drawrect';
32863 break;
32864 case 'circle':
32865 dragmode = 'drawcircle';
32866 break;
32867 case 'line':
32868 dragmode = 'drawline';
32869 break;
32870 case 'path':
32871 var path = shapes[id].path || '';
32872 if(path[path.length - 1] === 'Z') {
32873 dragmode = 'drawclosedpath';
32874 } else {
32875 dragmode = 'drawopenpath';
32876 }
32877 break;
32878 }
32879 }
32880 }
32881
32882 var isOpenMode = openMode(dragmode);
32883
32884 var polygons = readPaths(d, gd, plotinfo, isActiveShape);
32885
32886 var newShape = {
32887 editable: true,
32888
32889 xref: xPaper ? 'paper' : xaxis._id,
32890 yref: yPaper ? 'paper' : yaxis._id,
32891
32892 layer: drwStyle.layer,
32893 opacity: drwStyle.opacity,
32894 line: {
32895 color: drwStyle.line.color,
32896 width: drwStyle.line.width,
32897 dash: drwStyle.line.dash
32898 }
32899 };
32900
32901 if(!isOpenMode) {
32902 newShape.fillcolor = drwStyle.fillcolor;
32903 newShape.fillrule = drwStyle.fillrule;
32904 }
32905
32906 var cell;
32907 // line, rect and circle can be in one cell
32908 // only define cell if there is single cell
32909 if(polygons.length === 1) cell = polygons[0];
32910
32911 if(
32912 cell &&
32913 dragmode === 'drawrect'
32914 ) {
32915 newShape.type = 'rect';
32916 newShape.x0 = cell[0][1];
32917 newShape.y0 = cell[0][2];
32918 newShape.x1 = cell[2][1];
32919 newShape.y1 = cell[2][2];
32920 } else if(
32921 cell &&
32922 dragmode === 'drawline'
32923 ) {
32924 newShape.type = 'line';
32925 newShape.x0 = cell[0][1];
32926 newShape.y0 = cell[0][2];
32927 newShape.x1 = cell[1][1];
32928 newShape.y1 = cell[1][2];
32929 } else if(
32930 cell &&
32931 dragmode === 'drawcircle'
32932 ) {
32933 newShape.type = 'circle'; // an ellipse!
32934
32935 var xA = cell[i000][1];
32936 var xB = cell[i090][1];
32937 var xC = cell[i180][1];
32938 var xD = cell[i270][1];
32939
32940 var yA = cell[i000][2];
32941 var yB = cell[i090][2];
32942 var yC = cell[i180][2];
32943 var yD = cell[i270][2];
32944
32945 var xDateOrLog = plotinfo.xaxis && (
32946 plotinfo.xaxis.type === 'date' ||
32947 plotinfo.xaxis.type === 'log'
32948 );
32949
32950 var yDateOrLog = plotinfo.yaxis && (
32951 plotinfo.yaxis.type === 'date' ||
32952 plotinfo.yaxis.type === 'log'
32953 );
32954
32955 if(xDateOrLog) {
32956 xA = r2p(plotinfo.xaxis, xA);
32957 xB = r2p(plotinfo.xaxis, xB);
32958 xC = r2p(plotinfo.xaxis, xC);
32959 xD = r2p(plotinfo.xaxis, xD);
32960 }
32961
32962 if(yDateOrLog) {
32963 yA = r2p(plotinfo.yaxis, yA);
32964 yB = r2p(plotinfo.yaxis, yB);
32965 yC = r2p(plotinfo.yaxis, yC);
32966 yD = r2p(plotinfo.yaxis, yD);
32967 }
32968
32969 var x0 = (xB + xD) / 2;
32970 var y0 = (yA + yC) / 2;
32971 var rx = (xD - xB + xC - xA) / 2;
32972 var ry = (yD - yB + yC - yA) / 2;
32973 var pos = ellipseOver({
32974 x0: x0,
32975 y0: y0,
32976 x1: x0 + rx * cos45,
32977 y1: y0 + ry * sin45
32978 });
32979
32980 if(xDateOrLog) {
32981 pos.x0 = p2r(plotinfo.xaxis, pos.x0);
32982 pos.x1 = p2r(plotinfo.xaxis, pos.x1);
32983 }
32984
32985 if(yDateOrLog) {
32986 pos.y0 = p2r(plotinfo.yaxis, pos.y0);
32987 pos.y1 = p2r(plotinfo.yaxis, pos.y1);
32988 }
32989
32990 newShape.x0 = pos.x0;
32991 newShape.y0 = pos.y0;
32992 newShape.x1 = pos.x1;
32993 newShape.y1 = pos.y1;
32994 } else {
32995 newShape.type = 'path';
32996 if(xaxis && yaxis) fixDatesForPaths(polygons, xaxis, yaxis);
32997 newShape.path = writePaths(polygons);
32998 cell = null;
32999 }
33000
33001 clearSelect(gd);
33002
33003 var editHelpers = dragOptions.editHelpers;
33004 var modifyItem = (editHelpers || {}).modifyItem;
33005
33006 var allShapes = [];
33007 for(var q = 0; q < shapes.length; q++) {
33008 var beforeEdit = gd._fullLayout.shapes[q];
33009 allShapes[q] = beforeEdit._input;
33010
33011 if(
33012 isActiveShape !== undefined &&
33013 q === gd._fullLayout._activeShapeIndex
33014 ) {
33015 var afterEdit = newShape;
33016
33017 switch(beforeEdit.type) {
33018 case 'line':
33019 case 'rect':
33020 case 'circle':
33021 modifyItem('x0', afterEdit.x0);
33022 modifyItem('x1', afterEdit.x1);
33023 modifyItem('y0', afterEdit.y0);
33024 modifyItem('y1', afterEdit.y1);
33025 break;
33026
33027 case 'path':
33028 modifyItem('path', afterEdit.path);
33029 break;
33030 }
33031 }
33032 }
33033
33034 if(isActiveShape === undefined) {
33035 allShapes.push(newShape); // add new shape
33036 return allShapes;
33037 }
33038
33039 return editHelpers ? editHelpers.getUpdateObj() : {};
33040};
33041
33042function fixDatesForPaths(polygons, xaxis, yaxis) {
33043 var xIsDate = xaxis.type === 'date';
33044 var yIsDate = yaxis.type === 'date';
33045 if(!xIsDate && !yIsDate) return polygons;
33046
33047 for(var i = 0; i < polygons.length; i++) {
33048 for(var j = 0; j < polygons[i].length; j++) {
33049 for(var k = 0; k + 2 < polygons[i][j].length; k += 2) {
33050 if(xIsDate) polygons[i][j][k + 1] = polygons[i][j][k + 1].replace(' ', '_');
33051 if(yIsDate) polygons[i][j][k + 2] = polygons[i][j][k + 2].replace(' ', '_');
33052 }
33053 }
33054 }
33055
33056 return polygons;
33057}
33058
33059},{"../../../plots/cartesian/handle_outline":232,"../../../plots/cartesian/helpers":233,"../../dragelement/helpers":70,"./constants":135,"./helpers":138}],140:[function(_dereq_,module,exports){
33060/**
33061* Copyright 2012-2020, Plotly, Inc.
33062* All rights reserved.
33063*
33064* This source code is licensed under the MIT license found in the
33065* LICENSE file in the root directory of this source tree.
33066*/
33067
33068
33069'use strict';
33070
33071var constants = _dereq_('./constants');
33072
33073var Lib = _dereq_('../../lib');
33074
33075// special position conversion functions... category axis positions can't be
33076// specified by their data values, because they don't make a continuous mapping.
33077// so these have to be specified in terms of the category serial numbers,
33078// but can take fractional values. Other axis types we specify position based on
33079// the actual data values.
33080// TODO: in V2.0 (when log axis ranges are in data units) range and shape position
33081// will be identical, so rangeToShapePosition and shapePositionToRange can be
33082// removed entirely.
33083
33084exports.rangeToShapePosition = function(ax) {
33085 return (ax.type === 'log') ? ax.r2d : function(v) { return v; };
33086};
33087
33088exports.shapePositionToRange = function(ax) {
33089 return (ax.type === 'log') ? ax.d2r : function(v) { return v; };
33090};
33091
33092exports.decodeDate = function(convertToPx) {
33093 return function(v) {
33094 if(v.replace) v = v.replace('_', ' ');
33095 return convertToPx(v);
33096 };
33097};
33098
33099exports.encodeDate = function(convertToDate) {
33100 return function(v) { return convertToDate(v).replace(' ', '_'); };
33101};
33102
33103exports.extractPathCoords = function(path, paramsToUse) {
33104 var extractedCoordinates = [];
33105
33106 var segments = path.match(constants.segmentRE);
33107 segments.forEach(function(segment) {
33108 var relevantParamIdx = paramsToUse[segment.charAt(0)].drawn;
33109 if(relevantParamIdx === undefined) return;
33110
33111 var params = segment.substr(1).match(constants.paramRE);
33112 if(!params || params.length < relevantParamIdx) return;
33113
33114 extractedCoordinates.push(Lib.cleanNumber(params[relevantParamIdx]));
33115 });
33116
33117 return extractedCoordinates;
33118};
33119
33120exports.getDataToPixel = function(gd, axis, isVertical) {
33121 var gs = gd._fullLayout._size;
33122 var dataToPixel;
33123
33124 if(axis) {
33125 var d2r = exports.shapePositionToRange(axis);
33126
33127 dataToPixel = function(v) {
33128 return axis._offset + axis.r2p(d2r(v, true));
33129 };
33130
33131 if(axis.type === 'date') dataToPixel = exports.decodeDate(dataToPixel);
33132 } else if(isVertical) {
33133 dataToPixel = function(v) { return gs.t + gs.h * (1 - v); };
33134 } else {
33135 dataToPixel = function(v) { return gs.l + gs.w * v; };
33136 }
33137
33138 return dataToPixel;
33139};
33140
33141exports.getPixelToData = function(gd, axis, isVertical) {
33142 var gs = gd._fullLayout._size;
33143 var pixelToData;
33144
33145 if(axis) {
33146 var r2d = exports.rangeToShapePosition(axis);
33147 pixelToData = function(p) { return r2d(axis.p2r(p - axis._offset)); };
33148 } else if(isVertical) {
33149 pixelToData = function(p) { return 1 - (p - gs.t) / gs.h; };
33150 } else {
33151 pixelToData = function(p) { return (p - gs.l) / gs.w; };
33152 }
33153
33154 return pixelToData;
33155};
33156
33157/**
33158 * Based on the given stroke width, rounds the passed
33159 * position value to represent either a full or half pixel.
33160 *
33161 * In case of an odd stroke width (e.g. 1), this measure ensures
33162 * that a stroke positioned at the returned position isn't rendered
33163 * blurry due to anti-aliasing.
33164 *
33165 * In case of an even stroke width (e.g. 2), this measure ensures
33166 * that the position value is transformed to a full pixel value
33167 * so that anti-aliasing doesn't take effect either.
33168 *
33169 * @param {number} pos The raw position value to be transformed
33170 * @param {number} strokeWidth The stroke width
33171 * @returns {number} either an integer or a .5 decimal number
33172 */
33173exports.roundPositionForSharpStrokeRendering = function(pos, strokeWidth) {
33174 var strokeWidthIsOdd = Math.round(strokeWidth % 2) === 1;
33175 var posValAsInt = Math.round(pos);
33176
33177 return strokeWidthIsOdd ? posValAsInt + 0.5 : posValAsInt;
33178};
33179
33180exports.makeOptionsAndPlotinfo = function(gd, index) {
33181 var options = gd._fullLayout.shapes[index] || {};
33182
33183 var plotinfo = gd._fullLayout._plots[options.xref + options.yref];
33184 var hasPlotinfo = !!plotinfo;
33185 if(hasPlotinfo) {
33186 plotinfo._hadPlotinfo = true;
33187 } else {
33188 plotinfo = {};
33189 if(options.xref && options.xref !== 'paper') plotinfo.xaxis = gd._fullLayout[options.xref + 'axis'];
33190 if(options.yref && options.yref !== 'paper') plotinfo.yaxis = gd._fullLayout[options.yref + 'axis'];
33191 }
33192
33193 plotinfo.xsizemode = options.xsizemode;
33194 plotinfo.ysizemode = options.ysizemode;
33195 plotinfo.xanchor = options.xanchor;
33196 plotinfo.yanchor = options.yanchor;
33197
33198 return {
33199 options: options,
33200 plotinfo: plotinfo
33201 };
33202};
33203
33204},{"../../lib":178,"./constants":131}],141:[function(_dereq_,module,exports){
33205/**
33206* Copyright 2012-2020, Plotly, Inc.
33207* All rights reserved.
33208*
33209* This source code is licensed under the MIT license found in the
33210* LICENSE file in the root directory of this source tree.
33211*/
33212
33213
33214'use strict';
33215
33216var drawModule = _dereq_('./draw');
33217
33218module.exports = {
33219 moduleType: 'component',
33220 name: 'shapes',
33221
33222 layoutAttributes: _dereq_('./attributes'),
33223 supplyLayoutDefaults: _dereq_('./defaults'),
33224 supplyDrawNewShapeDefaults: _dereq_('./draw_newshape/defaults'),
33225 includeBasePlot: _dereq_('../../plots/cartesian/include_components')('shapes'),
33226
33227 calcAutorange: _dereq_('./calc_autorange'),
33228 draw: drawModule.draw,
33229 drawOne: drawModule.drawOne
33230};
33231
33232},{"../../plots/cartesian/include_components":234,"./attributes":129,"./calc_autorange":130,"./defaults":132,"./draw":133,"./draw_newshape/defaults":136}],142:[function(_dereq_,module,exports){
33233/**
33234* Copyright 2012-2020, Plotly, Inc.
33235* All rights reserved.
33236*
33237* This source code is licensed under the MIT license found in the
33238* LICENSE file in the root directory of this source tree.
33239*/
33240
33241'use strict';
33242
33243var fontAttrs = _dereq_('../../plots/font_attributes');
33244var padAttrs = _dereq_('../../plots/pad_attributes');
33245var extendDeepAll = _dereq_('../../lib/extend').extendDeepAll;
33246var overrideAll = _dereq_('../../plot_api/edit_types').overrideAll;
33247var animationAttrs = _dereq_('../../plots/animation_attributes');
33248var templatedArray = _dereq_('../../plot_api/plot_template').templatedArray;
33249var constants = _dereq_('./constants');
33250
33251var stepsAttrs = templatedArray('step', {
33252 visible: {
33253 valType: 'boolean',
33254
33255 dflt: true,
33256
33257 },
33258 method: {
33259 valType: 'enumerated',
33260 values: ['restyle', 'relayout', 'animate', 'update', 'skip'],
33261 dflt: 'restyle',
33262
33263
33264 },
33265 args: {
33266 valType: 'info_array',
33267
33268 freeLength: true,
33269 items: [
33270 { valType: 'any' },
33271 { valType: 'any' },
33272 { valType: 'any' }
33273 ],
33274
33275 },
33276 label: {
33277 valType: 'string',
33278
33279
33280 },
33281 value: {
33282 valType: 'string',
33283
33284
33285 },
33286 execute: {
33287 valType: 'boolean',
33288
33289 dflt: true,
33290
33291 }
33292});
33293
33294module.exports = overrideAll(templatedArray('slider', {
33295 visible: {
33296 valType: 'boolean',
33297
33298 dflt: true,
33299
33300 },
33301
33302 active: {
33303 valType: 'number',
33304
33305 min: 0,
33306 dflt: 0,
33307
33308 },
33309
33310 steps: stepsAttrs,
33311
33312 lenmode: {
33313 valType: 'enumerated',
33314 values: ['fraction', 'pixels'],
33315
33316 dflt: 'fraction',
33317
33318 },
33319 len: {
33320 valType: 'number',
33321 min: 0,
33322 dflt: 1,
33323
33324
33325 },
33326 x: {
33327 valType: 'number',
33328 min: -2,
33329 max: 3,
33330 dflt: 0,
33331
33332
33333 },
33334 pad: extendDeepAll(padAttrs({editType: 'arraydraw'}), {
33335
33336 }, {t: {dflt: 20}}),
33337 xanchor: {
33338 valType: 'enumerated',
33339 values: ['auto', 'left', 'center', 'right'],
33340 dflt: 'left',
33341
33342
33343 },
33344 y: {
33345 valType: 'number',
33346 min: -2,
33347 max: 3,
33348 dflt: 0,
33349
33350
33351 },
33352 yanchor: {
33353 valType: 'enumerated',
33354 values: ['auto', 'top', 'middle', 'bottom'],
33355 dflt: 'top',
33356
33357
33358 },
33359
33360 transition: {
33361 duration: {
33362 valType: 'number',
33363
33364 min: 0,
33365 dflt: 150,
33366
33367 },
33368 easing: {
33369 valType: 'enumerated',
33370 values: animationAttrs.transition.easing.values,
33371
33372 dflt: 'cubic-in-out',
33373
33374 }
33375 },
33376
33377 currentvalue: {
33378 visible: {
33379 valType: 'boolean',
33380
33381 dflt: true,
33382
33383 },
33384
33385 xanchor: {
33386 valType: 'enumerated',
33387 values: ['left', 'center', 'right'],
33388 dflt: 'left',
33389
33390
33391 },
33392
33393 offset: {
33394 valType: 'number',
33395 dflt: 10,
33396
33397
33398 },
33399
33400 prefix: {
33401 valType: 'string',
33402
33403
33404 },
33405
33406 suffix: {
33407 valType: 'string',
33408
33409
33410 },
33411
33412 font: fontAttrs({
33413
33414 })
33415 },
33416
33417 font: fontAttrs({
33418
33419 }),
33420
33421 activebgcolor: {
33422 valType: 'color',
33423
33424 dflt: constants.gripBgActiveColor,
33425
33426 },
33427 bgcolor: {
33428 valType: 'color',
33429
33430 dflt: constants.railBgColor,
33431
33432 },
33433 bordercolor: {
33434 valType: 'color',
33435 dflt: constants.railBorderColor,
33436
33437
33438 },
33439 borderwidth: {
33440 valType: 'number',
33441 min: 0,
33442 dflt: constants.railBorderWidth,
33443
33444
33445 },
33446 ticklen: {
33447 valType: 'number',
33448 min: 0,
33449 dflt: constants.tickLength,
33450
33451
33452 },
33453 tickcolor: {
33454 valType: 'color',
33455 dflt: constants.tickColor,
33456
33457
33458 },
33459 tickwidth: {
33460 valType: 'number',
33461 min: 0,
33462 dflt: 1,
33463
33464
33465 },
33466 minorticklen: {
33467 valType: 'number',
33468 min: 0,
33469 dflt: constants.minorTickLength,
33470
33471
33472 }
33473}), 'arraydraw', 'from-root');
33474
33475},{"../../lib/extend":173,"../../plot_api/edit_types":205,"../../plot_api/plot_template":212,"../../plots/animation_attributes":217,"../../plots/font_attributes":250,"../../plots/pad_attributes":255,"./constants":143}],143:[function(_dereq_,module,exports){
33476/**
33477* Copyright 2012-2020, Plotly, Inc.
33478* All rights reserved.
33479*
33480* This source code is licensed under the MIT license found in the
33481* LICENSE file in the root directory of this source tree.
33482*/
33483
33484
33485'use strict';
33486
33487
33488module.exports = {
33489
33490 // layout attribute name
33491 name: 'sliders',
33492
33493 // class names
33494 containerClassName: 'slider-container',
33495 groupClassName: 'slider-group',
33496 inputAreaClass: 'slider-input-area',
33497 railRectClass: 'slider-rail-rect',
33498 railTouchRectClass: 'slider-rail-touch-rect',
33499 gripRectClass: 'slider-grip-rect',
33500 tickRectClass: 'slider-tick-rect',
33501 inputProxyClass: 'slider-input-proxy',
33502 labelsClass: 'slider-labels',
33503 labelGroupClass: 'slider-label-group',
33504 labelClass: 'slider-label',
33505 currentValueClass: 'slider-current-value',
33506
33507 railHeight: 5,
33508
33509 // DOM attribute name in button group keeping track
33510 // of active update menu
33511 menuIndexAttrName: 'slider-active-index',
33512
33513 // id root pass to Plots.autoMargin
33514 autoMarginIdRoot: 'slider-',
33515
33516 // min item width / height
33517 minWidth: 30,
33518 minHeight: 30,
33519
33520 // padding around item text
33521 textPadX: 40,
33522
33523 // arrow offset off right edge
33524 arrowOffsetX: 4,
33525
33526 railRadius: 2,
33527 railWidth: 5,
33528 railBorder: 4,
33529 railBorderWidth: 1,
33530 railBorderColor: '#bec8d9',
33531 railBgColor: '#f8fafc',
33532
33533 // The distance of the rail from the edge of the touchable area
33534 // Slightly less than the step inset because of the curved edges
33535 // of the rail
33536 railInset: 8,
33537
33538 // The distance from the extremal tick marks to the edge of the
33539 // touchable area. This is basically the same as the grip radius,
33540 // but for other styles it wouldn't really need to be.
33541 stepInset: 10,
33542
33543 gripRadius: 10,
33544 gripWidth: 20,
33545 gripHeight: 20,
33546 gripBorder: 20,
33547 gripBorderWidth: 1,
33548 gripBorderColor: '#bec8d9',
33549 gripBgColor: '#f6f8fa',
33550 gripBgActiveColor: '#dbdde0',
33551
33552 labelPadding: 8,
33553 labelOffset: 0,
33554
33555 tickWidth: 1,
33556 tickColor: '#333',
33557 tickOffset: 25,
33558 tickLength: 7,
33559
33560 minorTickOffset: 25,
33561 minorTickColor: '#333',
33562 minorTickLength: 4,
33563
33564 // Extra space below the current value label:
33565 currentValuePadding: 8,
33566 currentValueInset: 0,
33567};
33568
33569},{}],144:[function(_dereq_,module,exports){
33570/**
33571* Copyright 2012-2020, Plotly, Inc.
33572* All rights reserved.
33573*
33574* This source code is licensed under the MIT license found in the
33575* LICENSE file in the root directory of this source tree.
33576*/
33577
33578'use strict';
33579
33580var Lib = _dereq_('../../lib');
33581var handleArrayContainerDefaults = _dereq_('../../plots/array_container_defaults');
33582
33583var attributes = _dereq_('./attributes');
33584var constants = _dereq_('./constants');
33585
33586var name = constants.name;
33587var stepAttrs = attributes.steps;
33588
33589
33590module.exports = function slidersDefaults(layoutIn, layoutOut) {
33591 handleArrayContainerDefaults(layoutIn, layoutOut, {
33592 name: name,
33593 handleItemDefaults: sliderDefaults
33594 });
33595};
33596
33597function sliderDefaults(sliderIn, sliderOut, layoutOut) {
33598 function coerce(attr, dflt) {
33599 return Lib.coerce(sliderIn, sliderOut, attributes, attr, dflt);
33600 }
33601
33602 var steps = handleArrayContainerDefaults(sliderIn, sliderOut, {
33603 name: 'steps',
33604 handleItemDefaults: stepDefaults
33605 });
33606
33607 var stepCount = 0;
33608 for(var i = 0; i < steps.length; i++) {
33609 if(steps[i].visible) stepCount++;
33610 }
33611
33612 var visible;
33613 // If it has fewer than two options, it's not really a slider
33614 if(stepCount < 2) visible = sliderOut.visible = false;
33615 else visible = coerce('visible');
33616 if(!visible) return;
33617
33618 sliderOut._stepCount = stepCount;
33619 var visSteps = sliderOut._visibleSteps = Lib.filterVisible(steps);
33620
33621 var active = coerce('active');
33622 if(!(steps[active] || {}).visible) sliderOut.active = visSteps[0]._index;
33623
33624 coerce('x');
33625 coerce('y');
33626 Lib.noneOrAll(sliderIn, sliderOut, ['x', 'y']);
33627
33628 coerce('xanchor');
33629 coerce('yanchor');
33630
33631 coerce('len');
33632 coerce('lenmode');
33633
33634 coerce('pad.t');
33635 coerce('pad.r');
33636 coerce('pad.b');
33637 coerce('pad.l');
33638
33639 Lib.coerceFont(coerce, 'font', layoutOut.font);
33640
33641 var currentValueIsVisible = coerce('currentvalue.visible');
33642
33643 if(currentValueIsVisible) {
33644 coerce('currentvalue.xanchor');
33645 coerce('currentvalue.prefix');
33646 coerce('currentvalue.suffix');
33647 coerce('currentvalue.offset');
33648
33649 Lib.coerceFont(coerce, 'currentvalue.font', sliderOut.font);
33650 }
33651
33652 coerce('transition.duration');
33653 coerce('transition.easing');
33654
33655 coerce('bgcolor');
33656 coerce('activebgcolor');
33657 coerce('bordercolor');
33658 coerce('borderwidth');
33659 coerce('ticklen');
33660 coerce('tickwidth');
33661 coerce('tickcolor');
33662 coerce('minorticklen');
33663}
33664
33665function stepDefaults(valueIn, valueOut) {
33666 function coerce(attr, dflt) {
33667 return Lib.coerce(valueIn, valueOut, stepAttrs, attr, dflt);
33668 }
33669
33670 var visible;
33671 if(valueIn.method !== 'skip' && !Array.isArray(valueIn.args)) {
33672 visible = valueOut.visible = false;
33673 } else visible = coerce('visible');
33674
33675 if(visible) {
33676 coerce('method');
33677 coerce('args');
33678 var label = coerce('label', 'step-' + valueOut._index);
33679 coerce('value', label);
33680 coerce('execute');
33681 }
33682}
33683
33684},{"../../lib":178,"../../plots/array_container_defaults":218,"./attributes":142,"./constants":143}],145:[function(_dereq_,module,exports){
33685/**
33686* Copyright 2012-2020, Plotly, Inc.
33687* All rights reserved.
33688*
33689* This source code is licensed under the MIT license found in the
33690* LICENSE file in the root directory of this source tree.
33691*/
33692
33693'use strict';
33694
33695var d3 = _dereq_('d3');
33696
33697var Plots = _dereq_('../../plots/plots');
33698var Color = _dereq_('../color');
33699var Drawing = _dereq_('../drawing');
33700var Lib = _dereq_('../../lib');
33701var svgTextUtils = _dereq_('../../lib/svg_text_utils');
33702var arrayEditor = _dereq_('../../plot_api/plot_template').arrayEditor;
33703
33704var constants = _dereq_('./constants');
33705var alignmentConstants = _dereq_('../../constants/alignment');
33706var LINE_SPACING = alignmentConstants.LINE_SPACING;
33707var FROM_TL = alignmentConstants.FROM_TL;
33708var FROM_BR = alignmentConstants.FROM_BR;
33709
33710module.exports = function draw(gd) {
33711 var fullLayout = gd._fullLayout;
33712 var sliderData = makeSliderData(fullLayout, gd);
33713
33714 // draw a container for *all* sliders:
33715 var sliders = fullLayout._infolayer
33716 .selectAll('g.' + constants.containerClassName)
33717 .data(sliderData.length > 0 ? [0] : []);
33718
33719 sliders.enter().append('g')
33720 .classed(constants.containerClassName, true)
33721 .style('cursor', 'ew-resize');
33722
33723 function clearSlider(sliderOpts) {
33724 if(sliderOpts._commandObserver) {
33725 sliderOpts._commandObserver.remove();
33726 delete sliderOpts._commandObserver;
33727 }
33728
33729 // Most components don't need to explicitly remove autoMargin, because
33730 // marginPushers does this - but slider updates don't go through
33731 // a full replot so we need to explicitly remove it.
33732 Plots.autoMargin(gd, autoMarginId(sliderOpts));
33733 }
33734
33735 sliders.exit().each(function() {
33736 d3.select(this).selectAll('g.' + constants.groupClassName)
33737 .each(clearSlider);
33738 })
33739 .remove();
33740
33741 // Return early if no menus visible:
33742 if(sliderData.length === 0) return;
33743
33744 var sliderGroups = sliders.selectAll('g.' + constants.groupClassName)
33745 .data(sliderData, keyFunction);
33746
33747 sliderGroups.enter().append('g')
33748 .classed(constants.groupClassName, true);
33749
33750 sliderGroups.exit()
33751 .each(clearSlider)
33752 .remove();
33753
33754 // Find the dimensions of the sliders:
33755 for(var i = 0; i < sliderData.length; i++) {
33756 var sliderOpts = sliderData[i];
33757 findDimensions(gd, sliderOpts);
33758 }
33759
33760 sliderGroups.each(function(sliderOpts) {
33761 var gSlider = d3.select(this);
33762
33763 computeLabelSteps(sliderOpts);
33764
33765 Plots.manageCommandObserver(gd, sliderOpts, sliderOpts._visibleSteps, function(data) {
33766 // NB: Same as below. This is *not* always the same as sliderOpts since
33767 // if a new set of steps comes in, the reference in this callback would
33768 // be invalid. We need to refetch it from the slider group, which is
33769 // the join data that creates this slider. So if this slider still exists,
33770 // the group should be valid, *to the best of my knowledge.* If not,
33771 // we'd have to look it up by d3 data join index/key.
33772 var opts = gSlider.data()[0];
33773
33774 if(opts.active === data.index) return;
33775 if(opts._dragging) return;
33776
33777 setActive(gd, gSlider, opts, data.index, false, true);
33778 });
33779
33780 drawSlider(gd, d3.select(this), sliderOpts);
33781 });
33782};
33783
33784function autoMarginId(sliderOpts) {
33785 return constants.autoMarginIdRoot + sliderOpts._index;
33786}
33787
33788// This really only just filters by visibility:
33789function makeSliderData(fullLayout, gd) {
33790 var contOpts = fullLayout[constants.name];
33791 var sliderData = [];
33792
33793 for(var i = 0; i < contOpts.length; i++) {
33794 var item = contOpts[i];
33795 if(!item.visible) continue;
33796 item._gd = gd;
33797 sliderData.push(item);
33798 }
33799
33800 return sliderData;
33801}
33802
33803// This is set in the defaults step:
33804function keyFunction(opts) {
33805 return opts._index;
33806}
33807
33808// Compute the dimensions (mutates sliderOpts):
33809function findDimensions(gd, sliderOpts) {
33810 var sliderLabels = Drawing.tester.selectAll('g.' + constants.labelGroupClass)
33811 .data(sliderOpts._visibleSteps);
33812
33813 sliderLabels.enter().append('g')
33814 .classed(constants.labelGroupClass, true);
33815
33816 // loop over fake buttons to find width / height
33817 var maxLabelWidth = 0;
33818 var labelHeight = 0;
33819 sliderLabels.each(function(stepOpts) {
33820 var labelGroup = d3.select(this);
33821
33822 var text = drawLabel(labelGroup, {step: stepOpts}, sliderOpts);
33823
33824 var textNode = text.node();
33825 if(textNode) {
33826 var bBox = Drawing.bBox(textNode);
33827 labelHeight = Math.max(labelHeight, bBox.height);
33828 maxLabelWidth = Math.max(maxLabelWidth, bBox.width);
33829 }
33830 });
33831
33832 sliderLabels.remove();
33833
33834 var dims = sliderOpts._dims = {};
33835
33836 dims.inputAreaWidth = Math.max(
33837 constants.railWidth,
33838 constants.gripHeight
33839 );
33840
33841 // calculate some overall dimensions - some of these are needed for
33842 // calculating the currentValue dimensions
33843 var graphSize = gd._fullLayout._size;
33844 dims.lx = graphSize.l + graphSize.w * sliderOpts.x;
33845 dims.ly = graphSize.t + graphSize.h * (1 - sliderOpts.y);
33846
33847 if(sliderOpts.lenmode === 'fraction') {
33848 // fraction:
33849 dims.outerLength = Math.round(graphSize.w * sliderOpts.len);
33850 } else {
33851 // pixels:
33852 dims.outerLength = sliderOpts.len;
33853 }
33854
33855 // The length of the rail, *excluding* padding on either end:
33856 dims.inputAreaStart = 0;
33857 dims.inputAreaLength = Math.round(dims.outerLength - sliderOpts.pad.l - sliderOpts.pad.r);
33858
33859 var textableInputLength = dims.inputAreaLength - 2 * constants.stepInset;
33860 var availableSpacePerLabel = textableInputLength / (sliderOpts._stepCount - 1);
33861 var computedSpacePerLabel = maxLabelWidth + constants.labelPadding;
33862 dims.labelStride = Math.max(1, Math.ceil(computedSpacePerLabel / availableSpacePerLabel));
33863 dims.labelHeight = labelHeight;
33864
33865 // loop over all possible values for currentValue to find the
33866 // area we need for it
33867 dims.currentValueMaxWidth = 0;
33868 dims.currentValueHeight = 0;
33869 dims.currentValueTotalHeight = 0;
33870 dims.currentValueMaxLines = 1;
33871
33872 if(sliderOpts.currentvalue.visible) {
33873 // Get the dimensions of the current value label:
33874 var dummyGroup = Drawing.tester.append('g');
33875
33876 sliderLabels.each(function(stepOpts) {
33877 var curValPrefix = drawCurrentValue(dummyGroup, sliderOpts, stepOpts.label);
33878 var curValSize = (curValPrefix.node() && Drawing.bBox(curValPrefix.node())) || {width: 0, height: 0};
33879 var lines = svgTextUtils.lineCount(curValPrefix);
33880 dims.currentValueMaxWidth = Math.max(dims.currentValueMaxWidth, Math.ceil(curValSize.width));
33881 dims.currentValueHeight = Math.max(dims.currentValueHeight, Math.ceil(curValSize.height));
33882 dims.currentValueMaxLines = Math.max(dims.currentValueMaxLines, lines);
33883 });
33884
33885 dims.currentValueTotalHeight = dims.currentValueHeight + sliderOpts.currentvalue.offset;
33886
33887 dummyGroup.remove();
33888 }
33889
33890 dims.height = dims.currentValueTotalHeight + constants.tickOffset + sliderOpts.ticklen + constants.labelOffset + dims.labelHeight + sliderOpts.pad.t + sliderOpts.pad.b;
33891
33892 var xanchor = 'left';
33893 if(Lib.isRightAnchor(sliderOpts)) {
33894 dims.lx -= dims.outerLength;
33895 xanchor = 'right';
33896 }
33897 if(Lib.isCenterAnchor(sliderOpts)) {
33898 dims.lx -= dims.outerLength / 2;
33899 xanchor = 'center';
33900 }
33901
33902 var yanchor = 'top';
33903 if(Lib.isBottomAnchor(sliderOpts)) {
33904 dims.ly -= dims.height;
33905 yanchor = 'bottom';
33906 }
33907 if(Lib.isMiddleAnchor(sliderOpts)) {
33908 dims.ly -= dims.height / 2;
33909 yanchor = 'middle';
33910 }
33911
33912 dims.outerLength = Math.ceil(dims.outerLength);
33913 dims.height = Math.ceil(dims.height);
33914 dims.lx = Math.round(dims.lx);
33915 dims.ly = Math.round(dims.ly);
33916
33917 var marginOpts = {
33918 y: sliderOpts.y,
33919 b: dims.height * FROM_BR[yanchor],
33920 t: dims.height * FROM_TL[yanchor]
33921 };
33922
33923 if(sliderOpts.lenmode === 'fraction') {
33924 marginOpts.l = 0;
33925 marginOpts.xl = sliderOpts.x - sliderOpts.len * FROM_TL[xanchor];
33926 marginOpts.r = 0;
33927 marginOpts.xr = sliderOpts.x + sliderOpts.len * FROM_BR[xanchor];
33928 } else {
33929 marginOpts.x = sliderOpts.x;
33930 marginOpts.l = dims.outerLength * FROM_TL[xanchor];
33931 marginOpts.r = dims.outerLength * FROM_BR[xanchor];
33932 }
33933
33934 Plots.autoMargin(gd, autoMarginId(sliderOpts), marginOpts);
33935}
33936
33937function drawSlider(gd, sliderGroup, sliderOpts) {
33938 // This is related to the other long notes in this file regarding what happens
33939 // when slider steps disappear. This particular fix handles what happens when
33940 // the *current* slider step is removed. The drawing functions will error out
33941 // when they fail to find it, so the fix for now is that it will just draw the
33942 // slider in the first position but will not execute the command.
33943 if(!((sliderOpts.steps[sliderOpts.active] || {}).visible)) {
33944 sliderOpts.active = sliderOpts._visibleSteps[0]._index;
33945 }
33946
33947 // These are carefully ordered for proper z-ordering:
33948 sliderGroup
33949 .call(drawCurrentValue, sliderOpts)
33950 .call(drawRail, sliderOpts)
33951 .call(drawLabelGroup, sliderOpts)
33952 .call(drawTicks, sliderOpts)
33953 .call(drawTouchRect, gd, sliderOpts)
33954 .call(drawGrip, gd, sliderOpts);
33955
33956 var dims = sliderOpts._dims;
33957
33958 // Position the rectangle:
33959 Drawing.setTranslate(sliderGroup, dims.lx + sliderOpts.pad.l, dims.ly + sliderOpts.pad.t);
33960
33961 sliderGroup.call(setGripPosition, sliderOpts, false);
33962 sliderGroup.call(drawCurrentValue, sliderOpts);
33963}
33964
33965function drawCurrentValue(sliderGroup, sliderOpts, valueOverride) {
33966 if(!sliderOpts.currentvalue.visible) return;
33967
33968 var dims = sliderOpts._dims;
33969 var x0, textAnchor;
33970
33971 switch(sliderOpts.currentvalue.xanchor) {
33972 case 'right':
33973 // This is anchored left and adjusted by the width of the longest label
33974 // so that the prefix doesn't move. The goal of this is to emphasize
33975 // what's actually changing and make the update less distracting.
33976 x0 = dims.inputAreaLength - constants.currentValueInset - dims.currentValueMaxWidth;
33977 textAnchor = 'left';
33978 break;
33979 case 'center':
33980 x0 = dims.inputAreaLength * 0.5;
33981 textAnchor = 'middle';
33982 break;
33983 default:
33984 x0 = constants.currentValueInset;
33985 textAnchor = 'left';
33986 }
33987
33988 var text = Lib.ensureSingle(sliderGroup, 'text', constants.labelClass, function(s) {
33989 s.classed('user-select-none', true)
33990 .attr({
33991 'text-anchor': textAnchor,
33992 'data-notex': 1
33993 });
33994 });
33995
33996 var str = sliderOpts.currentvalue.prefix ? sliderOpts.currentvalue.prefix : '';
33997
33998 if(typeof valueOverride === 'string') {
33999 str += valueOverride;
34000 } else {
34001 var curVal = sliderOpts.steps[sliderOpts.active].label;
34002 var _meta = sliderOpts._gd._fullLayout._meta;
34003 if(_meta) curVal = Lib.templateString(curVal, _meta);
34004 str += curVal;
34005 }
34006
34007 if(sliderOpts.currentvalue.suffix) {
34008 str += sliderOpts.currentvalue.suffix;
34009 }
34010
34011 text.call(Drawing.font, sliderOpts.currentvalue.font)
34012 .text(str)
34013 .call(svgTextUtils.convertToTspans, sliderOpts._gd);
34014
34015 var lines = svgTextUtils.lineCount(text);
34016
34017 var y0 = (dims.currentValueMaxLines + 1 - lines) *
34018 sliderOpts.currentvalue.font.size * LINE_SPACING;
34019
34020 svgTextUtils.positionText(text, x0, y0);
34021
34022 return text;
34023}
34024
34025function drawGrip(sliderGroup, gd, sliderOpts) {
34026 var grip = Lib.ensureSingle(sliderGroup, 'rect', constants.gripRectClass, function(s) {
34027 s.call(attachGripEvents, gd, sliderGroup, sliderOpts)
34028 .style('pointer-events', 'all');
34029 });
34030
34031 grip.attr({
34032 width: constants.gripWidth,
34033 height: constants.gripHeight,
34034 rx: constants.gripRadius,
34035 ry: constants.gripRadius,
34036 })
34037 .call(Color.stroke, sliderOpts.bordercolor)
34038 .call(Color.fill, sliderOpts.bgcolor)
34039 .style('stroke-width', sliderOpts.borderwidth + 'px');
34040}
34041
34042function drawLabel(item, data, sliderOpts) {
34043 var text = Lib.ensureSingle(item, 'text', constants.labelClass, function(s) {
34044 s.classed('user-select-none', true)
34045 .attr({
34046 'text-anchor': 'middle',
34047 'data-notex': 1
34048 });
34049 });
34050
34051 var tx = data.step.label;
34052 var _meta = sliderOpts._gd._fullLayout._meta;
34053 if(_meta) tx = Lib.templateString(tx, _meta);
34054
34055 text.call(Drawing.font, sliderOpts.font)
34056 .text(tx)
34057 .call(svgTextUtils.convertToTspans, sliderOpts._gd);
34058
34059 return text;
34060}
34061
34062function drawLabelGroup(sliderGroup, sliderOpts) {
34063 var labels = Lib.ensureSingle(sliderGroup, 'g', constants.labelsClass);
34064 var dims = sliderOpts._dims;
34065
34066 var labelItems = labels.selectAll('g.' + constants.labelGroupClass)
34067 .data(dims.labelSteps);
34068
34069 labelItems.enter().append('g')
34070 .classed(constants.labelGroupClass, true);
34071
34072 labelItems.exit().remove();
34073
34074 labelItems.each(function(d) {
34075 var item = d3.select(this);
34076
34077 item.call(drawLabel, d, sliderOpts);
34078
34079 Drawing.setTranslate(item,
34080 normalizedValueToPosition(sliderOpts, d.fraction),
34081 constants.tickOffset +
34082 sliderOpts.ticklen +
34083 // position is the baseline of the top line of text only, even
34084 // if the label spans multiple lines
34085 sliderOpts.font.size * LINE_SPACING +
34086 constants.labelOffset +
34087 dims.currentValueTotalHeight
34088 );
34089 });
34090}
34091
34092function handleInput(gd, sliderGroup, sliderOpts, normalizedPosition, doTransition) {
34093 var quantizedPosition = Math.round(normalizedPosition * (sliderOpts._stepCount - 1));
34094 var quantizedIndex = sliderOpts._visibleSteps[quantizedPosition]._index;
34095
34096 if(quantizedIndex !== sliderOpts.active) {
34097 setActive(gd, sliderGroup, sliderOpts, quantizedIndex, true, doTransition);
34098 }
34099}
34100
34101function setActive(gd, sliderGroup, sliderOpts, index, doCallback, doTransition) {
34102 var previousActive = sliderOpts.active;
34103 sliderOpts.active = index;
34104
34105 // due to templating, it's possible this slider doesn't even exist yet
34106 arrayEditor(gd.layout, constants.name, sliderOpts)
34107 .applyUpdate('active', index);
34108
34109 var step = sliderOpts.steps[sliderOpts.active];
34110
34111 sliderGroup.call(setGripPosition, sliderOpts, doTransition);
34112 sliderGroup.call(drawCurrentValue, sliderOpts);
34113
34114 gd.emit('plotly_sliderchange', {
34115 slider: sliderOpts,
34116 step: sliderOpts.steps[sliderOpts.active],
34117 interaction: doCallback,
34118 previousActive: previousActive
34119 });
34120
34121 if(step && step.method && doCallback) {
34122 if(sliderGroup._nextMethod) {
34123 // If we've already queued up an update, just overwrite it with the most recent:
34124 sliderGroup._nextMethod.step = step;
34125 sliderGroup._nextMethod.doCallback = doCallback;
34126 sliderGroup._nextMethod.doTransition = doTransition;
34127 } else {
34128 sliderGroup._nextMethod = {step: step, doCallback: doCallback, doTransition: doTransition};
34129 sliderGroup._nextMethodRaf = window.requestAnimationFrame(function() {
34130 var _step = sliderGroup._nextMethod.step;
34131 if(!_step.method) return;
34132
34133 if(_step.execute) {
34134 Plots.executeAPICommand(gd, _step.method, _step.args);
34135 }
34136
34137 sliderGroup._nextMethod = null;
34138 sliderGroup._nextMethodRaf = null;
34139 });
34140 }
34141 }
34142}
34143
34144function attachGripEvents(item, gd, sliderGroup) {
34145 var node = sliderGroup.node();
34146 var $gd = d3.select(gd);
34147
34148 // NB: This is *not* the same as sliderOpts itself! These callbacks
34149 // are in a closure so this array won't actually be correct if the
34150 // steps have changed since this was initialized. The sliderGroup,
34151 // however, has not changed since that *is* the slider, so it must
34152 // be present to receive mouse events.
34153 function getSliderOpts() {
34154 return sliderGroup.data()[0];
34155 }
34156
34157 item.on('mousedown', function() {
34158 var sliderOpts = getSliderOpts();
34159 gd.emit('plotly_sliderstart', {slider: sliderOpts});
34160
34161 var grip = sliderGroup.select('.' + constants.gripRectClass);
34162
34163 d3.event.stopPropagation();
34164 d3.event.preventDefault();
34165 grip.call(Color.fill, sliderOpts.activebgcolor);
34166
34167 var normalizedPosition = positionToNormalizedValue(sliderOpts, d3.mouse(node)[0]);
34168 handleInput(gd, sliderGroup, sliderOpts, normalizedPosition, true);
34169 sliderOpts._dragging = true;
34170
34171 $gd.on('mousemove', function() {
34172 var sliderOpts = getSliderOpts();
34173 var normalizedPosition = positionToNormalizedValue(sliderOpts, d3.mouse(node)[0]);
34174 handleInput(gd, sliderGroup, sliderOpts, normalizedPosition, false);
34175 });
34176
34177 $gd.on('mouseup', function() {
34178 var sliderOpts = getSliderOpts();
34179 sliderOpts._dragging = false;
34180 grip.call(Color.fill, sliderOpts.bgcolor);
34181 $gd.on('mouseup', null);
34182 $gd.on('mousemove', null);
34183
34184 gd.emit('plotly_sliderend', {
34185 slider: sliderOpts,
34186 step: sliderOpts.steps[sliderOpts.active]
34187 });
34188 });
34189 });
34190}
34191
34192function drawTicks(sliderGroup, sliderOpts) {
34193 var tick = sliderGroup.selectAll('rect.' + constants.tickRectClass)
34194 .data(sliderOpts._visibleSteps);
34195 var dims = sliderOpts._dims;
34196
34197 tick.enter().append('rect')
34198 .classed(constants.tickRectClass, true);
34199
34200 tick.exit().remove();
34201
34202 tick.attr({
34203 width: sliderOpts.tickwidth + 'px',
34204 'shape-rendering': 'crispEdges'
34205 });
34206
34207 tick.each(function(d, i) {
34208 var isMajor = i % dims.labelStride === 0;
34209 var item = d3.select(this);
34210
34211 item
34212 .attr({height: isMajor ? sliderOpts.ticklen : sliderOpts.minorticklen})
34213 .call(Color.fill, isMajor ? sliderOpts.tickcolor : sliderOpts.tickcolor);
34214
34215 Drawing.setTranslate(item,
34216 normalizedValueToPosition(sliderOpts, i / (sliderOpts._stepCount - 1)) - 0.5 * sliderOpts.tickwidth,
34217 (isMajor ? constants.tickOffset : constants.minorTickOffset) + dims.currentValueTotalHeight
34218 );
34219 });
34220}
34221
34222function computeLabelSteps(sliderOpts) {
34223 var dims = sliderOpts._dims;
34224 dims.labelSteps = [];
34225 var nsteps = sliderOpts._stepCount;
34226
34227 for(var i = 0; i < nsteps; i += dims.labelStride) {
34228 dims.labelSteps.push({
34229 fraction: i / (nsteps - 1),
34230 step: sliderOpts._visibleSteps[i]
34231 });
34232 }
34233}
34234
34235function setGripPosition(sliderGroup, sliderOpts, doTransition) {
34236 var grip = sliderGroup.select('rect.' + constants.gripRectClass);
34237
34238 var quantizedIndex = 0;
34239 for(var i = 0; i < sliderOpts._stepCount; i++) {
34240 if(sliderOpts._visibleSteps[i]._index === sliderOpts.active) {
34241 quantizedIndex = i;
34242 break;
34243 }
34244 }
34245
34246 var x = normalizedValueToPosition(sliderOpts, quantizedIndex / (sliderOpts._stepCount - 1));
34247
34248 // If this is true, then *this component* is already invoking its own command
34249 // and has triggered its own animation.
34250 if(sliderOpts._invokingCommand) return;
34251
34252 var el = grip;
34253 if(doTransition && sliderOpts.transition.duration > 0) {
34254 el = el.transition()
34255 .duration(sliderOpts.transition.duration)
34256 .ease(sliderOpts.transition.easing);
34257 }
34258
34259 // Drawing.setTranslate doesn't work here becasue of the transition duck-typing.
34260 // It's also not necessary because there are no other transitions to preserve.
34261 el.attr('transform', 'translate(' + (x - constants.gripWidth * 0.5) + ',' + (sliderOpts._dims.currentValueTotalHeight) + ')');
34262}
34263
34264// Convert a number from [0-1] to a pixel position relative to the slider group container:
34265function normalizedValueToPosition(sliderOpts, normalizedPosition) {
34266 var dims = sliderOpts._dims;
34267 return dims.inputAreaStart + constants.stepInset +
34268 (dims.inputAreaLength - 2 * constants.stepInset) * Math.min(1, Math.max(0, normalizedPosition));
34269}
34270
34271// Convert a position relative to the slider group to a nubmer in [0, 1]
34272function positionToNormalizedValue(sliderOpts, position) {
34273 var dims = sliderOpts._dims;
34274 return Math.min(1, Math.max(0, (position - constants.stepInset - dims.inputAreaStart) / (dims.inputAreaLength - 2 * constants.stepInset - 2 * dims.inputAreaStart)));
34275}
34276
34277function drawTouchRect(sliderGroup, gd, sliderOpts) {
34278 var dims = sliderOpts._dims;
34279 var rect = Lib.ensureSingle(sliderGroup, 'rect', constants.railTouchRectClass, function(s) {
34280 s.call(attachGripEvents, gd, sliderGroup, sliderOpts)
34281 .style('pointer-events', 'all');
34282 });
34283
34284 rect.attr({
34285 width: dims.inputAreaLength,
34286 height: Math.max(dims.inputAreaWidth, constants.tickOffset + sliderOpts.ticklen + dims.labelHeight)
34287 })
34288 .call(Color.fill, sliderOpts.bgcolor)
34289 .attr('opacity', 0);
34290
34291 Drawing.setTranslate(rect, 0, dims.currentValueTotalHeight);
34292}
34293
34294function drawRail(sliderGroup, sliderOpts) {
34295 var dims = sliderOpts._dims;
34296 var computedLength = dims.inputAreaLength - constants.railInset * 2;
34297 var rect = Lib.ensureSingle(sliderGroup, 'rect', constants.railRectClass);
34298
34299 rect.attr({
34300 width: computedLength,
34301 height: constants.railWidth,
34302 rx: constants.railRadius,
34303 ry: constants.railRadius,
34304 'shape-rendering': 'crispEdges'
34305 })
34306 .call(Color.stroke, sliderOpts.bordercolor)
34307 .call(Color.fill, sliderOpts.bgcolor)
34308 .style('stroke-width', sliderOpts.borderwidth + 'px');
34309
34310 Drawing.setTranslate(rect,
34311 constants.railInset,
34312 (dims.inputAreaWidth - constants.railWidth) * 0.5 + dims.currentValueTotalHeight
34313 );
34314}
34315
34316},{"../../constants/alignment":154,"../../lib":178,"../../lib/svg_text_utils":199,"../../plot_api/plot_template":212,"../../plots/plots":256,"../color":52,"../drawing":74,"./constants":143,"d3":16}],146:[function(_dereq_,module,exports){
34317/**
34318* Copyright 2012-2020, Plotly, Inc.
34319* All rights reserved.
34320*
34321* This source code is licensed under the MIT license found in the
34322* LICENSE file in the root directory of this source tree.
34323*/
34324
34325'use strict';
34326
34327var constants = _dereq_('./constants');
34328
34329module.exports = {
34330 moduleType: 'component',
34331 name: constants.name,
34332
34333 layoutAttributes: _dereq_('./attributes'),
34334 supplyLayoutDefaults: _dereq_('./defaults'),
34335
34336 draw: _dereq_('./draw')
34337};
34338
34339},{"./attributes":142,"./constants":143,"./defaults":144,"./draw":145}],147:[function(_dereq_,module,exports){
34340/**
34341* Copyright 2012-2020, Plotly, Inc.
34342* All rights reserved.
34343*
34344* This source code is licensed under the MIT license found in the
34345* LICENSE file in the root directory of this source tree.
34346*/
34347
34348
34349'use strict';
34350
34351var d3 = _dereq_('d3');
34352var isNumeric = _dereq_('fast-isnumeric');
34353
34354var Plots = _dereq_('../../plots/plots');
34355var Registry = _dereq_('../../registry');
34356var Lib = _dereq_('../../lib');
34357var Drawing = _dereq_('../drawing');
34358var Color = _dereq_('../color');
34359var svgTextUtils = _dereq_('../../lib/svg_text_utils');
34360var interactConstants = _dereq_('../../constants/interactions');
34361
34362var OPPOSITE_SIDE = _dereq_('../../constants/alignment').OPPOSITE_SIDE;
34363var numStripRE = / [XY][0-9]* /;
34364
34365/**
34366 * Titles - (re)draw titles on the axes and plot:
34367 * @param {DOM element} gd - the graphDiv
34368 * @param {string} titleClass - the css class of this title
34369 * @param {object} options - how and what to draw
34370 * propContainer - the layout object containing `title` and `titlefont`
34371 * attributes that apply to this title
34372 * propName - the full name of the title property (for Plotly.relayout)
34373 * [traceIndex] - include only if this property applies to one trace
34374 * (such as a colorbar title) - then editing pipes to Plotly.restyle
34375 * instead of Plotly.relayout
34376 * placeholder - placeholder text for an empty editable title
34377 * [avoid] {object} - include if this title should move to avoid other elements
34378 * selection - d3 selection of elements to avoid
34379 * side - which direction to move if there is a conflict
34380 * [offsetLeft] - if these elements are subject to a translation
34381 * wrt the title element
34382 * [offsetTop]
34383 * attributes {object} - position and alignment attributes
34384 * x - pixels
34385 * y - pixels
34386 * text-anchor - start|middle|end
34387 * transform {object} - how to transform the title after positioning
34388 * rotate - degrees
34389 * offset - shift up/down in the rotated frame (unused?)
34390 * containerGroup - if an svg <g> element already exists to hold this
34391 * title, include here. Otherwise it will go in fullLayout._infolayer
34392 * _meta {object (optional} - meta key-value to for title with
34393 * Lib.templateString, default to fullLayout._meta, if not provided
34394 *
34395 * @return {selection} d3 selection of title container group
34396 */
34397function draw(gd, titleClass, options) {
34398 var cont = options.propContainer;
34399 var prop = options.propName;
34400 var placeholder = options.placeholder;
34401 var traceIndex = options.traceIndex;
34402 var avoid = options.avoid || {};
34403 var attributes = options.attributes;
34404 var transform = options.transform;
34405 var group = options.containerGroup;
34406
34407 var fullLayout = gd._fullLayout;
34408
34409 var opacity = 1;
34410 var isplaceholder = false;
34411 var title = cont.title;
34412 var txt = (title && title.text ? title.text : '').trim();
34413
34414 var font = title && title.font ? title.font : {};
34415 var fontFamily = font.family;
34416 var fontSize = font.size;
34417 var fontColor = font.color;
34418
34419 // only make this title editable if we positively identify its property
34420 // as one that has editing enabled.
34421 var editAttr;
34422 if(prop === 'title.text') editAttr = 'titleText';
34423 else if(prop.indexOf('axis') !== -1) editAttr = 'axisTitleText';
34424 else if(prop.indexOf('colorbar' !== -1)) editAttr = 'colorbarTitleText';
34425 var editable = gd._context.edits[editAttr];
34426
34427 if(txt === '') opacity = 0;
34428 // look for placeholder text while stripping out numbers from eg X2, Y3
34429 // this is just for backward compatibility with the old version that had
34430 // "Click to enter X2 title" and may have gotten saved in some old plots,
34431 // we don't want this to show up when these are displayed.
34432 else if(txt.replace(numStripRE, ' % ') === placeholder.replace(numStripRE, ' % ')) {
34433 opacity = 0.2;
34434 isplaceholder = true;
34435 if(!editable) txt = '';
34436 }
34437
34438 if(options._meta) {
34439 txt = Lib.templateString(txt, options._meta);
34440 } else if(fullLayout._meta) {
34441 txt = Lib.templateString(txt, fullLayout._meta);
34442 }
34443
34444 var elShouldExist = txt || editable;
34445
34446 if(!group) {
34447 group = Lib.ensureSingle(fullLayout._infolayer, 'g', 'g-' + titleClass);
34448 }
34449
34450 var el = group.selectAll('text')
34451 .data(elShouldExist ? [0] : []);
34452 el.enter().append('text');
34453 el.text(txt)
34454 // this is hacky, but convertToTspans uses the class
34455 // to determine whether to rotate mathJax...
34456 // so we need to clear out any old class and put the
34457 // correct one (only relevant for colorbars, at least
34458 // for now) - ie don't use .classed
34459 .attr('class', titleClass);
34460 el.exit().remove();
34461
34462 if(!elShouldExist) return group;
34463
34464 function titleLayout(titleEl) {
34465 Lib.syncOrAsync([drawTitle, scootTitle], titleEl);
34466 }
34467
34468 function drawTitle(titleEl) {
34469 var transformVal;
34470
34471 if(transform) {
34472 transformVal = '';
34473 if(transform.rotate) {
34474 transformVal += 'rotate(' + [transform.rotate, attributes.x, attributes.y] + ')';
34475 }
34476 if(transform.offset) {
34477 transformVal += 'translate(0, ' + transform.offset + ')';
34478 }
34479 } else {
34480 transformVal = null;
34481 }
34482
34483 titleEl.attr('transform', transformVal);
34484
34485 titleEl.style({
34486 'font-family': fontFamily,
34487 'font-size': d3.round(fontSize, 2) + 'px',
34488 fill: Color.rgb(fontColor),
34489 opacity: opacity * Color.opacity(fontColor),
34490 'font-weight': Plots.fontWeight
34491 })
34492 .attr(attributes)
34493 .call(svgTextUtils.convertToTspans, gd);
34494
34495 return Plots.previousPromises(gd);
34496 }
34497
34498 function scootTitle(titleElIn) {
34499 var titleGroup = d3.select(titleElIn.node().parentNode);
34500
34501 if(avoid && avoid.selection && avoid.side && txt) {
34502 titleGroup.attr('transform', null);
34503
34504 // move toward avoid.side (= left, right, top, bottom) if needed
34505 // can include pad (pixels, default 2)
34506 var backside = OPPOSITE_SIDE[avoid.side];
34507 var shiftSign = (avoid.side === 'left' || avoid.side === 'top') ? -1 : 1;
34508 var pad = isNumeric(avoid.pad) ? avoid.pad : 2;
34509
34510 var titlebb = Drawing.bBox(titleGroup.node());
34511 var paperbb = {
34512 left: 0,
34513 top: 0,
34514 right: fullLayout.width,
34515 bottom: fullLayout.height
34516 };
34517
34518 var maxshift = avoid.maxShift ||
34519 shiftSign * (paperbb[avoid.side] - titlebb[avoid.side]);
34520 var shift = 0;
34521
34522 // Prevent the title going off the paper
34523 if(maxshift < 0) {
34524 shift = maxshift;
34525 } else {
34526 // so we don't have to offset each avoided element,
34527 // give the title the opposite offset
34528 var offsetLeft = avoid.offsetLeft || 0;
34529 var offsetTop = avoid.offsetTop || 0;
34530 titlebb.left -= offsetLeft;
34531 titlebb.right -= offsetLeft;
34532 titlebb.top -= offsetTop;
34533 titlebb.bottom -= offsetTop;
34534
34535 // iterate over a set of elements (avoid.selection)
34536 // to avoid collisions with
34537 avoid.selection.each(function() {
34538 var avoidbb = Drawing.bBox(this);
34539
34540 if(Lib.bBoxIntersect(titlebb, avoidbb, pad)) {
34541 shift = Math.max(shift, shiftSign * (
34542 avoidbb[avoid.side] - titlebb[backside]) + pad);
34543 }
34544 });
34545 shift = Math.min(maxshift, shift);
34546 }
34547
34548 if(shift > 0 || maxshift < 0) {
34549 var shiftTemplate = {
34550 left: [-shift, 0],
34551 right: [shift, 0],
34552 top: [0, -shift],
34553 bottom: [0, shift]
34554 }[avoid.side];
34555 titleGroup.attr('transform', 'translate(' + shiftTemplate + ')');
34556 }
34557 }
34558 }
34559
34560 el.call(titleLayout);
34561
34562 function setPlaceholder() {
34563 opacity = 0;
34564 isplaceholder = true;
34565 el.text(placeholder)
34566 .on('mouseover.opacity', function() {
34567 d3.select(this).transition()
34568 .duration(interactConstants.SHOW_PLACEHOLDER).style('opacity', 1);
34569 })
34570 .on('mouseout.opacity', function() {
34571 d3.select(this).transition()
34572 .duration(interactConstants.HIDE_PLACEHOLDER).style('opacity', 0);
34573 });
34574 }
34575
34576 if(editable) {
34577 if(!txt) setPlaceholder();
34578 else el.on('.opacity', null);
34579
34580 el.call(svgTextUtils.makeEditable, {gd: gd})
34581 .on('edit', function(text) {
34582 if(traceIndex !== undefined) {
34583 Registry.call('_guiRestyle', gd, prop, text, traceIndex);
34584 } else {
34585 Registry.call('_guiRelayout', gd, prop, text);
34586 }
34587 })
34588 .on('cancel', function() {
34589 this.text(this.attr('data-unformatted'))
34590 .call(titleLayout);
34591 })
34592 .on('input', function(d) {
34593 this.text(d || ' ')
34594 .call(svgTextUtils.positionText, attributes.x, attributes.y);
34595 });
34596 }
34597 el.classed('js-placeholder', isplaceholder);
34598
34599 return group;
34600}
34601
34602module.exports = {
34603 draw: draw
34604};
34605
34606},{"../../constants/alignment":154,"../../constants/interactions":157,"../../lib":178,"../../lib/svg_text_utils":199,"../../plots/plots":256,"../../registry":269,"../color":52,"../drawing":74,"d3":16,"fast-isnumeric":18}],148:[function(_dereq_,module,exports){
34607/**
34608* Copyright 2012-2020, Plotly, Inc.
34609* All rights reserved.
34610*
34611* This source code is licensed under the MIT license found in the
34612* LICENSE file in the root directory of this source tree.
34613*/
34614
34615'use strict';
34616
34617var fontAttrs = _dereq_('../../plots/font_attributes');
34618var colorAttrs = _dereq_('../color/attributes');
34619var extendFlat = _dereq_('../../lib/extend').extendFlat;
34620var overrideAll = _dereq_('../../plot_api/edit_types').overrideAll;
34621var padAttrs = _dereq_('../../plots/pad_attributes');
34622var templatedArray = _dereq_('../../plot_api/plot_template').templatedArray;
34623
34624var buttonsAttrs = templatedArray('button', {
34625 visible: {
34626 valType: 'boolean',
34627
34628
34629 },
34630 method: {
34631 valType: 'enumerated',
34632 values: ['restyle', 'relayout', 'animate', 'update', 'skip'],
34633 dflt: 'restyle',
34634
34635
34636 },
34637 args: {
34638 valType: 'info_array',
34639
34640 freeLength: true,
34641 items: [
34642 {valType: 'any'},
34643 {valType: 'any'},
34644 {valType: 'any'}
34645 ],
34646
34647 },
34648 args2: {
34649 valType: 'info_array',
34650
34651 freeLength: true,
34652 items: [
34653 {valType: 'any'},
34654 {valType: 'any'},
34655 {valType: 'any'}
34656 ],
34657
34658 },
34659 label: {
34660 valType: 'string',
34661
34662 dflt: '',
34663
34664 },
34665 execute: {
34666 valType: 'boolean',
34667
34668 dflt: true,
34669
34670 }
34671});
34672
34673module.exports = overrideAll(templatedArray('updatemenu', {
34674 _arrayAttrRegexps: [/^updatemenus\[(0|[1-9][0-9]+)\]\.buttons/],
34675
34676 visible: {
34677 valType: 'boolean',
34678
34679
34680 },
34681
34682 type: {
34683 valType: 'enumerated',
34684 values: ['dropdown', 'buttons'],
34685 dflt: 'dropdown',
34686
34687
34688 },
34689
34690 direction: {
34691 valType: 'enumerated',
34692 values: ['left', 'right', 'up', 'down'],
34693 dflt: 'down',
34694
34695
34696 },
34697
34698 active: {
34699 valType: 'integer',
34700
34701 min: -1,
34702 dflt: 0,
34703
34704 },
34705
34706 showactive: {
34707 valType: 'boolean',
34708
34709 dflt: true,
34710
34711 },
34712
34713 buttons: buttonsAttrs,
34714
34715 x: {
34716 valType: 'number',
34717 min: -2,
34718 max: 3,
34719 dflt: -0.05,
34720
34721
34722 },
34723 xanchor: {
34724 valType: 'enumerated',
34725 values: ['auto', 'left', 'center', 'right'],
34726 dflt: 'right',
34727
34728
34729 },
34730 y: {
34731 valType: 'number',
34732 min: -2,
34733 max: 3,
34734 dflt: 1,
34735
34736
34737 },
34738 yanchor: {
34739 valType: 'enumerated',
34740 values: ['auto', 'top', 'middle', 'bottom'],
34741 dflt: 'top',
34742
34743
34744 },
34745
34746 pad: extendFlat(padAttrs({editType: 'arraydraw'}), {
34747
34748 }),
34749
34750 font: fontAttrs({
34751
34752 }),
34753
34754 bgcolor: {
34755 valType: 'color',
34756
34757
34758 },
34759 bordercolor: {
34760 valType: 'color',
34761 dflt: colorAttrs.borderLine,
34762
34763
34764 },
34765 borderwidth: {
34766 valType: 'number',
34767 min: 0,
34768 dflt: 1,
34769
34770 editType: 'arraydraw',
34771
34772 }
34773}), 'arraydraw', 'from-root');
34774
34775},{"../../lib/extend":173,"../../plot_api/edit_types":205,"../../plot_api/plot_template":212,"../../plots/font_attributes":250,"../../plots/pad_attributes":255,"../color/attributes":51}],149:[function(_dereq_,module,exports){
34776/**
34777* Copyright 2012-2020, Plotly, Inc.
34778* All rights reserved.
34779*
34780* This source code is licensed under the MIT license found in the
34781* LICENSE file in the root directory of this source tree.
34782*/
34783
34784
34785'use strict';
34786
34787
34788module.exports = {
34789
34790 // layout attribute name
34791 name: 'updatemenus',
34792
34793 // class names
34794 containerClassName: 'updatemenu-container',
34795 headerGroupClassName: 'updatemenu-header-group',
34796 headerClassName: 'updatemenu-header',
34797 headerArrowClassName: 'updatemenu-header-arrow',
34798 dropdownButtonGroupClassName: 'updatemenu-dropdown-button-group',
34799 dropdownButtonClassName: 'updatemenu-dropdown-button',
34800 buttonClassName: 'updatemenu-button',
34801 itemRectClassName: 'updatemenu-item-rect',
34802 itemTextClassName: 'updatemenu-item-text',
34803
34804 // DOM attribute name in button group keeping track
34805 // of active update menu
34806 menuIndexAttrName: 'updatemenu-active-index',
34807
34808 // id root pass to Plots.autoMargin
34809 autoMarginIdRoot: 'updatemenu-',
34810
34811 // options when 'active: -1'
34812 blankHeaderOpts: { label: ' ' },
34813
34814 // min item width / height
34815 minWidth: 30,
34816 minHeight: 30,
34817
34818 // padding around item text
34819 textPadX: 24,
34820 arrowPadX: 16,
34821
34822 // item rect radii
34823 rx: 2,
34824 ry: 2,
34825
34826 // item text x offset off left edge
34827 textOffsetX: 12,
34828
34829 // item text y offset (w.r.t. middle)
34830 textOffsetY: 3,
34831
34832 // arrow offset off right edge
34833 arrowOffsetX: 4,
34834
34835 // gap between header and buttons
34836 gapButtonHeader: 5,
34837
34838 // gap between between buttons
34839 gapButton: 2,
34840
34841 // color given to active buttons
34842 activeColor: '#F4FAFF',
34843
34844 // color given to hovered buttons
34845 hoverColor: '#F4FAFF',
34846
34847 // symbol for menu open arrow
34848 arrowSymbol: {
34849 left: '◄',
34850 right: '►',
34851 up: '▲',
34852 down: '▼'
34853 }
34854};
34855
34856},{}],150:[function(_dereq_,module,exports){
34857/**
34858* Copyright 2012-2020, Plotly, Inc.
34859* All rights reserved.
34860*
34861* This source code is licensed under the MIT license found in the
34862* LICENSE file in the root directory of this source tree.
34863*/
34864
34865'use strict';
34866
34867var Lib = _dereq_('../../lib');
34868var handleArrayContainerDefaults = _dereq_('../../plots/array_container_defaults');
34869
34870var attributes = _dereq_('./attributes');
34871var constants = _dereq_('./constants');
34872
34873var name = constants.name;
34874var buttonAttrs = attributes.buttons;
34875
34876
34877module.exports = function updateMenusDefaults(layoutIn, layoutOut) {
34878 var opts = {
34879 name: name,
34880 handleItemDefaults: menuDefaults
34881 };
34882
34883 handleArrayContainerDefaults(layoutIn, layoutOut, opts);
34884};
34885
34886function menuDefaults(menuIn, menuOut, layoutOut) {
34887 function coerce(attr, dflt) {
34888 return Lib.coerce(menuIn, menuOut, attributes, attr, dflt);
34889 }
34890
34891 var buttons = handleArrayContainerDefaults(menuIn, menuOut, {
34892 name: 'buttons',
34893 handleItemDefaults: buttonDefaults
34894 });
34895
34896 var visible = coerce('visible', buttons.length > 0);
34897 if(!visible) return;
34898
34899 coerce('active');
34900 coerce('direction');
34901 coerce('type');
34902 coerce('showactive');
34903
34904 coerce('x');
34905 coerce('y');
34906 Lib.noneOrAll(menuIn, menuOut, ['x', 'y']);
34907
34908 coerce('xanchor');
34909 coerce('yanchor');
34910
34911 coerce('pad.t');
34912 coerce('pad.r');
34913 coerce('pad.b');
34914 coerce('pad.l');
34915
34916 Lib.coerceFont(coerce, 'font', layoutOut.font);
34917
34918 coerce('bgcolor', layoutOut.paper_bgcolor);
34919 coerce('bordercolor');
34920 coerce('borderwidth');
34921}
34922
34923function buttonDefaults(buttonIn, buttonOut) {
34924 function coerce(attr, dflt) {
34925 return Lib.coerce(buttonIn, buttonOut, buttonAttrs, attr, dflt);
34926 }
34927
34928 var visible = coerce('visible',
34929 (buttonIn.method === 'skip' || Array.isArray(buttonIn.args)));
34930 if(visible) {
34931 coerce('method');
34932 coerce('args');
34933 coerce('args2');
34934 coerce('label');
34935 coerce('execute');
34936 }
34937}
34938
34939},{"../../lib":178,"../../plots/array_container_defaults":218,"./attributes":148,"./constants":149}],151:[function(_dereq_,module,exports){
34940/**
34941* Copyright 2012-2020, Plotly, Inc.
34942* All rights reserved.
34943*
34944* This source code is licensed under the MIT license found in the
34945* LICENSE file in the root directory of this source tree.
34946*/
34947
34948
34949'use strict';
34950
34951var d3 = _dereq_('d3');
34952
34953var Plots = _dereq_('../../plots/plots');
34954var Color = _dereq_('../color');
34955var Drawing = _dereq_('../drawing');
34956var Lib = _dereq_('../../lib');
34957var svgTextUtils = _dereq_('../../lib/svg_text_utils');
34958var arrayEditor = _dereq_('../../plot_api/plot_template').arrayEditor;
34959
34960var LINE_SPACING = _dereq_('../../constants/alignment').LINE_SPACING;
34961
34962var constants = _dereq_('./constants');
34963var ScrollBox = _dereq_('./scrollbox');
34964
34965module.exports = function draw(gd) {
34966 var fullLayout = gd._fullLayout;
34967 var menuData = Lib.filterVisible(fullLayout[constants.name]);
34968
34969 /* Update menu data is bound to the header-group.
34970 * The items in the header group are always present.
34971 *
34972 * Upon clicking on a header its corresponding button
34973 * data is bound to the button-group.
34974 *
34975 * We draw all headers in one group before all buttons
34976 * so that the buttons *always* appear above the headers.
34977 *
34978 * Note that only one set of buttons are visible at once.
34979 *
34980 * <g container />
34981 *
34982 * <g header-group />
34983 * <g item header />
34984 * <text item header-arrow />
34985 * <g header-group />
34986 * <g item header />
34987 * <text item header-arrow />
34988 * ...
34989 *
34990 * <g button-group />
34991 * <g item button />
34992 * <g item button />
34993 * ...
34994 */
34995
34996 function clearAutoMargin(menuOpts) {
34997 Plots.autoMargin(gd, autoMarginId(menuOpts));
34998 }
34999
35000 // draw update menu container
35001 var menus = fullLayout._menulayer
35002 .selectAll('g.' + constants.containerClassName)
35003 .data(menuData.length > 0 ? [0] : []);
35004
35005 menus.enter().append('g')
35006 .classed(constants.containerClassName, true)
35007 .style('cursor', 'pointer');
35008
35009 menus.exit().each(function() {
35010 // Most components don't need to explicitly remove autoMargin, because
35011 // marginPushers does this - but updatemenu updates don't go through
35012 // a full replot so we need to explicitly remove it.
35013 // This is for removing *all* updatemenus, removing individuals is
35014 // handled below, in headerGroups.exit
35015 d3.select(this).selectAll('g.' + constants.headerGroupClassName)
35016 .each(clearAutoMargin);
35017 }).remove();
35018
35019 // return early if no update menus are visible
35020 if(menuData.length === 0) return;
35021
35022 // join header group
35023 var headerGroups = menus.selectAll('g.' + constants.headerGroupClassName)
35024 .data(menuData, keyFunction);
35025
35026 headerGroups.enter().append('g')
35027 .classed(constants.headerGroupClassName, true);
35028
35029 // draw dropdown button container
35030 var gButton = Lib.ensureSingle(menus, 'g', constants.dropdownButtonGroupClassName, function(s) {
35031 s.style('pointer-events', 'all');
35032 });
35033
35034 // find dimensions before plotting anything (this mutates menuOpts)
35035 for(var i = 0; i < menuData.length; i++) {
35036 var menuOpts = menuData[i];
35037 findDimensions(gd, menuOpts);
35038 }
35039
35040 // setup scrollbox
35041 var scrollBoxId = 'updatemenus' + fullLayout._uid;
35042 var scrollBox = new ScrollBox(gd, gButton, scrollBoxId);
35043
35044 // remove exiting header, remove dropped buttons and reset margins
35045 if(headerGroups.enter().size()) {
35046 // make sure gButton is on top of all headers
35047 gButton.node().parentNode.appendChild(gButton.node());
35048 gButton.call(removeAllButtons);
35049 }
35050
35051 headerGroups.exit().each(function(menuOpts) {
35052 gButton.call(removeAllButtons);
35053 clearAutoMargin(menuOpts);
35054 }).remove();
35055
35056 // draw headers!
35057 headerGroups.each(function(menuOpts) {
35058 var gHeader = d3.select(this);
35059
35060 var _gButton = menuOpts.type === 'dropdown' ? gButton : null;
35061
35062 Plots.manageCommandObserver(gd, menuOpts, menuOpts.buttons, function(data) {
35063 setActive(gd, menuOpts, menuOpts.buttons[data.index], gHeader, _gButton, scrollBox, data.index, true);
35064 });
35065
35066 if(menuOpts.type === 'dropdown') {
35067 drawHeader(gd, gHeader, gButton, scrollBox, menuOpts);
35068
35069 // if this menu is active, update the dropdown container
35070 if(isActive(gButton, menuOpts)) {
35071 drawButtons(gd, gHeader, gButton, scrollBox, menuOpts);
35072 }
35073 } else {
35074 drawButtons(gd, gHeader, null, null, menuOpts);
35075 }
35076 });
35077};
35078
35079// Note that '_index' is set at the default step,
35080// it corresponds to the menu index in the user layout update menu container.
35081// Because a menu can be set invisible,
35082// this is a more 'consistent' field than the index in the menuData.
35083function keyFunction(menuOpts) {
35084 return menuOpts._index;
35085}
35086
35087function isFolded(gButton) {
35088 return +gButton.attr(constants.menuIndexAttrName) === -1;
35089}
35090
35091function isActive(gButton, menuOpts) {
35092 return +gButton.attr(constants.menuIndexAttrName) === menuOpts._index;
35093}
35094
35095function setActive(gd, menuOpts, buttonOpts, gHeader, gButton, scrollBox, buttonIndex, isSilentUpdate) {
35096 // update 'active' attribute in menuOpts
35097 menuOpts.active = buttonIndex;
35098
35099 // due to templating, it's possible this slider doesn't even exist yet
35100 arrayEditor(gd.layout, constants.name, menuOpts)
35101 .applyUpdate('active', buttonIndex);
35102
35103 if(menuOpts.type === 'buttons') {
35104 drawButtons(gd, gHeader, null, null, menuOpts);
35105 } else if(menuOpts.type === 'dropdown') {
35106 // fold up buttons and redraw header
35107 gButton.attr(constants.menuIndexAttrName, '-1');
35108
35109 drawHeader(gd, gHeader, gButton, scrollBox, menuOpts);
35110
35111 if(!isSilentUpdate) {
35112 drawButtons(gd, gHeader, gButton, scrollBox, menuOpts);
35113 }
35114 }
35115}
35116
35117function drawHeader(gd, gHeader, gButton, scrollBox, menuOpts) {
35118 var header = Lib.ensureSingle(gHeader, 'g', constants.headerClassName, function(s) {
35119 s.style('pointer-events', 'all');
35120 });
35121
35122 var dims = menuOpts._dims;
35123 var active = menuOpts.active;
35124 var headerOpts = menuOpts.buttons[active] || constants.blankHeaderOpts;
35125 var posOpts = { y: menuOpts.pad.t, yPad: 0, x: menuOpts.pad.l, xPad: 0, index: 0 };
35126 var positionOverrides = {
35127 width: dims.headerWidth,
35128 height: dims.headerHeight
35129 };
35130
35131 header
35132 .call(drawItem, menuOpts, headerOpts, gd)
35133 .call(setItemPosition, menuOpts, posOpts, positionOverrides);
35134
35135 // draw drop arrow at the right edge
35136 var arrow = Lib.ensureSingle(gHeader, 'text', constants.headerArrowClassName, function(s) {
35137 s.classed('user-select-none', true)
35138 .attr('text-anchor', 'end')
35139 .call(Drawing.font, menuOpts.font)
35140 .text(constants.arrowSymbol[menuOpts.direction]);
35141 });
35142
35143 arrow.attr({
35144 x: dims.headerWidth - constants.arrowOffsetX + menuOpts.pad.l,
35145 y: dims.headerHeight / 2 + constants.textOffsetY + menuOpts.pad.t
35146 });
35147
35148 header.on('click', function() {
35149 gButton.call(removeAllButtons,
35150 String(isActive(gButton, menuOpts) ? -1 : menuOpts._index)
35151 );
35152
35153 drawButtons(gd, gHeader, gButton, scrollBox, menuOpts);
35154 });
35155
35156 header.on('mouseover', function() {
35157 header.call(styleOnMouseOver);
35158 });
35159
35160 header.on('mouseout', function() {
35161 header.call(styleOnMouseOut, menuOpts);
35162 });
35163
35164 // translate header group
35165 Drawing.setTranslate(gHeader, dims.lx, dims.ly);
35166}
35167
35168function drawButtons(gd, gHeader, gButton, scrollBox, menuOpts) {
35169 // If this is a set of buttons, set pointer events = all since we play
35170 // some minor games with which container is which in order to simplify
35171 // the drawing of *either* buttons or menus
35172 if(!gButton) {
35173 gButton = gHeader;
35174 gButton.attr('pointer-events', 'all');
35175 }
35176
35177 var buttonData = (!isFolded(gButton) || menuOpts.type === 'buttons') ?
35178 menuOpts.buttons :
35179 [];
35180
35181 var klass = menuOpts.type === 'dropdown' ? constants.dropdownButtonClassName : constants.buttonClassName;
35182
35183 var buttons = gButton.selectAll('g.' + klass)
35184 .data(Lib.filterVisible(buttonData));
35185
35186 var enter = buttons.enter().append('g')
35187 .classed(klass, true);
35188
35189 var exit = buttons.exit();
35190
35191 if(menuOpts.type === 'dropdown') {
35192 enter.attr('opacity', '0')
35193 .transition()
35194 .attr('opacity', '1');
35195
35196 exit.transition()
35197 .attr('opacity', '0')
35198 .remove();
35199 } else {
35200 exit.remove();
35201 }
35202
35203 var x0 = 0;
35204 var y0 = 0;
35205 var dims = menuOpts._dims;
35206
35207 var isVertical = ['up', 'down'].indexOf(menuOpts.direction) !== -1;
35208
35209 if(menuOpts.type === 'dropdown') {
35210 if(isVertical) {
35211 y0 = dims.headerHeight + constants.gapButtonHeader;
35212 } else {
35213 x0 = dims.headerWidth + constants.gapButtonHeader;
35214 }
35215 }
35216
35217 if(menuOpts.type === 'dropdown' && menuOpts.direction === 'up') {
35218 y0 = -constants.gapButtonHeader + constants.gapButton - dims.openHeight;
35219 }
35220
35221 if(menuOpts.type === 'dropdown' && menuOpts.direction === 'left') {
35222 x0 = -constants.gapButtonHeader + constants.gapButton - dims.openWidth;
35223 }
35224
35225 var posOpts = {
35226 x: dims.lx + x0 + menuOpts.pad.l,
35227 y: dims.ly + y0 + menuOpts.pad.t,
35228 yPad: constants.gapButton,
35229 xPad: constants.gapButton,
35230 index: 0,
35231 };
35232
35233 var scrollBoxPosition = {
35234 l: posOpts.x + menuOpts.borderwidth,
35235 t: posOpts.y + menuOpts.borderwidth
35236 };
35237
35238 buttons.each(function(buttonOpts, buttonIndex) {
35239 var button = d3.select(this);
35240
35241 button
35242 .call(drawItem, menuOpts, buttonOpts, gd)
35243 .call(setItemPosition, menuOpts, posOpts);
35244
35245 button.on('click', function() {
35246 // skip `dragend` events
35247 if(d3.event.defaultPrevented) return;
35248
35249 if(buttonOpts.execute) {
35250 if(buttonOpts.args2 && menuOpts.active === buttonIndex) {
35251 setActive(gd, menuOpts, buttonOpts, gHeader, gButton, scrollBox, -1);
35252 Plots.executeAPICommand(gd, buttonOpts.method, buttonOpts.args2);
35253 } else {
35254 setActive(gd, menuOpts, buttonOpts, gHeader, gButton, scrollBox, buttonIndex);
35255 Plots.executeAPICommand(gd, buttonOpts.method, buttonOpts.args);
35256 }
35257 }
35258
35259 gd.emit('plotly_buttonclicked', {menu: menuOpts, button: buttonOpts, active: menuOpts.active});
35260 });
35261
35262 button.on('mouseover', function() {
35263 button.call(styleOnMouseOver);
35264 });
35265
35266 button.on('mouseout', function() {
35267 button.call(styleOnMouseOut, menuOpts);
35268 buttons.call(styleButtons, menuOpts);
35269 });
35270 });
35271
35272 buttons.call(styleButtons, menuOpts);
35273
35274 if(isVertical) {
35275 scrollBoxPosition.w = Math.max(dims.openWidth, dims.headerWidth);
35276 scrollBoxPosition.h = posOpts.y - scrollBoxPosition.t;
35277 } else {
35278 scrollBoxPosition.w = posOpts.x - scrollBoxPosition.l;
35279 scrollBoxPosition.h = Math.max(dims.openHeight, dims.headerHeight);
35280 }
35281
35282 scrollBoxPosition.direction = menuOpts.direction;
35283
35284 if(scrollBox) {
35285 if(buttons.size()) {
35286 drawScrollBox(gd, gHeader, gButton, scrollBox, menuOpts, scrollBoxPosition);
35287 } else {
35288 hideScrollBox(scrollBox);
35289 }
35290 }
35291}
35292
35293function drawScrollBox(gd, gHeader, gButton, scrollBox, menuOpts, position) {
35294 // enable the scrollbox
35295 var direction = menuOpts.direction;
35296 var isVertical = (direction === 'up' || direction === 'down');
35297 var dims = menuOpts._dims;
35298
35299 var active = menuOpts.active;
35300 var translateX, translateY;
35301 var i;
35302 if(isVertical) {
35303 translateY = 0;
35304 for(i = 0; i < active; i++) {
35305 translateY += dims.heights[i] + constants.gapButton;
35306 }
35307 } else {
35308 translateX = 0;
35309 for(i = 0; i < active; i++) {
35310 translateX += dims.widths[i] + constants.gapButton;
35311 }
35312 }
35313
35314 scrollBox.enable(position, translateX, translateY);
35315
35316 if(scrollBox.hbar) {
35317 scrollBox.hbar
35318 .attr('opacity', '0')
35319 .transition()
35320 .attr('opacity', '1');
35321 }
35322
35323 if(scrollBox.vbar) {
35324 scrollBox.vbar
35325 .attr('opacity', '0')
35326 .transition()
35327 .attr('opacity', '1');
35328 }
35329}
35330
35331function hideScrollBox(scrollBox) {
35332 var hasHBar = !!scrollBox.hbar;
35333 var hasVBar = !!scrollBox.vbar;
35334
35335 if(hasHBar) {
35336 scrollBox.hbar
35337 .transition()
35338 .attr('opacity', '0')
35339 .each('end', function() {
35340 hasHBar = false;
35341 if(!hasVBar) scrollBox.disable();
35342 });
35343 }
35344
35345 if(hasVBar) {
35346 scrollBox.vbar
35347 .transition()
35348 .attr('opacity', '0')
35349 .each('end', function() {
35350 hasVBar = false;
35351 if(!hasHBar) scrollBox.disable();
35352 });
35353 }
35354}
35355
35356function drawItem(item, menuOpts, itemOpts, gd) {
35357 item.call(drawItemRect, menuOpts)
35358 .call(drawItemText, menuOpts, itemOpts, gd);
35359}
35360
35361function drawItemRect(item, menuOpts) {
35362 var rect = Lib.ensureSingle(item, 'rect', constants.itemRectClassName, function(s) {
35363 s.attr({
35364 rx: constants.rx,
35365 ry: constants.ry,
35366 'shape-rendering': 'crispEdges'
35367 });
35368 });
35369
35370 rect.call(Color.stroke, menuOpts.bordercolor)
35371 .call(Color.fill, menuOpts.bgcolor)
35372 .style('stroke-width', menuOpts.borderwidth + 'px');
35373}
35374
35375function drawItemText(item, menuOpts, itemOpts, gd) {
35376 var text = Lib.ensureSingle(item, 'text', constants.itemTextClassName, function(s) {
35377 s.classed('user-select-none', true)
35378 .attr({
35379 'text-anchor': 'start',
35380 'data-notex': 1
35381 });
35382 });
35383
35384 var tx = itemOpts.label;
35385 var _meta = gd._fullLayout._meta;
35386 if(_meta) tx = Lib.templateString(tx, _meta);
35387
35388 text.call(Drawing.font, menuOpts.font)
35389 .text(tx)
35390 .call(svgTextUtils.convertToTspans, gd);
35391}
35392
35393function styleButtons(buttons, menuOpts) {
35394 var active = menuOpts.active;
35395
35396 buttons.each(function(buttonOpts, i) {
35397 var button = d3.select(this);
35398
35399 if(i === active && menuOpts.showactive) {
35400 button.select('rect.' + constants.itemRectClassName)
35401 .call(Color.fill, constants.activeColor);
35402 }
35403 });
35404}
35405
35406function styleOnMouseOver(item) {
35407 item.select('rect.' + constants.itemRectClassName)
35408 .call(Color.fill, constants.hoverColor);
35409}
35410
35411function styleOnMouseOut(item, menuOpts) {
35412 item.select('rect.' + constants.itemRectClassName)
35413 .call(Color.fill, menuOpts.bgcolor);
35414}
35415
35416// find item dimensions (this mutates menuOpts)
35417function findDimensions(gd, menuOpts) {
35418 var dims = menuOpts._dims = {
35419 width1: 0,
35420 height1: 0,
35421 heights: [],
35422 widths: [],
35423 totalWidth: 0,
35424 totalHeight: 0,
35425 openWidth: 0,
35426 openHeight: 0,
35427 lx: 0,
35428 ly: 0
35429 };
35430
35431 var fakeButtons = Drawing.tester.selectAll('g.' + constants.dropdownButtonClassName)
35432 .data(Lib.filterVisible(menuOpts.buttons));
35433
35434 fakeButtons.enter().append('g')
35435 .classed(constants.dropdownButtonClassName, true);
35436
35437 var isVertical = ['up', 'down'].indexOf(menuOpts.direction) !== -1;
35438
35439 // loop over fake buttons to find width / height
35440 fakeButtons.each(function(buttonOpts, i) {
35441 var button = d3.select(this);
35442
35443 button.call(drawItem, menuOpts, buttonOpts, gd);
35444
35445 var text = button.select('.' + constants.itemTextClassName);
35446
35447 // width is given by max width of all buttons
35448 var tWidth = text.node() && Drawing.bBox(text.node()).width;
35449 var wEff = Math.max(tWidth + constants.textPadX, constants.minWidth);
35450
35451 // height is determined by item text
35452 var tHeight = menuOpts.font.size * LINE_SPACING;
35453 var tLines = svgTextUtils.lineCount(text);
35454 var hEff = Math.max(tHeight * tLines, constants.minHeight) + constants.textOffsetY;
35455
35456 hEff = Math.ceil(hEff);
35457 wEff = Math.ceil(wEff);
35458
35459 // Store per-item sizes since a row of horizontal buttons, for example,
35460 // don't all need to be the same width:
35461 dims.widths[i] = wEff;
35462 dims.heights[i] = hEff;
35463
35464 // Height and width of individual element:
35465 dims.height1 = Math.max(dims.height1, hEff);
35466 dims.width1 = Math.max(dims.width1, wEff);
35467
35468 if(isVertical) {
35469 dims.totalWidth = Math.max(dims.totalWidth, wEff);
35470 dims.openWidth = dims.totalWidth;
35471 dims.totalHeight += hEff + constants.gapButton;
35472 dims.openHeight += hEff + constants.gapButton;
35473 } else {
35474 dims.totalWidth += wEff + constants.gapButton;
35475 dims.openWidth += wEff + constants.gapButton;
35476 dims.totalHeight = Math.max(dims.totalHeight, hEff);
35477 dims.openHeight = dims.totalHeight;
35478 }
35479 });
35480
35481 if(isVertical) {
35482 dims.totalHeight -= constants.gapButton;
35483 } else {
35484 dims.totalWidth -= constants.gapButton;
35485 }
35486
35487
35488 dims.headerWidth = dims.width1 + constants.arrowPadX;
35489 dims.headerHeight = dims.height1;
35490
35491 if(menuOpts.type === 'dropdown') {
35492 if(isVertical) {
35493 dims.width1 += constants.arrowPadX;
35494 dims.totalHeight = dims.height1;
35495 } else {
35496 dims.totalWidth = dims.width1;
35497 }
35498 dims.totalWidth += constants.arrowPadX;
35499 }
35500
35501 fakeButtons.remove();
35502
35503 var paddedWidth = dims.totalWidth + menuOpts.pad.l + menuOpts.pad.r;
35504 var paddedHeight = dims.totalHeight + menuOpts.pad.t + menuOpts.pad.b;
35505
35506 var graphSize = gd._fullLayout._size;
35507 dims.lx = graphSize.l + graphSize.w * menuOpts.x;
35508 dims.ly = graphSize.t + graphSize.h * (1 - menuOpts.y);
35509
35510 var xanchor = 'left';
35511 if(Lib.isRightAnchor(menuOpts)) {
35512 dims.lx -= paddedWidth;
35513 xanchor = 'right';
35514 }
35515 if(Lib.isCenterAnchor(menuOpts)) {
35516 dims.lx -= paddedWidth / 2;
35517 xanchor = 'center';
35518 }
35519
35520 var yanchor = 'top';
35521 if(Lib.isBottomAnchor(menuOpts)) {
35522 dims.ly -= paddedHeight;
35523 yanchor = 'bottom';
35524 }
35525 if(Lib.isMiddleAnchor(menuOpts)) {
35526 dims.ly -= paddedHeight / 2;
35527 yanchor = 'middle';
35528 }
35529
35530 dims.totalWidth = Math.ceil(dims.totalWidth);
35531 dims.totalHeight = Math.ceil(dims.totalHeight);
35532 dims.lx = Math.round(dims.lx);
35533 dims.ly = Math.round(dims.ly);
35534
35535 Plots.autoMargin(gd, autoMarginId(menuOpts), {
35536 x: menuOpts.x,
35537 y: menuOpts.y,
35538 l: paddedWidth * ({right: 1, center: 0.5}[xanchor] || 0),
35539 r: paddedWidth * ({left: 1, center: 0.5}[xanchor] || 0),
35540 b: paddedHeight * ({top: 1, middle: 0.5}[yanchor] || 0),
35541 t: paddedHeight * ({bottom: 1, middle: 0.5}[yanchor] || 0)
35542 });
35543}
35544
35545function autoMarginId(menuOpts) {
35546 return constants.autoMarginIdRoot + menuOpts._index;
35547}
35548
35549// set item positions (mutates posOpts)
35550function setItemPosition(item, menuOpts, posOpts, overrideOpts) {
35551 overrideOpts = overrideOpts || {};
35552 var rect = item.select('.' + constants.itemRectClassName);
35553 var text = item.select('.' + constants.itemTextClassName);
35554 var borderWidth = menuOpts.borderwidth;
35555 var index = posOpts.index;
35556 var dims = menuOpts._dims;
35557
35558 Drawing.setTranslate(item, borderWidth + posOpts.x, borderWidth + posOpts.y);
35559
35560 var isVertical = ['up', 'down'].indexOf(menuOpts.direction) !== -1;
35561 var finalHeight = overrideOpts.height || (isVertical ? dims.heights[index] : dims.height1);
35562
35563 rect.attr({
35564 x: 0,
35565 y: 0,
35566 width: overrideOpts.width || (isVertical ? dims.width1 : dims.widths[index]),
35567 height: finalHeight
35568 });
35569
35570 var tHeight = menuOpts.font.size * LINE_SPACING;
35571 var tLines = svgTextUtils.lineCount(text);
35572 var spanOffset = ((tLines - 1) * tHeight / 2);
35573
35574 svgTextUtils.positionText(text, constants.textOffsetX,
35575 finalHeight / 2 - spanOffset + constants.textOffsetY);
35576
35577 if(isVertical) {
35578 posOpts.y += dims.heights[index] + posOpts.yPad;
35579 } else {
35580 posOpts.x += dims.widths[index] + posOpts.xPad;
35581 }
35582
35583 posOpts.index++;
35584}
35585
35586function removeAllButtons(gButton, newMenuIndexAttr) {
35587 gButton
35588 .attr(constants.menuIndexAttrName, newMenuIndexAttr || '-1')
35589 .selectAll('g.' + constants.dropdownButtonClassName).remove();
35590}
35591
35592},{"../../constants/alignment":154,"../../lib":178,"../../lib/svg_text_utils":199,"../../plot_api/plot_template":212,"../../plots/plots":256,"../color":52,"../drawing":74,"./constants":149,"./scrollbox":153,"d3":16}],152:[function(_dereq_,module,exports){
35593arguments[4][146][0].apply(exports,arguments)
35594},{"./attributes":148,"./constants":149,"./defaults":150,"./draw":151,"dup":146}],153:[function(_dereq_,module,exports){
35595/**
35596* Copyright 2012-2020, Plotly, Inc.
35597* All rights reserved.
35598*
35599* This source code is licensed under the MIT license found in the
35600* LICENSE file in the root directory of this source tree.
35601*/
35602
35603'use strict';
35604
35605module.exports = ScrollBox;
35606
35607var d3 = _dereq_('d3');
35608
35609var Color = _dereq_('../color');
35610var Drawing = _dereq_('../drawing');
35611
35612var Lib = _dereq_('../../lib');
35613
35614/**
35615 * Helper class to setup a scroll box
35616 *
35617 * @class
35618 * @param gd Plotly's graph div
35619 * @param container Container to be scroll-boxed (as a D3 selection)
35620 * @param {string} id Id for the clip path to implement the scroll box
35621 */
35622function ScrollBox(gd, container, id) {
35623 this.gd = gd;
35624 this.container = container;
35625 this.id = id;
35626
35627 // See ScrollBox.prototype.enable for further definition
35628 this.position = null; // scrollbox position
35629 this.translateX = null; // scrollbox horizontal translation
35630 this.translateY = null; // scrollbox vertical translation
35631 this.hbar = null; // horizontal scrollbar D3 selection
35632 this.vbar = null; // vertical scrollbar D3 selection
35633
35634 // <rect> element to capture pointer events
35635 this.bg = this.container.selectAll('rect.scrollbox-bg').data([0]);
35636
35637 this.bg.exit()
35638 .on('.drag', null)
35639 .on('wheel', null)
35640 .remove();
35641
35642 this.bg.enter().append('rect')
35643 .classed('scrollbox-bg', true)
35644 .style('pointer-events', 'all')
35645 .attr({
35646 opacity: 0,
35647 x: 0,
35648 y: 0,
35649 width: 0,
35650 height: 0
35651 });
35652}
35653
35654// scroll bar dimensions
35655ScrollBox.barWidth = 2;
35656ScrollBox.barLength = 20;
35657ScrollBox.barRadius = 2;
35658ScrollBox.barPad = 1;
35659ScrollBox.barColor = '#808BA4';
35660
35661/**
35662 * If needed, setup a clip path and scrollbars
35663 *
35664 * @method
35665 * @param {Object} position
35666 * @param {number} position.l Left side position (in pixels)
35667 * @param {number} position.t Top side (in pixels)
35668 * @param {number} position.w Width (in pixels)
35669 * @param {number} position.h Height (in pixels)
35670 * @param {string} [position.direction='down']
35671 * Either 'down', 'left', 'right' or 'up'
35672 * @param {number} [translateX=0] Horizontal offset (in pixels)
35673 * @param {number} [translateY=0] Vertical offset (in pixels)
35674 */
35675ScrollBox.prototype.enable = function enable(position, translateX, translateY) {
35676 var fullLayout = this.gd._fullLayout;
35677 var fullWidth = fullLayout.width;
35678 var fullHeight = fullLayout.height;
35679
35680 // compute position of scrollbox
35681 this.position = position;
35682
35683 var l = this.position.l;
35684 var w = this.position.w;
35685 var t = this.position.t;
35686 var h = this.position.h;
35687 var direction = this.position.direction;
35688 var isDown = (direction === 'down');
35689 var isLeft = (direction === 'left');
35690 var isRight = (direction === 'right');
35691 var isUp = (direction === 'up');
35692 var boxW = w;
35693 var boxH = h;
35694 var boxL, boxR;
35695 var boxT, boxB;
35696
35697 if(!isDown && !isLeft && !isRight && !isUp) {
35698 this.position.direction = 'down';
35699 isDown = true;
35700 }
35701
35702 var isVertical = isDown || isUp;
35703 if(isVertical) {
35704 boxL = l;
35705 boxR = boxL + boxW;
35706
35707 if(isDown) {
35708 // anchor to top side
35709 boxT = t;
35710 boxB = Math.min(boxT + boxH, fullHeight);
35711 boxH = boxB - boxT;
35712 } else {
35713 // anchor to bottom side
35714 boxB = t + boxH;
35715 boxT = Math.max(boxB - boxH, 0);
35716 boxH = boxB - boxT;
35717 }
35718 } else {
35719 boxT = t;
35720 boxB = boxT + boxH;
35721
35722 if(isLeft) {
35723 // anchor to right side
35724 boxR = l + boxW;
35725 boxL = Math.max(boxR - boxW, 0);
35726 boxW = boxR - boxL;
35727 } else {
35728 // anchor to left side
35729 boxL = l;
35730 boxR = Math.min(boxL + boxW, fullWidth);
35731 boxW = boxR - boxL;
35732 }
35733 }
35734
35735 this._box = {
35736 l: boxL,
35737 t: boxT,
35738 w: boxW,
35739 h: boxH
35740 };
35741
35742 // compute position of horizontal scroll bar
35743 var needsHorizontalScrollBar = (w > boxW);
35744 var hbarW = ScrollBox.barLength + 2 * ScrollBox.barPad;
35745 var hbarH = ScrollBox.barWidth + 2 * ScrollBox.barPad;
35746 // draw horizontal scrollbar on the bottom side
35747 var hbarL = l;
35748 var hbarT = t + h;
35749
35750 if(hbarT + hbarH > fullHeight) hbarT = fullHeight - hbarH;
35751
35752 var hbar = this.container.selectAll('rect.scrollbar-horizontal').data(
35753 (needsHorizontalScrollBar) ? [0] : []);
35754
35755 hbar.exit()
35756 .on('.drag', null)
35757 .remove();
35758
35759 hbar.enter().append('rect')
35760 .classed('scrollbar-horizontal', true)
35761 .call(Color.fill, ScrollBox.barColor);
35762
35763 if(needsHorizontalScrollBar) {
35764 this.hbar = hbar.attr({
35765 'rx': ScrollBox.barRadius,
35766 'ry': ScrollBox.barRadius,
35767 'x': hbarL,
35768 'y': hbarT,
35769 'width': hbarW,
35770 'height': hbarH
35771 });
35772
35773 // hbar center moves between hbarXMin and hbarXMin + hbarTranslateMax
35774 this._hbarXMin = hbarL + hbarW / 2;
35775 this._hbarTranslateMax = boxW - hbarW;
35776 } else {
35777 delete this.hbar;
35778 delete this._hbarXMin;
35779 delete this._hbarTranslateMax;
35780 }
35781
35782 // compute position of vertical scroll bar
35783 var needsVerticalScrollBar = (h > boxH);
35784 var vbarW = ScrollBox.barWidth + 2 * ScrollBox.barPad;
35785 var vbarH = ScrollBox.barLength + 2 * ScrollBox.barPad;
35786 // draw vertical scrollbar on the right side
35787 var vbarL = l + w;
35788 var vbarT = t;
35789
35790 if(vbarL + vbarW > fullWidth) vbarL = fullWidth - vbarW;
35791
35792 var vbar = this.container.selectAll('rect.scrollbar-vertical').data(
35793 (needsVerticalScrollBar) ? [0] : []);
35794
35795 vbar.exit()
35796 .on('.drag', null)
35797 .remove();
35798
35799 vbar.enter().append('rect')
35800 .classed('scrollbar-vertical', true)
35801 .call(Color.fill, ScrollBox.barColor);
35802
35803 if(needsVerticalScrollBar) {
35804 this.vbar = vbar.attr({
35805 'rx': ScrollBox.barRadius,
35806 'ry': ScrollBox.barRadius,
35807 'x': vbarL,
35808 'y': vbarT,
35809 'width': vbarW,
35810 'height': vbarH
35811 });
35812
35813 // vbar center moves between vbarYMin and vbarYMin + vbarTranslateMax
35814 this._vbarYMin = vbarT + vbarH / 2;
35815 this._vbarTranslateMax = boxH - vbarH;
35816 } else {
35817 delete this.vbar;
35818 delete this._vbarYMin;
35819 delete this._vbarTranslateMax;
35820 }
35821
35822 // setup a clip path (if scroll bars are needed)
35823 var clipId = this.id;
35824 var clipL = boxL - 0.5;
35825 var clipR = (needsVerticalScrollBar) ? boxR + vbarW + 0.5 : boxR + 0.5;
35826 var clipT = boxT - 0.5;
35827 var clipB = (needsHorizontalScrollBar) ? boxB + hbarH + 0.5 : boxB + 0.5;
35828
35829 var clipPath = fullLayout._topdefs.selectAll('#' + clipId)
35830 .data((needsHorizontalScrollBar || needsVerticalScrollBar) ? [0] : []);
35831
35832 clipPath.exit().remove();
35833
35834 clipPath.enter()
35835 .append('clipPath').attr('id', clipId)
35836 .append('rect');
35837
35838 if(needsHorizontalScrollBar || needsVerticalScrollBar) {
35839 this._clipRect = clipPath.select('rect').attr({
35840 x: Math.floor(clipL),
35841 y: Math.floor(clipT),
35842 width: Math.ceil(clipR) - Math.floor(clipL),
35843 height: Math.ceil(clipB) - Math.floor(clipT)
35844 });
35845
35846 this.container.call(Drawing.setClipUrl, clipId, this.gd);
35847
35848 this.bg.attr({
35849 x: l,
35850 y: t,
35851 width: w,
35852 height: h
35853 });
35854 } else {
35855 this.bg.attr({
35856 width: 0,
35857 height: 0
35858 });
35859 this.container
35860 .on('wheel', null)
35861 .on('.drag', null)
35862 .call(Drawing.setClipUrl, null);
35863 delete this._clipRect;
35864 }
35865
35866 // set up drag listeners (if scroll bars are needed)
35867 if(needsHorizontalScrollBar || needsVerticalScrollBar) {
35868 var onBoxDrag = d3.behavior.drag()
35869 .on('dragstart', function() {
35870 d3.event.sourceEvent.preventDefault();
35871 })
35872 .on('drag', this._onBoxDrag.bind(this));
35873
35874 this.container
35875 .on('wheel', null)
35876 .on('wheel', this._onBoxWheel.bind(this))
35877 .on('.drag', null)
35878 .call(onBoxDrag);
35879
35880 var onBarDrag = d3.behavior.drag()
35881 .on('dragstart', function() {
35882 d3.event.sourceEvent.preventDefault();
35883 d3.event.sourceEvent.stopPropagation();
35884 })
35885 .on('drag', this._onBarDrag.bind(this));
35886
35887 if(needsHorizontalScrollBar) {
35888 this.hbar
35889 .on('.drag', null)
35890 .call(onBarDrag);
35891 }
35892
35893 if(needsVerticalScrollBar) {
35894 this.vbar
35895 .on('.drag', null)
35896 .call(onBarDrag);
35897 }
35898 }
35899
35900 // set scrollbox translation
35901 this.setTranslate(translateX, translateY);
35902};
35903
35904/**
35905 * If present, remove clip-path and scrollbars
35906 *
35907 * @method
35908 */
35909ScrollBox.prototype.disable = function disable() {
35910 if(this.hbar || this.vbar) {
35911 this.bg.attr({
35912 width: 0,
35913 height: 0
35914 });
35915 this.container
35916 .on('wheel', null)
35917 .on('.drag', null)
35918 .call(Drawing.setClipUrl, null);
35919 delete this._clipRect;
35920 }
35921
35922 if(this.hbar) {
35923 this.hbar.on('.drag', null);
35924 this.hbar.remove();
35925 delete this.hbar;
35926 delete this._hbarXMin;
35927 delete this._hbarTranslateMax;
35928 }
35929
35930 if(this.vbar) {
35931 this.vbar.on('.drag', null);
35932 this.vbar.remove();
35933 delete this.vbar;
35934 delete this._vbarYMin;
35935 delete this._vbarTranslateMax;
35936 }
35937};
35938
35939/**
35940 * Handles scroll box drag events
35941 *
35942 * @method
35943 */
35944ScrollBox.prototype._onBoxDrag = function _onBoxDrag() {
35945 var translateX = this.translateX;
35946 var translateY = this.translateY;
35947
35948 if(this.hbar) {
35949 translateX -= d3.event.dx;
35950 }
35951
35952 if(this.vbar) {
35953 translateY -= d3.event.dy;
35954 }
35955
35956 this.setTranslate(translateX, translateY);
35957};
35958
35959/**
35960 * Handles scroll box wheel events
35961 *
35962 * @method
35963 */
35964ScrollBox.prototype._onBoxWheel = function _onBoxWheel() {
35965 var translateX = this.translateX;
35966 var translateY = this.translateY;
35967
35968 if(this.hbar) {
35969 translateX += d3.event.deltaY;
35970 }
35971
35972 if(this.vbar) {
35973 translateY += d3.event.deltaY;
35974 }
35975
35976 this.setTranslate(translateX, translateY);
35977};
35978
35979/**
35980 * Handles scroll bar drag events
35981 *
35982 * @method
35983 */
35984ScrollBox.prototype._onBarDrag = function _onBarDrag() {
35985 var translateX = this.translateX;
35986 var translateY = this.translateY;
35987
35988 if(this.hbar) {
35989 var xMin = translateX + this._hbarXMin;
35990 var xMax = xMin + this._hbarTranslateMax;
35991 var x = Lib.constrain(d3.event.x, xMin, xMax);
35992 var xf = (x - xMin) / (xMax - xMin);
35993
35994 var translateXMax = this.position.w - this._box.w;
35995
35996 translateX = xf * translateXMax;
35997 }
35998
35999 if(this.vbar) {
36000 var yMin = translateY + this._vbarYMin;
36001 var yMax = yMin + this._vbarTranslateMax;
36002 var y = Lib.constrain(d3.event.y, yMin, yMax);
36003 var yf = (y - yMin) / (yMax - yMin);
36004
36005 var translateYMax = this.position.h - this._box.h;
36006
36007 translateY = yf * translateYMax;
36008 }
36009
36010 this.setTranslate(translateX, translateY);
36011};
36012
36013/**
36014 * Set clip path and scroll bar translate transform
36015 *
36016 * @method
36017 * @param {number} [translateX=0] Horizontal offset (in pixels)
36018 * @param {number} [translateY=0] Vertical offset (in pixels)
36019 */
36020ScrollBox.prototype.setTranslate = function setTranslate(translateX, translateY) {
36021 // store translateX and translateY (needed by mouse event handlers)
36022 var translateXMax = this.position.w - this._box.w;
36023 var translateYMax = this.position.h - this._box.h;
36024
36025 translateX = Lib.constrain(translateX || 0, 0, translateXMax);
36026 translateY = Lib.constrain(translateY || 0, 0, translateYMax);
36027
36028 this.translateX = translateX;
36029 this.translateY = translateY;
36030
36031 this.container.call(Drawing.setTranslate,
36032 this._box.l - this.position.l - translateX,
36033 this._box.t - this.position.t - translateY);
36034
36035 if(this._clipRect) {
36036 this._clipRect.attr({
36037 x: Math.floor(this.position.l + translateX - 0.5),
36038 y: Math.floor(this.position.t + translateY - 0.5)
36039 });
36040 }
36041
36042 if(this.hbar) {
36043 var xf = translateX / translateXMax;
36044
36045 this.hbar.call(Drawing.setTranslate,
36046 translateX + xf * this._hbarTranslateMax,
36047 translateY);
36048 }
36049
36050 if(this.vbar) {
36051 var yf = translateY / translateYMax;
36052
36053 this.vbar.call(Drawing.setTranslate,
36054 translateX,
36055 translateY + yf * this._vbarTranslateMax);
36056 }
36057};
36058
36059},{"../../lib":178,"../color":52,"../drawing":74,"d3":16}],154:[function(_dereq_,module,exports){
36060/**
36061* Copyright 2012-2020, Plotly, Inc.
36062* All rights reserved.
36063*
36064* This source code is licensed under the MIT license found in the
36065* LICENSE file in the root directory of this source tree.
36066*/
36067
36068'use strict';
36069
36070// fraction of some size to get to a named position
36071module.exports = {
36072 // from bottom left: this is the origin of our paper-reference
36073 // positioning system
36074 FROM_BL: {
36075 left: 0,
36076 center: 0.5,
36077 right: 1,
36078 bottom: 0,
36079 middle: 0.5,
36080 top: 1
36081 },
36082 // from top left: this is the screen pixel positioning origin
36083 FROM_TL: {
36084 left: 0,
36085 center: 0.5,
36086 right: 1,
36087 bottom: 1,
36088 middle: 0.5,
36089 top: 0
36090 },
36091 // from bottom right: sometimes you just need the opposite of ^^
36092 FROM_BR: {
36093 left: 1,
36094 center: 0.5,
36095 right: 0,
36096 bottom: 0,
36097 middle: 0.5,
36098 top: 1
36099 },
36100 // multiple of fontSize to get the vertical offset between lines
36101 LINE_SPACING: 1.3,
36102
36103 // multiple of fontSize to shift from the baseline
36104 // to the cap (captical letter) line
36105 // (to use when we don't calculate this shift from Drawing.bBox)
36106 // This is an approximation since in reality cap height can differ
36107 // from font to font. However, according to Wikipedia
36108 // an "average" font might have a cap height of 70% of the em
36109 // https://en.wikipedia.org/wiki/Em_(typography)#History
36110 CAP_SHIFT: 0.70,
36111
36112 // half the cap height (distance between baseline and cap line)
36113 // of an "average" font (for more info see above).
36114 MID_SHIFT: 0.35,
36115
36116 OPPOSITE_SIDE: {
36117 left: 'right',
36118 right: 'left',
36119 top: 'bottom',
36120 bottom: 'top'
36121 }
36122};
36123
36124},{}],155:[function(_dereq_,module,exports){
36125/**
36126* Copyright 2012-2020, Plotly, Inc.
36127* All rights reserved.
36128*
36129* This source code is licensed under the MIT license found in the
36130* LICENSE file in the root directory of this source tree.
36131*/
36132
36133'use strict';
36134
36135module.exports = {
36136 FORMAT_LINK: 'https://github.com/d3/d3-3.x-api-reference/blob/master/Formatting.md#d3_format',
36137 DATE_FORMAT_LINK: 'https://github.com/d3/d3-3.x-api-reference/blob/master/Time-Formatting.md#format'
36138};
36139
36140},{}],156:[function(_dereq_,module,exports){
36141/**
36142* Copyright 2012-2020, Plotly, Inc.
36143* All rights reserved.
36144*
36145* This source code is licensed under the MIT license found in the
36146* LICENSE file in the root directory of this source tree.
36147*/
36148
36149'use strict';
36150
36151module.exports = {
36152 COMPARISON_OPS: ['=', '!=', '<', '>=', '>', '<='],
36153 COMPARISON_OPS2: ['=', '<', '>=', '>', '<='],
36154 INTERVAL_OPS: ['[]', '()', '[)', '(]', '][', ')(', '](', ')['],
36155 SET_OPS: ['{}', '}{'],
36156 CONSTRAINT_REDUCTION: {
36157 // for contour constraints, open/closed endpoints are equivalent
36158 '=': '=',
36159
36160 '<': '<',
36161 '<=': '<',
36162
36163 '>': '>',
36164 '>=': '>',
36165
36166 '[]': '[]',
36167 '()': '[]',
36168 '[)': '[]',
36169 '(]': '[]',
36170
36171 '][': '][',
36172 ')(': '][',
36173 '](': '][',
36174 ')[': ']['
36175 }
36176};
36177
36178},{}],157:[function(_dereq_,module,exports){
36179/**
36180* Copyright 2012-2020, Plotly, Inc.
36181* All rights reserved.
36182*
36183* This source code is licensed under the MIT license found in the
36184* LICENSE file in the root directory of this source tree.
36185*/
36186
36187'use strict';
36188
36189
36190module.exports = {
36191 /**
36192 * Timing information for interactive elements
36193 */
36194 SHOW_PLACEHOLDER: 100,
36195 HIDE_PLACEHOLDER: 1000,
36196
36197 // opacity dimming fraction for points that are not in selection
36198 DESELECTDIM: 0.2
36199};
36200
36201},{}],158:[function(_dereq_,module,exports){
36202/**
36203* Copyright 2012-2020, Plotly, Inc.
36204* All rights reserved.
36205*
36206* This source code is licensed under the MIT license found in the
36207* LICENSE file in the root directory of this source tree.
36208*/
36209
36210'use strict';
36211
36212
36213module.exports = {
36214 /**
36215 * Standardize all missing data in calcdata to use undefined
36216 * never null or NaN.
36217 * That way we can use !==undefined, or !== BADNUM,
36218 * to test for real data
36219 */
36220 BADNUM: undefined,
36221
36222 /*
36223 * Limit certain operations to well below floating point max value
36224 * to avoid glitches: Make sure that even when you multiply it by the
36225 * number of pixels on a giant screen it still works
36226 */
36227 FP_SAFE: Number.MAX_VALUE / 10000,
36228
36229 /*
36230 * conversion of date units to milliseconds
36231 * year and month constants are marked "AVG"
36232 * to remind us that not all years and months
36233 * have the same length
36234 */
36235 ONEAVGYEAR: 31557600000, // 365.25 days
36236 ONEAVGMONTH: 2629800000, // 1/12 of ONEAVGYEAR
36237 ONEDAY: 86400000,
36238 ONEHOUR: 3600000,
36239 ONEMIN: 60000,
36240 ONESEC: 1000,
36241
36242 /*
36243 * For fast conversion btwn world calendars and epoch ms, the Julian Day Number
36244 * of the unix epoch. From calendars.instance().newDate(1970, 1, 1).toJD()
36245 */
36246 EPOCHJD: 2440587.5,
36247
36248 /*
36249 * Are two values nearly equal? Compare to 1PPM
36250 */
36251 ALMOST_EQUAL: 1 - 1e-6,
36252
36253 /*
36254 * If we're asked to clip a non-positive log value, how far off-screen
36255 * do we put it?
36256 */
36257 LOG_CLIP: 10,
36258
36259 /*
36260 * not a number, but for displaying numbers: the "minus sign" symbol is
36261 * wider than the regular ascii dash "-"
36262 */
36263 MINUS_SIGN: '\u2212'
36264};
36265
36266},{}],159:[function(_dereq_,module,exports){
36267/**
36268* Copyright 2012-2020, Plotly, Inc.
36269* All rights reserved.
36270*
36271* This source code is licensed under the MIT license found in the
36272* LICENSE file in the root directory of this source tree.
36273*/
36274
36275
36276'use strict';
36277
36278
36279exports.xmlns = 'http://www.w3.org/2000/xmlns/';
36280exports.svg = 'http://www.w3.org/2000/svg';
36281exports.xlink = 'http://www.w3.org/1999/xlink';
36282
36283// the 'old' d3 quirk got fix in v3.5.7
36284// https://github.com/mbostock/d3/commit/a6f66e9dd37f764403fc7c1f26be09ab4af24fed
36285exports.svgAttrs = {
36286 xmlns: exports.svg,
36287 'xmlns:xlink': exports.xlink
36288};
36289
36290},{}],160:[function(_dereq_,module,exports){
36291/**
36292* Copyright 2012-2020, Plotly, Inc.
36293* All rights reserved.
36294*
36295* This source code is licensed under the MIT license found in the
36296* LICENSE file in the root directory of this source tree.
36297*/
36298
36299'use strict';
36300
36301exports.version = _dereq_('./version').version;
36302
36303// inject promise polyfill
36304_dereq_('es6-promise').polyfill();
36305
36306// inject plot css
36307_dereq_('../build/plotcss');
36308
36309// inject default MathJax config
36310_dereq_('./fonts/mathjax_config')();
36311
36312// include registry module and expose register method
36313var Registry = _dereq_('./registry');
36314var register = exports.register = Registry.register;
36315
36316// expose plot api methods
36317var plotApi = _dereq_('./plot_api');
36318var methodNames = Object.keys(plotApi);
36319for(var i = 0; i < methodNames.length; i++) {
36320 var name = methodNames[i];
36321 // _ -> private API methods, but still registered for internal use
36322 if(name.charAt(0) !== '_') exports[name] = plotApi[name];
36323 register({
36324 moduleType: 'apiMethod',
36325 name: name,
36326 fn: plotApi[name]
36327 });
36328}
36329
36330// scatter is the only trace included by default
36331register(_dereq_('./traces/scatter'));
36332
36333// register all registrable components modules
36334register([
36335 _dereq_('./components/legend'),
36336 _dereq_('./components/fx'), // fx needs to come after legend
36337 _dereq_('./components/annotations'),
36338 _dereq_('./components/annotations3d'),
36339 _dereq_('./components/shapes'),
36340 _dereq_('./components/images'),
36341 _dereq_('./components/updatemenus'),
36342 _dereq_('./components/sliders'),
36343 _dereq_('./components/rangeslider'),
36344 _dereq_('./components/rangeselector'),
36345 _dereq_('./components/grid'),
36346 _dereq_('./components/errorbars'),
36347 _dereq_('./components/colorscale'),
36348 _dereq_('./components/colorbar')
36349]);
36350
36351// locales en and en-US are required for default behavior
36352register([
36353 _dereq_('./locale-en'),
36354 _dereq_('./locale-en-us')
36355]);
36356
36357// locales that are present in the window should be loaded
36358if(window.PlotlyLocales && Array.isArray(window.PlotlyLocales)) {
36359 register(window.PlotlyLocales);
36360 delete window.PlotlyLocales;
36361}
36362
36363// plot icons
36364exports.Icons = _dereq_('./fonts/ploticon');
36365
36366// unofficial 'beta' plot methods, use at your own risk
36367exports.Plots = _dereq_('./plots/plots');
36368exports.Fx = _dereq_('./components/fx');
36369exports.Snapshot = _dereq_('./snapshot');
36370exports.PlotSchema = _dereq_('./plot_api/plot_schema');
36371exports.Queue = _dereq_('./lib/queue');
36372
36373// export d3 used in the bundle
36374exports.d3 = _dereq_('d3');
36375
36376},{"../build/plotcss":1,"./components/annotations":45,"./components/annotations3d":50,"./components/colorbar":58,"./components/colorscale":64,"./components/errorbars":80,"./components/fx":92,"./components/grid":96,"./components/images":101,"./components/legend":109,"./components/rangeselector":120,"./components/rangeslider":127,"./components/shapes":141,"./components/sliders":146,"./components/updatemenus":152,"./fonts/mathjax_config":161,"./fonts/ploticon":162,"./lib/queue":192,"./locale-en":203,"./locale-en-us":202,"./plot_api":207,"./plot_api/plot_schema":211,"./plots/plots":256,"./registry":269,"./snapshot":274,"./traces/scatter":401,"./version":435,"d3":16,"es6-promise":17}],161:[function(_dereq_,module,exports){
36377/**
36378* Copyright 2012-2020, Plotly, Inc.
36379* All rights reserved.
36380*
36381* This source code is licensed under the MIT license found in the
36382* LICENSE file in the root directory of this source tree.
36383*/
36384
36385'use strict';
36386
36387/* global MathJax:false */
36388
36389module.exports = function() {
36390 if(typeof MathJax !== 'undefined') {
36391 var globalConfig = (window.PlotlyConfig || {}).MathJaxConfig !== 'local';
36392
36393 if(globalConfig) {
36394 MathJax.Hub.Config({
36395 messageStyle: 'none',
36396 skipStartupTypeset: true,
36397 displayAlign: 'left',
36398 tex2jax: {
36399 inlineMath: [['$', '$'], ['\\(', '\\)']]
36400 }
36401 });
36402 MathJax.Hub.Configured();
36403 }
36404 }
36405};
36406
36407},{}],162:[function(_dereq_,module,exports){
36408/**
36409* Copyright 2012-2020, Plotly, Inc.
36410* All rights reserved.
36411*
36412* This source code is licensed under the MIT license found in the
36413* LICENSE file in the root directory of this source tree.
36414*/
36415
36416'use strict';
36417
36418module.exports = {
36419 'undo': {
36420 'width': 857.1,
36421 'height': 1000,
36422 'path': 'm857 350q0-87-34-166t-91-137-137-92-166-34q-96 0-183 41t-147 114q-4 6-4 13t5 11l76 77q6 5 14 5 9-1 13-7 41-53 100-82t126-29q58 0 110 23t92 61 61 91 22 111-22 111-61 91-92 61-110 23q-55 0-105-20t-90-57l77-77q17-16 8-38-10-23-33-23h-250q-15 0-25 11t-11 25v250q0 24 22 33 22 10 39-8l72-72q60 57 137 88t159 31q87 0 166-34t137-92 91-137 34-166z',
36423 'transform': 'matrix(1 0 0 -1 0 850)'
36424 },
36425 'home': {
36426 'width': 928.6,
36427 'height': 1000,
36428 'path': 'm786 296v-267q0-15-11-26t-25-10h-214v214h-143v-214h-214q-15 0-25 10t-11 26v267q0 1 0 2t0 2l321 264 321-264q1-1 1-4z m124 39l-34-41q-5-5-12-6h-2q-7 0-12 3l-386 322-386-322q-7-4-13-4-7 2-12 7l-35 41q-4 5-3 13t6 12l401 334q18 15 42 15t43-15l136-114v109q0 8 5 13t13 5h107q8 0 13-5t5-13v-227l122-102q5-5 6-12t-4-13z',
36429 'transform': 'matrix(1 0 0 -1 0 850)'
36430 },
36431 'camera-retro': {
36432 'width': 1000,
36433 'height': 1000,
36434 'path': 'm518 386q0 8-5 13t-13 5q-37 0-63-27t-26-63q0-8 5-13t13-5 12 5 5 13q0 23 16 38t38 16q8 0 13 5t5 13z m125-73q0-59-42-101t-101-42-101 42-42 101 42 101 101 42 101-42 42-101z m-572-320h858v71h-858v-71z m643 320q0 89-62 152t-152 62-151-62-63-152 63-151 151-63 152 63 62 151z m-571 358h214v72h-214v-72z m-72-107h858v143h-462l-36-71h-360v-72z m929 143v-714q0-30-21-51t-50-21h-858q-29 0-50 21t-21 51v714q0 30 21 51t50 21h858q29 0 50-21t21-51z',
36435 'transform': 'matrix(1 0 0 -1 0 850)'
36436 },
36437 'zoombox': {
36438 'width': 1000,
36439 'height': 1000,
36440 'path': 'm1000-25l-250 251c40 63 63 138 63 218 0 224-182 406-407 406-224 0-406-182-406-406s183-406 407-406c80 0 155 22 218 62l250-250 125 125z m-812 250l0 438 437 0 0-438-437 0z m62 375l313 0 0-312-313 0 0 312z',
36441 'transform': 'matrix(1 0 0 -1 0 850)'
36442 },
36443 'pan': {
36444 'width': 1000,
36445 'height': 1000,
36446 'path': 'm1000 350l-187 188 0-125-250 0 0 250 125 0-188 187-187-187 125 0 0-250-250 0 0 125-188-188 186-187 0 125 252 0 0-250-125 0 187-188 188 188-125 0 0 250 250 0 0-126 187 188z',
36447 'transform': 'matrix(1 0 0 -1 0 850)'
36448 },
36449 'zoom_plus': {
36450 'width': 875,
36451 'height': 1000,
36452 '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',
36453 'transform': 'matrix(1 0 0 -1 0 850)'
36454 },
36455 'zoom_minus': {
36456 'width': 875,
36457 'height': 1000,
36458 'path': 'm0 788l0-876 875 0 0 876-875 0z m688-500l-500 0 0 125 500 0 0-125z',
36459 'transform': 'matrix(1 0 0 -1 0 850)'
36460 },
36461 'autoscale': {
36462 'width': 1000,
36463 'height': 1000,
36464 'path': 'm250 850l-187 0-63 0 0-62 0-188 63 0 0 188 187 0 0 62z m688 0l-188 0 0-62 188 0 0-188 62 0 0 188 0 62-62 0z m-875-938l0 188-63 0 0-188 0-62 63 0 187 0 0 62-187 0z m875 188l0-188-188 0 0-62 188 0 62 0 0 62 0 188-62 0z m-125 188l-1 0-93-94-156 156 156 156 92-93 2 0 0 250-250 0 0-2 93-92-156-156-156 156 94 92 0 2-250 0 0-250 0 0 93 93 157-156-157-156-93 94 0 0 0-250 250 0 0 0-94 93 156 157 156-157-93-93 0 0 250 0 0 250z',
36465 'transform': 'matrix(1 0 0 -1 0 850)'
36466 },
36467 'tooltip_basic': {
36468 'width': 1500,
36469 'height': 1000,
36470 'path': 'm375 725l0 0-375-375 375-374 0-1 1125 0 0 750-1125 0z',
36471 'transform': 'matrix(1 0 0 -1 0 850)'
36472 },
36473 'tooltip_compare': {
36474 'width': 1125,
36475 'height': 1000,
36476 'path': 'm187 786l0 2-187-188 188-187 0 0 937 0 0 373-938 0z m0-499l0 1-187-188 188-188 0 0 937 0 0 376-938-1z',
36477 'transform': 'matrix(1 0 0 -1 0 850)'
36478 },
36479 'plotlylogo': {
36480 'width': 1542,
36481 'height': 1000,
36482 'path': 'm0-10h182v-140h-182v140z m228 146h183v-286h-183v286z m225 714h182v-1000h-182v1000z m225-285h182v-715h-182v715z m225 142h183v-857h-183v857z m231-428h182v-429h-182v429z m225-291h183v-138h-183v138z',
36483 'transform': 'matrix(1 0 0 -1 0 850)'
36484 },
36485 'z-axis': {
36486 'width': 1000,
36487 'height': 1000,
36488 'path': 'm833 5l-17 108v41l-130-65 130-66c0 0 0 38 0 39 0-1 36-14 39-25 4-15-6-22-16-30-15-12-39-16-56-20-90-22-187-23-279-23-261 0-341 34-353 59 3 60 228 110 228 110-140-8-351-35-351-116 0-120 293-142 474-142 155 0 477 22 477 142 0 50-74 79-163 96z m-374 94c-58-5-99-21-99-40 0-24 65-43 144-43 79 0 143 19 143 43 0 19-42 34-98 40v216h87l-132 135-133-135h88v-216z m167 515h-136v1c16 16 31 34 46 52l84 109v54h-230v-71h124v-1c-16-17-28-32-44-51l-89-114v-51h245v72z',
36489 'transform': 'matrix(1 0 0 -1 0 850)'
36490 },
36491 '3d_rotate': {
36492 'width': 1000,
36493 'height': 1000,
36494 'path': 'm922 660c-5 4-9 7-14 11-359 263-580-31-580-31l-102 28 58-400c0 1 1 1 2 2 118 108 351 249 351 249s-62 27-100 42c88 83 222 183 347 122 16-8 30-17 44-27-2 1-4 2-6 4z m36-329c0 0 64 229-88 296-62 27-124 14-175-11 157-78 225-208 249-266 8-19 11-31 11-31 2 5 6 15 11 32-5-13-8-20-8-20z m-775-239c70-31 117-50 198-32-121 80-199 346-199 346l-96-15-58-12c0 0 55-226 155-287z m603 133l-317-139c0 0 4-4 19-14 7-5 24-15 24-15s-177-147-389 4c235-287 536-112 536-112l31-22 100 299-4-1z m-298-153c6-4 14-9 24-15 0 0-17 10-24 15z',
36495 'transform': 'matrix(1 0 0 -1 0 850)'
36496 },
36497 'camera': {
36498 'width': 1000,
36499 'height': 1000,
36500 'path': 'm500 450c-83 0-150-67-150-150 0-83 67-150 150-150 83 0 150 67 150 150 0 83-67 150-150 150z m400 150h-120c-16 0-34 13-39 29l-31 93c-6 15-23 28-40 28h-340c-16 0-34-13-39-28l-31-94c-6-15-23-28-40-28h-120c-55 0-100-45-100-100v-450c0-55 45-100 100-100h800c55 0 100 45 100 100v450c0 55-45 100-100 100z m-400-550c-138 0-250 112-250 250 0 138 112 250 250 250 138 0 250-112 250-250 0-138-112-250-250-250z m365 380c-19 0-35 16-35 35 0 19 16 35 35 35 19 0 35-16 35-35 0-19-16-35-35-35z',
36501 'transform': 'matrix(1 0 0 -1 0 850)'
36502 },
36503 'movie': {
36504 'width': 1000,
36505 'height': 1000,
36506 'path': 'm938 413l-188-125c0 37-17 71-44 94 64 38 107 107 107 187 0 121-98 219-219 219-121 0-219-98-219-219 0-61 25-117 66-156h-115c30 33 49 76 49 125 0 103-84 187-187 187s-188-84-188-187c0-57 26-107 65-141-38-22-65-62-65-109v-250c0-70 56-126 125-126h500c69 0 125 56 125 126l188-126c34 0 62 28 62 63v375c0 35-28 63-62 63z m-750 0c-69 0-125 56-125 125s56 125 125 125 125-56 125-125-56-125-125-125z m406-1c-87 0-157 70-157 157 0 86 70 156 157 156s156-70 156-156-70-157-156-157z',
36507 'transform': 'matrix(1 0 0 -1 0 850)'
36508 },
36509 'question': {
36510 'width': 857.1,
36511 'height': 1000,
36512 'path': 'm500 82v107q0 8-5 13t-13 5h-107q-8 0-13-5t-5-13v-107q0-8 5-13t13-5h107q8 0 13 5t5 13z m143 375q0 49-31 91t-77 65-95 23q-136 0-207-119-9-14 4-24l74-55q4-4 10-4 9 0 14 7 30 38 48 51 19 14 48 14 27 0 48-15t21-33q0-21-11-34t-38-25q-35-16-65-48t-29-70v-20q0-8 5-13t13-5h107q8 0 13 5t5 13q0 10 12 27t30 28q18 10 28 16t25 19 25 27 16 34 7 45z m214-107q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z',
36513 'transform': 'matrix(1 0 0 -1 0 850)'
36514 },
36515 'disk': {
36516 'width': 857.1,
36517 'height': 1000,
36518 'path': 'm214-7h429v214h-429v-214z m500 0h72v500q0 8-6 21t-11 20l-157 156q-5 6-19 12t-22 5v-232q0-22-15-38t-38-16h-322q-22 0-37 16t-16 38v232h-72v-714h72v232q0 22 16 38t37 16h465q22 0 38-16t15-38v-232z m-214 518v178q0 8-5 13t-13 5h-107q-7 0-13-5t-5-13v-178q0-8 5-13t13-5h107q7 0 13 5t5 13z m357-18v-518q0-22-15-38t-38-16h-750q-23 0-38 16t-16 38v750q0 22 16 38t38 16h517q23 0 50-12t42-26l156-157q16-15 27-42t11-49z',
36519 'transform': 'matrix(1 0 0 -1 0 850)'
36520 },
36521 'drawopenpath': {
36522 'width': 70,
36523 'height': 70,
36524 'path': 'M33.21,85.65a7.31,7.31,0,0,1-2.59-.48c-8.16-3.11-9.27-19.8-9.88-41.3-.1-3.58-.19-6.68-.35-9-.15-2.1-.67-3.48-1.43-3.79-2.13-.88-7.91,2.32-12,5.86L3,32.38c1.87-1.64,11.55-9.66,18.27-6.9,2.13.87,4.75,3.14,5.17,9,.17,2.43.26,5.59.36,9.25a224.17,224.17,0,0,0,1.5,23.4c1.54,10.76,4,12.22,4.48,12.4.84.32,2.79-.46,5.76-3.59L43,80.07C41.53,81.57,37.68,85.64,33.21,85.65ZM74.81,69a11.34,11.34,0,0,0,6.09-6.72L87.26,44.5,74.72,32,56.9,38.35c-2.37.86-5.57,3.42-6.61,6L38.65,72.14l8.42,8.43ZM55,46.27a7.91,7.91,0,0,1,3.64-3.17l14.8-5.3,8,8L76.11,60.6l-.06.19a6.37,6.37,0,0,1-3,3.43L48.25,74.59,44.62,71Zm16.57,7.82A6.9,6.9,0,1,0,64.64,61,6.91,6.91,0,0,0,71.54,54.09Zm-4.05,0a2.85,2.85,0,1,1-2.85-2.85A2.86,2.86,0,0,1,67.49,54.09Zm-4.13,5.22L60.5,56.45,44.26,72.7l2.86,2.86ZM97.83,35.67,84.14,22l-8.57,8.57L89.26,44.24Zm-13.69-8,8,8-2.85,2.85-8-8Z',
36525 'transform': 'matrix(1 0 0 1 -15 -15)'
36526 },
36527 'drawclosedpath': {
36528 'width': 90,
36529 'height': 90,
36530 'path': 'M88.41,21.12a26.56,26.56,0,0,0-36.18,0l-2.07,2-2.07-2a26.57,26.57,0,0,0-36.18,0,23.74,23.74,0,0,0,0,34.8L48,90.12a3.22,3.22,0,0,0,4.42,0l36-34.21a23.73,23.73,0,0,0,0-34.79ZM84,51.24,50.16,83.35,16.35,51.25a17.28,17.28,0,0,1,0-25.47,20,20,0,0,1,27.3,0l4.29,4.07a3.23,3.23,0,0,0,4.44,0l4.29-4.07a20,20,0,0,1,27.3,0,17.27,17.27,0,0,1,0,25.46ZM66.76,47.68h-33v6.91h33ZM53.35,35H46.44V68h6.91Z',
36531 'transform': 'matrix(1 0 0 1 -5 -5)'
36532 },
36533 'lasso': {
36534 'width': 1031,
36535 'height': 1000,
36536 'path': 'm1018 538c-36 207-290 336-568 286-277-48-473-256-436-463 10-57 36-108 76-151-13-66 11-137 68-183 34-28 75-41 114-42l-55-70 0 0c-2-1-3-2-4-3-10-14-8-34 5-45 14-11 34-8 45 4 1 1 2 3 2 5l0 0 113 140c16 11 31 24 45 40 4 3 6 7 8 11 48-3 100 0 151 9 278 48 473 255 436 462z m-624-379c-80 14-149 48-197 96 42 42 109 47 156 9 33-26 47-66 41-105z m-187-74c-19 16-33 37-39 60 50-32 109-55 174-68-42-25-95-24-135 8z m360 75c-34-7-69-9-102-8 8 62-16 128-68 170-73 59-175 54-244-5-9 20-16 40-20 61-28 159 121 317 333 354s407-60 434-217c28-159-121-318-333-355z',
36537 'transform': 'matrix(1 0 0 -1 0 850)'
36538 },
36539 'selectbox': {
36540 'width': 1000,
36541 'height': 1000,
36542 'path': 'm0 850l0-143 143 0 0 143-143 0z m286 0l0-143 143 0 0 143-143 0z m285 0l0-143 143 0 0 143-143 0z m286 0l0-143 143 0 0 143-143 0z m-857-286l0-143 143 0 0 143-143 0z m857 0l0-143 143 0 0 143-143 0z m-857-285l0-143 143 0 0 143-143 0z m857 0l0-143 143 0 0 143-143 0z m-857-286l0-143 143 0 0 143-143 0z m286 0l0-143 143 0 0 143-143 0z m285 0l0-143 143 0 0 143-143 0z m286 0l0-143 143 0 0 143-143 0z',
36543 'transform': 'matrix(1 0 0 -1 0 850)'
36544 },
36545 'drawline': {
36546 'width': 70,
36547 'height': 70,
36548 'path': 'M60.64,62.3a11.29,11.29,0,0,0,6.09-6.72l6.35-17.72L60.54,25.31l-17.82,6.4c-2.36.86-5.57,3.41-6.6,6L24.48,65.5l8.42,8.42ZM40.79,39.63a7.89,7.89,0,0,1,3.65-3.17l14.79-5.31,8,8L61.94,54l-.06.19a6.44,6.44,0,0,1-3,3.43L34.07,68l-3.62-3.63Zm16.57,7.81a6.9,6.9,0,1,0-6.89,6.9A6.9,6.9,0,0,0,57.36,47.44Zm-4,0a2.86,2.86,0,1,1-2.85-2.85A2.86,2.86,0,0,1,53.32,47.44Zm-4.13,5.22L46.33,49.8,30.08,66.05l2.86,2.86ZM83.65,29,70,15.34,61.4,23.9,75.09,37.59ZM70,21.06l8,8-2.84,2.85-8-8ZM87,80.49H10.67V87H87Z',
36549 'transform': 'matrix(1 0 0 1 -15 -15)'
36550 },
36551 'drawrect': {
36552 'width': 80,
36553 'height': 80,
36554 'path': 'M78,22V79H21V22H78m9-9H12V88H87V13ZM68,46.22H31V54H68ZM53,32H45.22V69H53Z',
36555 'transform': 'matrix(1 0 0 1 -10 -10)'
36556 },
36557 'drawcircle': {
36558 'width': 80,
36559 'height': 80,
36560 'path': 'M50,84.72C26.84,84.72,8,69.28,8,50.3S26.84,15.87,50,15.87,92,31.31,92,50.3,73.16,84.72,50,84.72Zm0-60.59c-18.6,0-33.74,11.74-33.74,26.17S31.4,76.46,50,76.46,83.74,64.72,83.74,50.3,68.6,24.13,50,24.13Zm17.15,22h-34v7.11h34Zm-13.8-13H46.24v34h7.11Z',
36561 'transform': 'matrix(1 0 0 1 -10 -10)'
36562 },
36563 'eraseshape': {
36564 'width': 80,
36565 'height': 80,
36566 'path': 'M82.77,78H31.85L6,49.57,31.85,21.14H82.77a8.72,8.72,0,0,1,8.65,8.77V69.24A8.72,8.72,0,0,1,82.77,78ZM35.46,69.84H82.77a.57.57,0,0,0,.49-.6V29.91a.57.57,0,0,0-.49-.61H35.46L17,49.57Zm32.68-34.7-24,24,5,5,24-24Zm-19,.53-5,5,24,24,5-5Z',
36567 'transform': 'matrix(1 0 0 1 -10 -10)'
36568 },
36569 'spikeline': {
36570 'width': 1000,
36571 'height': 1000,
36572 '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',
36573 'transform': 'matrix(1.5 0 0 -1.5 0 850)'
36574 },
36575 'pencil': {
36576 'width': 1792,
36577 'height': 1792,
36578 'path': 'M491 1536l91-91-235-235-91 91v107h128v128h107zm523-928q0-22-22-22-10 0-17 7l-542 542q-7 7-7 17 0 22 22 22 10 0 17-7l542-542q7-7 7-17zm-54-192l416 416-832 832h-416v-416zm683 96q0 53-37 90l-166 166-416-416 166-165q36-38 90-38 53 0 91 38l235 234q37 39 37 91z',
36579 'transform': 'matrix(1 0 0 1 0 1)'
36580 },
36581 'newplotlylogo': {
36582 'name': 'newplotlylogo',
36583 'svg': '<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 132 132\'><defs><style>.cls-1 {fill: #3f4f75;} .cls-2 {fill: #80cfbe;} .cls-3 {fill: #fff;}</style></defs><title>plotly-logomark</title><g id=\'symbol\'><rect class=\'cls-1\' width=\'132\' height=\'132\' rx=\'6\' ry=\'6\'/><circle class=\'cls-2\' cx=\'78\' cy=\'54\' r=\'6\'/><circle class=\'cls-2\' cx=\'102\' cy=\'30\' r=\'6\'/><circle class=\'cls-2\' cx=\'78\' cy=\'30\' r=\'6\'/><circle class=\'cls-2\' cx=\'54\' cy=\'30\' r=\'6\'/><circle class=\'cls-2\' cx=\'30\' cy=\'30\' r=\'6\'/><circle class=\'cls-2\' cx=\'30\' cy=\'54\' r=\'6\'/><path class=\'cls-3\' d=\'M30,72a6,6,0,0,0-6,6v24a6,6,0,0,0,12,0V78A6,6,0,0,0,30,72Z\'/><path class=\'cls-3\' d=\'M78,72a6,6,0,0,0-6,6v24a6,6,0,0,0,12,0V78A6,6,0,0,0,78,72Z\'/><path class=\'cls-3\' d=\'M54,48a6,6,0,0,0-6,6v48a6,6,0,0,0,12,0V54A6,6,0,0,0,54,48Z\'/><path class=\'cls-3\' d=\'M102,48a6,6,0,0,0-6,6v48a6,6,0,0,0,12,0V54A6,6,0,0,0,102,48Z\'/></g></svg>'
36584 }
36585};
36586
36587},{}],163:[function(_dereq_,module,exports){
36588/**
36589* Copyright 2012-2020, Plotly, Inc.
36590* All rights reserved.
36591*
36592* This source code is licensed under the MIT license found in the
36593* LICENSE file in the root directory of this source tree.
36594*/
36595
36596
36597'use strict';
36598
36599
36600/**
36601 * Determine the position anchor property of x/y xanchor/yanchor components.
36602 *
36603 * - values < 1/3 align the low side at that fraction,
36604 * - values [1/3, 2/3] align the center at that fraction,
36605 * - values > 2/3 align the right at that fraction.
36606 */
36607
36608
36609exports.isLeftAnchor = function isLeftAnchor(opts) {
36610 return (
36611 opts.xanchor === 'left' ||
36612 (opts.xanchor === 'auto' && opts.x <= 1 / 3)
36613 );
36614};
36615
36616exports.isCenterAnchor = function isCenterAnchor(opts) {
36617 return (
36618 opts.xanchor === 'center' ||
36619 (opts.xanchor === 'auto' && opts.x > 1 / 3 && opts.x < 2 / 3)
36620 );
36621};
36622
36623exports.isRightAnchor = function isRightAnchor(opts) {
36624 return (
36625 opts.xanchor === 'right' ||
36626 (opts.xanchor === 'auto' && opts.x >= 2 / 3)
36627 );
36628};
36629
36630exports.isTopAnchor = function isTopAnchor(opts) {
36631 return (
36632 opts.yanchor === 'top' ||
36633 (opts.yanchor === 'auto' && opts.y >= 2 / 3)
36634 );
36635};
36636
36637exports.isMiddleAnchor = function isMiddleAnchor(opts) {
36638 return (
36639 opts.yanchor === 'middle' ||
36640 (opts.yanchor === 'auto' && opts.y > 1 / 3 && opts.y < 2 / 3)
36641 );
36642};
36643
36644exports.isBottomAnchor = function isBottomAnchor(opts) {
36645 return (
36646 opts.yanchor === 'bottom' ||
36647 (opts.yanchor === 'auto' && opts.y <= 1 / 3)
36648 );
36649};
36650
36651},{}],164:[function(_dereq_,module,exports){
36652/**
36653* Copyright 2012-2020, Plotly, Inc.
36654* All rights reserved.
36655*
36656* This source code is licensed under the MIT license found in the
36657* LICENSE file in the root directory of this source tree.
36658*/
36659
36660'use strict';
36661
36662var modModule = _dereq_('./mod');
36663var mod = modModule.mod;
36664var modHalf = modModule.modHalf;
36665
36666var PI = Math.PI;
36667var twoPI = 2 * PI;
36668
36669function deg2rad(deg) { return deg / 180 * PI; }
36670
36671function rad2deg(rad) { return rad / PI * 180; }
36672
36673/**
36674 * is sector a full circle?
36675 * ... this comes up a lot in SVG path-drawing routines
36676 *
36677 * N.B. we consider all sectors that span more that 2pi 'full' circles
36678 *
36679 * @param {2-item array} aBnds : angular bounds in *radians*
36680 * @return {boolean}
36681 */
36682function isFullCircle(aBnds) {
36683 return Math.abs(aBnds[1] - aBnds[0]) > twoPI - 1e-14;
36684}
36685
36686/**
36687 * angular delta between angle 'a' and 'b'
36688 * solution taken from: https://stackoverflow.com/a/2007279
36689 *
36690 * @param {number} a : first angle in *radians*
36691 * @param {number} b : second angle in *radians*
36692 * @return {number} angular delta in *radians*
36693 */
36694function angleDelta(a, b) {
36695 return modHalf(b - a, twoPI);
36696}
36697
36698/**
36699 * angular distance between angle 'a' and 'b'
36700 *
36701 * @param {number} a : first angle in *radians*
36702 * @param {number} b : second angle in *radians*
36703 * @return {number} angular distance in *radians*
36704 */
36705function angleDist(a, b) {
36706 return Math.abs(angleDelta(a, b));
36707}
36708
36709/**
36710 * is angle inside sector?
36711 *
36712 * @param {number} a : angle to test in *radians*
36713 * @param {2-item array} aBnds : sector's angular bounds in *radians*
36714 * @param {boolean}
36715 */
36716function isAngleInsideSector(a, aBnds) {
36717 if(isFullCircle(aBnds)) return true;
36718
36719 var s0, s1;
36720
36721 if(aBnds[0] < aBnds[1]) {
36722 s0 = aBnds[0];
36723 s1 = aBnds[1];
36724 } else {
36725 s0 = aBnds[1];
36726 s1 = aBnds[0];
36727 }
36728
36729 s0 = mod(s0, twoPI);
36730 s1 = mod(s1, twoPI);
36731 if(s0 > s1) s1 += twoPI;
36732
36733 var a0 = mod(a, twoPI);
36734 var a1 = a0 + twoPI;
36735
36736 return (a0 >= s0 && a0 <= s1) || (a1 >= s0 && a1 <= s1);
36737}
36738
36739/**
36740 * is pt (r,a) inside sector?
36741 *
36742 * @param {number} r : pt's radial coordinate
36743 * @param {number} a : pt's angular coordinate in *radians*
36744 * @param {2-item array} rBnds : sector's radial bounds
36745 * @param {2-item array} aBnds : sector's angular bounds in *radians*
36746 * @return {boolean}
36747 */
36748function isPtInsideSector(r, a, rBnds, aBnds) {
36749 if(!isAngleInsideSector(a, aBnds)) return false;
36750
36751 var r0, r1;
36752
36753 if(rBnds[0] < rBnds[1]) {
36754 r0 = rBnds[0];
36755 r1 = rBnds[1];
36756 } else {
36757 r0 = rBnds[1];
36758 r1 = rBnds[0];
36759 }
36760
36761 return r >= r0 && r <= r1;
36762}
36763
36764// common to pathArc, pathSector and pathAnnulus
36765function _path(r0, r1, a0, a1, cx, cy, isClosed) {
36766 cx = cx || 0;
36767 cy = cy || 0;
36768
36769 var isCircle = isFullCircle([a0, a1]);
36770 var aStart, aMid, aEnd;
36771 var rStart, rEnd;
36772
36773 if(isCircle) {
36774 aStart = 0;
36775 aMid = PI;
36776 aEnd = twoPI;
36777 } else {
36778 if(a0 < a1) {
36779 aStart = a0;
36780 aEnd = a1;
36781 } else {
36782 aStart = a1;
36783 aEnd = a0;
36784 }
36785 }
36786
36787 if(r0 < r1) {
36788 rStart = r0;
36789 rEnd = r1;
36790 } else {
36791 rStart = r1;
36792 rEnd = r0;
36793 }
36794
36795 // N.B. svg coordinates here, where y increases downward
36796 function pt(r, a) {
36797 return [r * Math.cos(a) + cx, cy - r * Math.sin(a)];
36798 }
36799
36800 var largeArc = Math.abs(aEnd - aStart) <= PI ? 0 : 1;
36801 function arc(r, a, cw) {
36802 return 'A' + [r, r] + ' ' + [0, largeArc, cw] + ' ' + pt(r, a);
36803 }
36804
36805 var p;
36806
36807 if(isCircle) {
36808 if(rStart === null) {
36809 p = 'M' + pt(rEnd, aStart) +
36810 arc(rEnd, aMid, 0) +
36811 arc(rEnd, aEnd, 0) + 'Z';
36812 } else {
36813 p = 'M' + pt(rStart, aStart) +
36814 arc(rStart, aMid, 0) +
36815 arc(rStart, aEnd, 0) + 'Z' +
36816 'M' + pt(rEnd, aStart) +
36817 arc(rEnd, aMid, 1) +
36818 arc(rEnd, aEnd, 1) + 'Z';
36819 }
36820 } else {
36821 if(rStart === null) {
36822 p = 'M' + pt(rEnd, aStart) + arc(rEnd, aEnd, 0);
36823 if(isClosed) p += 'L0,0Z';
36824 } else {
36825 p = 'M' + pt(rStart, aStart) +
36826 'L' + pt(rEnd, aStart) +
36827 arc(rEnd, aEnd, 0) +
36828 'L' + pt(rStart, aEnd) +
36829 arc(rStart, aStart, 1) + 'Z';
36830 }
36831 }
36832
36833 return p;
36834}
36835
36836/**
36837 * path an arc
36838 *
36839 * @param {number} r : radius
36840 * @param {number} a0 : first angular coordinate in *radians*
36841 * @param {number} a1 : second angular coordinate in *radians*
36842 * @param {number (optional)} cx : x coordinate of center
36843 * @param {number (optional)} cy : y coordinate of center
36844 * @return {string} svg path
36845 */
36846function pathArc(r, a0, a1, cx, cy) {
36847 return _path(null, r, a0, a1, cx, cy, 0);
36848}
36849
36850/**
36851 * path a sector
36852 *
36853 * @param {number} r : radius
36854 * @param {number} a0 : first angular coordinate in *radians*
36855 * @param {number} a1 : second angular coordinate in *radians*
36856 * @param {number (optional)} cx : x coordinate of center
36857 * @param {number (optional)} cy : y coordinate of center
36858 * @return {string} svg path
36859 */
36860function pathSector(r, a0, a1, cx, cy) {
36861 return _path(null, r, a0, a1, cx, cy, 1);
36862}
36863
36864/**
36865 * path an annulus
36866 *
36867 * @param {number} r0 : first radial coordinate
36868 * @param {number} r1 : second radial coordinate
36869 * @param {number} a0 : first angular coordinate in *radians*
36870 * @param {number} a1 : second angular coordinate in *radians*
36871 * @param {number (optional)} cx : x coordinate of center
36872 * @param {number (optional)} cy : y coordinate of center
36873 * @return {string} svg path
36874 */
36875function pathAnnulus(r0, r1, a0, a1, cx, cy) {
36876 return _path(r0, r1, a0, a1, cx, cy, 1);
36877}
36878
36879module.exports = {
36880 deg2rad: deg2rad,
36881 rad2deg: rad2deg,
36882 angleDelta: angleDelta,
36883 angleDist: angleDist,
36884 isFullCircle: isFullCircle,
36885 isAngleInsideSector: isAngleInsideSector,
36886 isPtInsideSector: isPtInsideSector,
36887 pathArc: pathArc,
36888 pathSector: pathSector,
36889 pathAnnulus: pathAnnulus
36890};
36891
36892},{"./mod":185}],165:[function(_dereq_,module,exports){
36893/**
36894* Copyright 2012-2020, Plotly, Inc.
36895* All rights reserved.
36896*
36897* This source code is licensed under the MIT license found in the
36898* LICENSE file in the root directory of this source tree.
36899*/
36900
36901'use strict';
36902
36903var isArray = Array.isArray;
36904
36905// IE9 fallbacks
36906
36907var ab = (typeof ArrayBuffer === 'undefined' || !ArrayBuffer.isView) ?
36908 {isView: function() { return false; }} :
36909 ArrayBuffer;
36910
36911var dv = (typeof DataView === 'undefined') ?
36912 function() {} :
36913 DataView;
36914
36915function isTypedArray(a) {
36916 return ab.isView(a) && !(a instanceof dv);
36917}
36918exports.isTypedArray = isTypedArray;
36919
36920function isArrayOrTypedArray(a) {
36921 return isArray(a) || isTypedArray(a);
36922}
36923exports.isArrayOrTypedArray = isArrayOrTypedArray;
36924
36925/*
36926 * Test whether an input object is 1D.
36927 *
36928 * Assumes we already know the object is an array.
36929 *
36930 * Looks only at the first element, if the dimensionality is
36931 * not consistent we won't figure that out here.
36932 */
36933function isArray1D(a) {
36934 return !isArrayOrTypedArray(a[0]);
36935}
36936exports.isArray1D = isArray1D;
36937
36938/*
36939 * Ensures an array has the right amount of storage space. If it doesn't
36940 * exist, it creates an array. If it does exist, it returns it if too
36941 * short or truncates it in-place.
36942 *
36943 * The goal is to just reuse memory to avoid a bit of excessive garbage
36944 * collection.
36945 */
36946exports.ensureArray = function(out, n) {
36947 // TODO: typed array support here? This is only used in
36948 // traces/carpet/compute_control_points
36949 if(!isArray(out)) out = [];
36950
36951 // If too long, truncate. (If too short, it will grow
36952 // automatically so we don't care about that case)
36953 out.length = n;
36954
36955 return out;
36956};
36957
36958/*
36959 * TypedArray-compatible concatenation of n arrays
36960 * if all arrays are the same type it will preserve that type,
36961 * otherwise it falls back on Array.
36962 * Also tries to avoid copying, in case one array has zero length
36963 * But never mutates an existing array
36964 */
36965exports.concat = function() {
36966 var args = [];
36967 var allArray = true;
36968 var totalLen = 0;
36969
36970 var _constructor, arg0, i, argi, posi, leni, out, j;
36971
36972 for(i = 0; i < arguments.length; i++) {
36973 argi = arguments[i];
36974 leni = argi.length;
36975 if(leni) {
36976 if(arg0) args.push(argi);
36977 else {
36978 arg0 = argi;
36979 posi = leni;
36980 }
36981
36982 if(isArray(argi)) {
36983 _constructor = false;
36984 } else {
36985 allArray = false;
36986 if(!totalLen) {
36987 _constructor = argi.constructor;
36988 } else if(_constructor !== argi.constructor) {
36989 // TODO: in principle we could upgrade here,
36990 // ie keep typed array but convert all to Float64Array?
36991 _constructor = false;
36992 }
36993 }
36994
36995 totalLen += leni;
36996 }
36997 }
36998
36999 if(!totalLen) return [];
37000 if(!args.length) return arg0;
37001
37002 if(allArray) return arg0.concat.apply(arg0, args);
37003 if(_constructor) {
37004 // matching typed arrays
37005 out = new _constructor(totalLen);
37006 out.set(arg0);
37007 for(i = 0; i < args.length; i++) {
37008 argi = args[i];
37009 out.set(argi, posi);
37010 posi += argi.length;
37011 }
37012 return out;
37013 }
37014
37015 // mismatched types or Array + typed
37016 out = new Array(totalLen);
37017 for(j = 0; j < arg0.length; j++) out[j] = arg0[j];
37018 for(i = 0; i < args.length; i++) {
37019 argi = args[i];
37020 for(j = 0; j < argi.length; j++) out[posi + j] = argi[j];
37021 posi += j;
37022 }
37023 return out;
37024};
37025
37026exports.maxRowLength = function(z) {
37027 return _rowLength(z, Math.max, 0);
37028};
37029
37030exports.minRowLength = function(z) {
37031 return _rowLength(z, Math.min, Infinity);
37032};
37033
37034function _rowLength(z, fn, len0) {
37035 if(isArrayOrTypedArray(z)) {
37036 if(isArrayOrTypedArray(z[0])) {
37037 var len = len0;
37038 for(var i = 0; i < z.length; i++) {
37039 len = fn(len, z[i].length);
37040 }
37041 return len;
37042 } else {
37043 return z.length;
37044 }
37045 }
37046 return 0;
37047}
37048
37049},{}],166:[function(_dereq_,module,exports){
37050/**
37051* Copyright 2012-2020, Plotly, Inc.
37052* All rights reserved.
37053*
37054* This source code is licensed under the MIT license found in the
37055* LICENSE file in the root directory of this source tree.
37056*/
37057
37058
37059'use strict';
37060
37061var isNumeric = _dereq_('fast-isnumeric');
37062
37063var BADNUM = _dereq_('../constants/numerical').BADNUM;
37064
37065// precompile for speed
37066var JUNK = /^['"%,$#\s']+|[, ]|['"%,$#\s']+$/g;
37067
37068/**
37069 * cleanNumber: remove common leading and trailing cruft
37070 * Always returns either a number or BADNUM.
37071 */
37072module.exports = function cleanNumber(v) {
37073 if(typeof v === 'string') {
37074 v = v.replace(JUNK, '');
37075 }
37076
37077 if(isNumeric(v)) return Number(v);
37078
37079 return BADNUM;
37080};
37081
37082},{"../constants/numerical":158,"fast-isnumeric":18}],167:[function(_dereq_,module,exports){
37083/**
37084* Copyright 2012-2020, Plotly, Inc.
37085* All rights reserved.
37086*
37087* This source code is licensed under the MIT license found in the
37088* LICENSE file in the root directory of this source tree.
37089*/
37090
37091'use strict';
37092
37093/**
37094 * Clear gl frame (if any). This is a common pattern as
37095 * we usually set `preserveDrawingBuffer: true` during
37096 * gl context creation (e.g. via `reglUtils.prepare`).
37097 *
37098 * @param {DOM node or object} gd : graph div object
37099 */
37100module.exports = function clearGlCanvases(gd) {
37101 var fullLayout = gd._fullLayout;
37102
37103 if(fullLayout._glcanvas && fullLayout._glcanvas.size()) {
37104 fullLayout._glcanvas.each(function(d) {
37105 if(d.regl) d.regl.clear({color: true, depth: true});
37106 });
37107 }
37108};
37109
37110},{}],168:[function(_dereq_,module,exports){
37111/**
37112* Copyright 2012-2020, Plotly, Inc.
37113* All rights reserved.
37114*
37115* This source code is licensed under the MIT license found in the
37116* LICENSE file in the root directory of this source tree.
37117*/
37118
37119'use strict';
37120
37121/**
37122 * Clear responsive handlers (if any).
37123 *
37124 * @param {DOM node or object} gd : graph div object
37125 */
37126module.exports = function clearResponsive(gd) {
37127 if(gd._responsiveChartHandler) {
37128 window.removeEventListener('resize', gd._responsiveChartHandler);
37129 delete gd._responsiveChartHandler;
37130 }
37131};
37132
37133},{}],169:[function(_dereq_,module,exports){
37134/**
37135* Copyright 2012-2020, Plotly, Inc.
37136* All rights reserved.
37137*
37138* This source code is licensed under the MIT license found in the
37139* LICENSE file in the root directory of this source tree.
37140*/
37141
37142'use strict';
37143
37144var isNumeric = _dereq_('fast-isnumeric');
37145var tinycolor = _dereq_('tinycolor2');
37146
37147var baseTraceAttrs = _dereq_('../plots/attributes');
37148var colorscales = _dereq_('../components/colorscale/scales');
37149var DESELECTDIM = _dereq_('../constants/interactions').DESELECTDIM;
37150
37151var nestedProperty = _dereq_('./nested_property');
37152var counterRegex = _dereq_('./regex').counter;
37153var modHalf = _dereq_('./mod').modHalf;
37154var isArrayOrTypedArray = _dereq_('./array').isArrayOrTypedArray;
37155
37156exports.valObjectMeta = {
37157 data_array: {
37158 // You can use *dflt=[] to force said array to exist though.
37159
37160
37161
37162 coerceFunction: function(v, propOut, dflt) {
37163 // TODO maybe `v: {type: 'float32', vals: [/* ... */]}` also
37164 if(isArrayOrTypedArray(v)) propOut.set(v);
37165 else if(dflt !== undefined) propOut.set(dflt);
37166 }
37167 },
37168 enumerated: {
37169
37170
37171
37172 coerceFunction: function(v, propOut, dflt, opts) {
37173 if(opts.coerceNumber) v = +v;
37174 if(opts.values.indexOf(v) === -1) propOut.set(dflt);
37175 else propOut.set(v);
37176 },
37177 validateFunction: function(v, opts) {
37178 if(opts.coerceNumber) v = +v;
37179
37180 var values = opts.values;
37181 for(var i = 0; i < values.length; i++) {
37182 var k = String(values[i]);
37183
37184 if((k.charAt(0) === '/' && k.charAt(k.length - 1) === '/')) {
37185 var regex = new RegExp(k.substr(1, k.length - 2));
37186 if(regex.test(v)) return true;
37187 } else if(v === values[i]) return true;
37188 }
37189 return false;
37190 }
37191 },
37192 'boolean': {
37193
37194
37195
37196 coerceFunction: function(v, propOut, dflt) {
37197 if(v === true || v === false) propOut.set(v);
37198 else propOut.set(dflt);
37199 }
37200 },
37201 number: {
37202
37203
37204
37205 coerceFunction: function(v, propOut, dflt, opts) {
37206 if(!isNumeric(v) ||
37207 (opts.min !== undefined && v < opts.min) ||
37208 (opts.max !== undefined && v > opts.max)) {
37209 propOut.set(dflt);
37210 } else propOut.set(+v);
37211 }
37212 },
37213 integer: {
37214
37215
37216
37217 coerceFunction: function(v, propOut, dflt, opts) {
37218 if(v % 1 || !isNumeric(v) ||
37219 (opts.min !== undefined && v < opts.min) ||
37220 (opts.max !== undefined && v > opts.max)) {
37221 propOut.set(dflt);
37222 } else propOut.set(+v);
37223 }
37224 },
37225 string: {
37226
37227
37228 // TODO 'values shouldn't be in there (edge case: 'dash' in Scatter)
37229
37230 coerceFunction: function(v, propOut, dflt, opts) {
37231 if(typeof v !== 'string') {
37232 var okToCoerce = (typeof v === 'number');
37233
37234 if(opts.strict === true || !okToCoerce) propOut.set(dflt);
37235 else propOut.set(String(v));
37236 } else if(opts.noBlank && !v) propOut.set(dflt);
37237 else propOut.set(v);
37238 }
37239 },
37240 color: {
37241
37242
37243
37244 coerceFunction: function(v, propOut, dflt) {
37245 if(tinycolor(v).isValid()) propOut.set(v);
37246 else propOut.set(dflt);
37247 }
37248 },
37249 colorlist: {
37250
37251
37252
37253 coerceFunction: function(v, propOut, dflt) {
37254 function isColor(color) {
37255 return tinycolor(color).isValid();
37256 }
37257 if(!Array.isArray(v) || !v.length) propOut.set(dflt);
37258 else if(v.every(isColor)) propOut.set(v);
37259 else propOut.set(dflt);
37260 }
37261 },
37262 colorscale: {
37263
37264
37265
37266 coerceFunction: function(v, propOut, dflt) {
37267 propOut.set(colorscales.get(v, dflt));
37268 }
37269 },
37270 angle: {
37271
37272
37273
37274 coerceFunction: function(v, propOut, dflt) {
37275 if(v === 'auto') propOut.set('auto');
37276 else if(!isNumeric(v)) propOut.set(dflt);
37277 else propOut.set(modHalf(+v, 360));
37278 }
37279 },
37280 subplotid: {
37281
37282
37283
37284 coerceFunction: function(v, propOut, dflt, opts) {
37285 var regex = opts.regex || counterRegex(dflt);
37286 if(typeof v === 'string' && regex.test(v)) {
37287 propOut.set(v);
37288 return;
37289 }
37290 propOut.set(dflt);
37291 },
37292 validateFunction: function(v, opts) {
37293 var dflt = opts.dflt;
37294
37295 if(v === dflt) return true;
37296 if(typeof v !== 'string') return false;
37297 if(counterRegex(dflt).test(v)) return true;
37298
37299 return false;
37300 }
37301 },
37302 flaglist: {
37303
37304
37305
37306 coerceFunction: function(v, propOut, dflt, opts) {
37307 if(typeof v !== 'string') {
37308 propOut.set(dflt);
37309 return;
37310 }
37311 if((opts.extras || []).indexOf(v) !== -1) {
37312 propOut.set(v);
37313 return;
37314 }
37315 var vParts = v.split('+');
37316 var i = 0;
37317 while(i < vParts.length) {
37318 var vi = vParts[i];
37319 if(opts.flags.indexOf(vi) === -1 || vParts.indexOf(vi) < i) {
37320 vParts.splice(i, 1);
37321 } else i++;
37322 }
37323 if(!vParts.length) propOut.set(dflt);
37324 else propOut.set(vParts.join('+'));
37325 }
37326 },
37327 any: {
37328
37329
37330
37331 coerceFunction: function(v, propOut, dflt) {
37332 if(v === undefined) propOut.set(dflt);
37333 else propOut.set(v);
37334 }
37335 },
37336 info_array: {
37337
37338
37339 // set `dimensions=2` for a 2D array or '1-2' for either
37340 // `items` may be a single object instead of an array, in which case
37341 // `freeLength` must be true.
37342 // if `dimensions='1-2'` and items is a 1D array, then the value can
37343 // either be a matching 1D array or an array of such matching 1D arrays
37344
37345 coerceFunction: function(v, propOut, dflt, opts) {
37346 // simplified coerce function just for array items
37347 function coercePart(v, opts, dflt) {
37348 var out;
37349 var propPart = {set: function(v) { out = v; }};
37350
37351 if(dflt === undefined) dflt = opts.dflt;
37352
37353 exports.valObjectMeta[opts.valType].coerceFunction(v, propPart, dflt, opts);
37354
37355 return out;
37356 }
37357
37358 var twoD = opts.dimensions === 2 || (opts.dimensions === '1-2' && Array.isArray(v) && Array.isArray(v[0]));
37359
37360 if(!Array.isArray(v)) {
37361 propOut.set(dflt);
37362 return;
37363 }
37364
37365 var items = opts.items;
37366 var vOut = [];
37367 var arrayItems = Array.isArray(items);
37368 var arrayItems2D = arrayItems && twoD && Array.isArray(items[0]);
37369 var innerItemsOnly = twoD && arrayItems && !arrayItems2D;
37370 var len = (arrayItems && !innerItemsOnly) ? items.length : v.length;
37371
37372 var i, j, row, item, len2, vNew;
37373
37374 dflt = Array.isArray(dflt) ? dflt : [];
37375
37376 if(twoD) {
37377 for(i = 0; i < len; i++) {
37378 vOut[i] = [];
37379 row = Array.isArray(v[i]) ? v[i] : [];
37380 if(innerItemsOnly) len2 = items.length;
37381 else if(arrayItems) len2 = items[i].length;
37382 else len2 = row.length;
37383
37384 for(j = 0; j < len2; j++) {
37385 if(innerItemsOnly) item = items[j];
37386 else if(arrayItems) item = items[i][j];
37387 else item = items;
37388
37389 vNew = coercePart(row[j], item, (dflt[i] || [])[j]);
37390 if(vNew !== undefined) vOut[i][j] = vNew;
37391 }
37392 }
37393 } else {
37394 for(i = 0; i < len; i++) {
37395 vNew = coercePart(v[i], arrayItems ? items[i] : items, dflt[i]);
37396 if(vNew !== undefined) vOut[i] = vNew;
37397 }
37398 }
37399
37400 propOut.set(vOut);
37401 },
37402 validateFunction: function(v, opts) {
37403 if(!Array.isArray(v)) return false;
37404
37405 var items = opts.items;
37406 var arrayItems = Array.isArray(items);
37407 var twoD = opts.dimensions === 2;
37408
37409 // when free length is off, input and declared lengths must match
37410 if(!opts.freeLength && v.length !== items.length) return false;
37411
37412 // valid when all input items are valid
37413 for(var i = 0; i < v.length; i++) {
37414 if(twoD) {
37415 if(!Array.isArray(v[i]) || (!opts.freeLength && v[i].length !== items[i].length)) {
37416 return false;
37417 }
37418 for(var j = 0; j < v[i].length; j++) {
37419 if(!validate(v[i][j], arrayItems ? items[i][j] : items)) {
37420 return false;
37421 }
37422 }
37423 } else if(!validate(v[i], arrayItems ? items[i] : items)) return false;
37424 }
37425
37426 return true;
37427 }
37428 }
37429};
37430
37431/**
37432 * Ensures that container[attribute] has a valid value.
37433 *
37434 * attributes[attribute] is an object with possible keys:
37435 * - valType: data_array, enumerated, boolean, ... as in valObjectMeta
37436 * - values: (enumerated only) array of allowed vals
37437 * - min, max: (number, integer only) inclusive bounds on allowed vals
37438 * either or both may be omitted
37439 * - dflt: if attribute is invalid or missing, use this default
37440 * if dflt is provided as an argument to lib.coerce it takes precedence
37441 * as a convenience, returns the value it finally set
37442 */
37443exports.coerce = function(containerIn, containerOut, attributes, attribute, dflt) {
37444 return _coerce(containerIn, containerOut, attributes, attribute, dflt).val;
37445};
37446
37447function _coerce(containerIn, containerOut, attributes, attribute, dflt, opts) {
37448 var shouldValidate = (opts || {}).shouldValidate;
37449
37450 var attr = nestedProperty(attributes, attribute).get();
37451 if(dflt === undefined) dflt = attr.dflt;
37452 var src = false;
37453
37454 var propIn = nestedProperty(containerIn, attribute);
37455 var propOut = nestedProperty(containerOut, attribute);
37456 var valIn = propIn.get();
37457
37458 var template = containerOut._template;
37459 if(valIn === undefined && template) {
37460 valIn = nestedProperty(template, attribute).get();
37461 src = (valIn !== undefined);
37462
37463 // already used the template value, so short-circuit the second check
37464 template = 0;
37465 }
37466
37467 /**
37468 * arrayOk: value MAY be an array, then we do no value checking
37469 * at this point, because it can be more complicated than the
37470 * individual form (eg. some array vals can be numbers, even if the
37471 * single values must be color strings)
37472 */
37473 if(attr.arrayOk && isArrayOrTypedArray(valIn)) {
37474 propOut.set(valIn);
37475 return {
37476 inp: valIn,
37477 val: valIn,
37478 src: true
37479 };
37480 }
37481
37482 var coerceFunction = exports.valObjectMeta[attr.valType].coerceFunction;
37483 coerceFunction(valIn, propOut, dflt, attr);
37484
37485 var valOut = propOut.get();
37486 src = (valOut !== undefined) && shouldValidate && validate(valIn, attr);
37487
37488 // in case v was provided but invalid, try the template again so it still
37489 // overrides the regular default
37490 if(template && valOut === dflt && !validate(valIn, attr)) {
37491 valIn = nestedProperty(template, attribute).get();
37492 coerceFunction(valIn, propOut, dflt, attr);
37493 valOut = propOut.get();
37494
37495 src = (valOut !== undefined) && shouldValidate && validate(valIn, attr);
37496 }
37497
37498 return {
37499 inp: valIn,
37500 val: valOut,
37501 src: src
37502 };
37503}
37504
37505/**
37506 * Variation on coerce
37507 * useful when setting an attribute to a valid value
37508 * can change the default for another attribute.
37509 *
37510 * Uses coerce to get attribute value if user input is valid,
37511 * returns attribute default if user input it not valid or
37512 * returns false if there is no user input.
37513 */
37514exports.coerce2 = function(containerIn, containerOut, attributes, attribute, dflt) {
37515 var out = _coerce(containerIn, containerOut, attributes, attribute, dflt, {
37516 shouldValidate: true
37517 });
37518 return (out.src && out.inp !== undefined) ? out.val : false;
37519};
37520
37521/*
37522 * Shortcut to coerce the three font attributes
37523 *
37524 * 'coerce' is a lib.coerce wrapper with implied first three arguments
37525 */
37526exports.coerceFont = function(coerce, attr, dfltObj) {
37527 var out = {};
37528
37529 dfltObj = dfltObj || {};
37530
37531 out.family = coerce(attr + '.family', dfltObj.family);
37532 out.size = coerce(attr + '.size', dfltObj.size);
37533 out.color = coerce(attr + '.color', dfltObj.color);
37534
37535 return out;
37536};
37537
37538/** Coerce shortcut for 'hoverinfo'
37539 * handling 1-vs-multi-trace dflt logic
37540 *
37541 * @param {object} traceIn : user trace object
37542 * @param {object} traceOut : full trace object (requires _module ref)
37543 * @param {object} layoutOut : full layout object (require _dataLength ref)
37544 * @return {any} : the coerced value
37545 */
37546exports.coerceHoverinfo = function(traceIn, traceOut, layoutOut) {
37547 var moduleAttrs = traceOut._module.attributes;
37548 var attrs = moduleAttrs.hoverinfo ? moduleAttrs : baseTraceAttrs;
37549
37550 var valObj = attrs.hoverinfo;
37551 var dflt;
37552
37553 if(layoutOut._dataLength === 1) {
37554 var flags = valObj.dflt === 'all' ?
37555 valObj.flags.slice() :
37556 valObj.dflt.split('+');
37557
37558 flags.splice(flags.indexOf('name'), 1);
37559 dflt = flags.join('+');
37560 }
37561
37562 return exports.coerce(traceIn, traceOut, attrs, 'hoverinfo', dflt);
37563};
37564
37565/** Coerce shortcut for [un]selected.marker.opacity,
37566 * which has special default logic, to ensure that it corresponds to the
37567 * default selection behavior while allowing to be overtaken by any other
37568 * [un]selected attribute.
37569 *
37570 * N.B. This must be called *after* coercing all the other [un]selected attrs,
37571 * to give the intended result.
37572 *
37573 * @param {object} traceOut : fullData item
37574 * @param {function} coerce : lib.coerce wrapper with implied first three arguments
37575 */
37576exports.coerceSelectionMarkerOpacity = function(traceOut, coerce) {
37577 if(!traceOut.marker) return;
37578
37579 var mo = traceOut.marker.opacity;
37580 // you can still have a `marker` container with no markers if there's text
37581 if(mo === undefined) return;
37582
37583 var smoDflt;
37584 var usmoDflt;
37585
37586 // Don't give [un]selected.marker.opacity a default value if
37587 // marker.opacity is an array: handle this during style step.
37588 //
37589 // Only give [un]selected.marker.opacity a default value if you don't
37590 // set any other [un]selected attributes.
37591 if(!isArrayOrTypedArray(mo) && !traceOut.selected && !traceOut.unselected) {
37592 smoDflt = mo;
37593 usmoDflt = DESELECTDIM * mo;
37594 }
37595
37596 coerce('selected.marker.opacity', smoDflt);
37597 coerce('unselected.marker.opacity', usmoDflt);
37598};
37599
37600function validate(value, opts) {
37601 var valObjectDef = exports.valObjectMeta[opts.valType];
37602
37603 if(opts.arrayOk && isArrayOrTypedArray(value)) return true;
37604
37605 if(valObjectDef.validateFunction) {
37606 return valObjectDef.validateFunction(value, opts);
37607 }
37608
37609 var failed = {};
37610 var out = failed;
37611 var propMock = { set: function(v) { out = v; } };
37612
37613 // 'failed' just something mutable that won't be === anything else
37614
37615 valObjectDef.coerceFunction(value, propMock, failed, opts);
37616 return out !== failed;
37617}
37618exports.validate = validate;
37619
37620},{"../components/colorscale/scales":67,"../constants/interactions":157,"../plots/attributes":219,"./array":165,"./mod":185,"./nested_property":186,"./regex":193,"fast-isnumeric":18,"tinycolor2":35}],170:[function(_dereq_,module,exports){
37621/**
37622* Copyright 2012-2020, Plotly, Inc.
37623* All rights reserved.
37624*
37625* This source code is licensed under the MIT license found in the
37626* LICENSE file in the root directory of this source tree.
37627*/
37628
37629
37630'use strict';
37631
37632var d3 = _dereq_('d3');
37633var isNumeric = _dereq_('fast-isnumeric');
37634
37635var Loggers = _dereq_('./loggers');
37636var mod = _dereq_('./mod').mod;
37637
37638var constants = _dereq_('../constants/numerical');
37639var BADNUM = constants.BADNUM;
37640var ONEDAY = constants.ONEDAY;
37641var ONEHOUR = constants.ONEHOUR;
37642var ONEMIN = constants.ONEMIN;
37643var ONESEC = constants.ONESEC;
37644var EPOCHJD = constants.EPOCHJD;
37645
37646var Registry = _dereq_('../registry');
37647
37648var utcFormat = d3.time.format.utc;
37649
37650var DATETIME_REGEXP = /^\s*(-?\d\d\d\d|\d\d)(-(\d?\d)(-(\d?\d)([ Tt]([01]?\d|2[0-3])(:([0-5]\d)(:([0-5]\d(\.\d+)?))?(Z|z|[+\-]\d\d:?\d\d)?)?)?)?)?\s*$/m;
37651// special regex for chinese calendars to support yyyy-mmi-dd etc for intercalary months
37652var DATETIME_REGEXP_CN = /^\s*(-?\d\d\d\d|\d\d)(-(\d?\di?)(-(\d?\d)([ Tt]([01]?\d|2[0-3])(:([0-5]\d)(:([0-5]\d(\.\d+)?))?(Z|z|[+\-]\d\d:?\d\d)?)?)?)?)?\s*$/m;
37653
37654// for 2-digit years, the first year we map them onto
37655var YFIRST = new Date().getFullYear() - 70;
37656
37657function isWorldCalendar(calendar) {
37658 return (
37659 calendar &&
37660 Registry.componentsRegistry.calendars &&
37661 typeof calendar === 'string' && calendar !== 'gregorian'
37662 );
37663}
37664
37665/*
37666 * dateTick0: get the canonical tick for this calendar
37667 *
37668 * bool sunday is for week ticks, shift it to a Sunday.
37669 */
37670exports.dateTick0 = function(calendar, sunday) {
37671 if(isWorldCalendar(calendar)) {
37672 return sunday ?
37673 Registry.getComponentMethod('calendars', 'CANONICAL_SUNDAY')[calendar] :
37674 Registry.getComponentMethod('calendars', 'CANONICAL_TICK')[calendar];
37675 } else {
37676 return sunday ? '2000-01-02' : '2000-01-01';
37677 }
37678};
37679
37680/*
37681 * dfltRange: for each calendar, give a valid default range
37682 */
37683exports.dfltRange = function(calendar) {
37684 if(isWorldCalendar(calendar)) {
37685 return Registry.getComponentMethod('calendars', 'DFLTRANGE')[calendar];
37686 } else {
37687 return ['2000-01-01', '2001-01-01'];
37688 }
37689};
37690
37691// is an object a javascript date?
37692exports.isJSDate = function(v) {
37693 return typeof v === 'object' && v !== null && typeof v.getTime === 'function';
37694};
37695
37696// The absolute limits of our date-time system
37697// This is a little weird: we use MIN_MS and MAX_MS in dateTime2ms
37698// but we use dateTime2ms to calculate them (after defining it!)
37699var MIN_MS, MAX_MS;
37700
37701/**
37702 * dateTime2ms - turn a date object or string s into milliseconds
37703 * (relative to 1970-01-01, per javascript standard)
37704 * optional calendar (string) to use a non-gregorian calendar
37705 *
37706 * Returns BADNUM if it doesn't find a date
37707 *
37708 * strings should have the form:
37709 *
37710 * -?YYYY-mm-dd<sep>HH:MM:SS.sss<tzInfo>?
37711 *
37712 * <sep>: space (our normal standard) or T or t (ISO-8601)
37713 * <tzInfo>: Z, z, or [+\-]HH:?MM and we THROW IT AWAY
37714 * this format comes from https://tools.ietf.org/html/rfc3339#section-5.6
37715 * but we allow it even with a space as the separator
37716 *
37717 * May truncate after any full field, and sss can be any length
37718 * even >3 digits, though javascript dates truncate to milliseconds,
37719 * we keep as much as javascript numeric precision can hold, but we only
37720 * report back up to 100 microsecond precision, because most dates support
37721 * this precision (close to 1970 support more, very far away support less)
37722 *
37723 * Expanded to support negative years to -9999 but you must always
37724 * give 4 digits, except for 2-digit positive years which we assume are
37725 * near the present time.
37726 * Note that we follow ISO 8601:2004: there *is* a year 0, which
37727 * is 1BC/BCE, and -1===2BC etc.
37728 *
37729 * World calendars: not all of these *have* agreed extensions to this full range,
37730 * if you have another calendar system but want a date range outside its validity,
37731 * you can use a gregorian date string prefixed with 'G' or 'g'.
37732 *
37733 * Where to cut off 2-digit years between 1900s and 2000s?
37734 * from http://support.microsoft.com/kb/244664:
37735 * 1930-2029 (the most retro of all...)
37736 * but in my mac chrome from eg. d=new Date(Date.parse('8/19/50')):
37737 * 1950-2049
37738 * by Java, from http://stackoverflow.com/questions/2024273/:
37739 * now-80 - now+19
37740 * or FileMaker Pro, from
37741 * http://www.filemaker.com/12help/html/add_view_data.4.21.html:
37742 * now-70 - now+29
37743 * but python strptime etc, via
37744 * http://docs.python.org/py3k/library/time.html:
37745 * 1969-2068 (super forward-looking, but static, not sliding!)
37746 *
37747 * lets go with now-70 to now+29, and if anyone runs into this problem
37748 * they can learn the hard way not to use 2-digit years, as no choice we
37749 * make now will cover all possibilities. mostly this will all be taken
37750 * care of in initial parsing, should only be an issue for hand-entered data
37751 * currently (2016) this range is:
37752 * 1946-2045
37753 */
37754exports.dateTime2ms = function(s, calendar) {
37755 // first check if s is a date object
37756 if(exports.isJSDate(s)) {
37757 // Convert to the UTC milliseconds that give the same
37758 // hours as this date has in the local timezone
37759 var tzOffset = s.getTimezoneOffset() * ONEMIN;
37760 var offsetTweak = (s.getUTCMinutes() - s.getMinutes()) * ONEMIN +
37761 (s.getUTCSeconds() - s.getSeconds()) * ONESEC +
37762 (s.getUTCMilliseconds() - s.getMilliseconds());
37763
37764 if(offsetTweak) {
37765 var comb = 3 * ONEMIN;
37766 tzOffset = tzOffset - comb / 2 + mod(offsetTweak - tzOffset + comb / 2, comb);
37767 }
37768 s = Number(s) - tzOffset;
37769 if(s >= MIN_MS && s <= MAX_MS) return s;
37770 return BADNUM;
37771 }
37772 // otherwise only accept strings and numbers
37773 if(typeof s !== 'string' && typeof s !== 'number') return BADNUM;
37774
37775 s = String(s);
37776
37777 var isWorld = isWorldCalendar(calendar);
37778
37779 // to handle out-of-range dates in international calendars, accept
37780 // 'G' as a prefix to force the built-in gregorian calendar.
37781 var s0 = s.charAt(0);
37782 if(isWorld && (s0 === 'G' || s0 === 'g')) {
37783 s = s.substr(1);
37784 calendar = '';
37785 }
37786
37787 var isChinese = isWorld && calendar.substr(0, 7) === 'chinese';
37788
37789 var match = s.match(isChinese ? DATETIME_REGEXP_CN : DATETIME_REGEXP);
37790 if(!match) return BADNUM;
37791 var y = match[1];
37792 var m = match[3] || '1';
37793 var d = Number(match[5] || 1);
37794 var H = Number(match[7] || 0);
37795 var M = Number(match[9] || 0);
37796 var S = Number(match[11] || 0);
37797
37798 if(isWorld) {
37799 // disallow 2-digit years for world calendars
37800 if(y.length === 2) return BADNUM;
37801 y = Number(y);
37802
37803 var cDate;
37804 try {
37805 var calInstance = Registry.getComponentMethod('calendars', 'getCal')(calendar);
37806 if(isChinese) {
37807 var isIntercalary = m.charAt(m.length - 1) === 'i';
37808 m = parseInt(m, 10);
37809 cDate = calInstance.newDate(y, calInstance.toMonthIndex(y, m, isIntercalary), d);
37810 } else {
37811 cDate = calInstance.newDate(y, Number(m), d);
37812 }
37813 } catch(e) { return BADNUM; } // Invalid ... date
37814
37815 if(!cDate) return BADNUM;
37816
37817 return ((cDate.toJD() - EPOCHJD) * ONEDAY) +
37818 (H * ONEHOUR) + (M * ONEMIN) + (S * ONESEC);
37819 }
37820
37821 if(y.length === 2) {
37822 y = (Number(y) + 2000 - YFIRST) % 100 + YFIRST;
37823 } else y = Number(y);
37824
37825 // new Date uses months from 0; subtract 1 here just so we
37826 // don't have to do it again during the validity test below
37827 m -= 1;
37828
37829 // javascript takes new Date(0..99,m,d) to mean 1900-1999, so
37830 // to support years 0-99 we need to use setFullYear explicitly
37831 // Note that 2000 is a leap year.
37832 var date = new Date(Date.UTC(2000, m, d, H, M));
37833 date.setUTCFullYear(y);
37834
37835 if(date.getUTCMonth() !== m) return BADNUM;
37836 if(date.getUTCDate() !== d) return BADNUM;
37837
37838 return date.getTime() + S * ONESEC;
37839};
37840
37841MIN_MS = exports.MIN_MS = exports.dateTime2ms('-9999');
37842MAX_MS = exports.MAX_MS = exports.dateTime2ms('9999-12-31 23:59:59.9999');
37843
37844// is string s a date? (see above)
37845exports.isDateTime = function(s, calendar) {
37846 return (exports.dateTime2ms(s, calendar) !== BADNUM);
37847};
37848
37849// pad a number with zeroes, to given # of digits before the decimal point
37850function lpad(val, digits) {
37851 return String(val + Math.pow(10, digits)).substr(1);
37852}
37853
37854/**
37855 * Turn ms into string of the form YYYY-mm-dd HH:MM:SS.ssss
37856 * Crop any trailing zeros in time, except never stop right after hours
37857 * (we could choose to crop '-01' from date too but for now we always
37858 * show the whole date)
37859 * Optional range r is the data range that applies, also in ms.
37860 * If rng is big, the later parts of time will be omitted
37861 */
37862var NINETYDAYS = 90 * ONEDAY;
37863var THREEHOURS = 3 * ONEHOUR;
37864var FIVEMIN = 5 * ONEMIN;
37865exports.ms2DateTime = function(ms, r, calendar) {
37866 if(typeof ms !== 'number' || !(ms >= MIN_MS && ms <= MAX_MS)) return BADNUM;
37867
37868 if(!r) r = 0;
37869
37870 var msecTenths = Math.floor(mod(ms + 0.05, 1) * 10);
37871 var msRounded = Math.round(ms - msecTenths / 10);
37872 var dateStr, h, m, s, msec10, d;
37873
37874 if(isWorldCalendar(calendar)) {
37875 var dateJD = Math.floor(msRounded / ONEDAY) + EPOCHJD;
37876 var timeMs = Math.floor(mod(ms, ONEDAY));
37877 try {
37878 dateStr = Registry.getComponentMethod('calendars', 'getCal')(calendar)
37879 .fromJD(dateJD).formatDate('yyyy-mm-dd');
37880 } catch(e) {
37881 // invalid date in this calendar - fall back to Gyyyy-mm-dd
37882 dateStr = utcFormat('G%Y-%m-%d')(new Date(msRounded));
37883 }
37884
37885 // yyyy does NOT guarantee 4-digit years. YYYY mostly does, but does
37886 // other things for a few calendars, so we can't trust it. Just pad
37887 // it manually (after the '-' if there is one)
37888 if(dateStr.charAt(0) === '-') {
37889 while(dateStr.length < 11) dateStr = '-0' + dateStr.substr(1);
37890 } else {
37891 while(dateStr.length < 10) dateStr = '0' + dateStr;
37892 }
37893
37894 // TODO: if this is faster, we could use this block for extracting
37895 // the time components of regular gregorian too
37896 h = (r < NINETYDAYS) ? Math.floor(timeMs / ONEHOUR) : 0;
37897 m = (r < NINETYDAYS) ? Math.floor((timeMs % ONEHOUR) / ONEMIN) : 0;
37898 s = (r < THREEHOURS) ? Math.floor((timeMs % ONEMIN) / ONESEC) : 0;
37899 msec10 = (r < FIVEMIN) ? (timeMs % ONESEC) * 10 + msecTenths : 0;
37900 } else {
37901 d = new Date(msRounded);
37902
37903 dateStr = utcFormat('%Y-%m-%d')(d);
37904
37905 // <90 days: add hours and minutes - never *only* add hours
37906 h = (r < NINETYDAYS) ? d.getUTCHours() : 0;
37907 m = (r < NINETYDAYS) ? d.getUTCMinutes() : 0;
37908 // <3 hours: add seconds
37909 s = (r < THREEHOURS) ? d.getUTCSeconds() : 0;
37910 // <5 minutes: add ms (plus one extra digit, this is msec*10)
37911 msec10 = (r < FIVEMIN) ? d.getUTCMilliseconds() * 10 + msecTenths : 0;
37912 }
37913
37914 return includeTime(dateStr, h, m, s, msec10);
37915};
37916
37917// For converting old-style milliseconds to date strings,
37918// we use the local timezone rather than UTC like we use
37919// everywhere else, both for backward compatibility and
37920// because that's how people mostly use javasript date objects.
37921// Clip one extra day off our date range though so we can't get
37922// thrown beyond the range by the timezone shift.
37923exports.ms2DateTimeLocal = function(ms) {
37924 if(!(ms >= MIN_MS + ONEDAY && ms <= MAX_MS - ONEDAY)) return BADNUM;
37925
37926 var msecTenths = Math.floor(mod(ms + 0.05, 1) * 10);
37927 var d = new Date(Math.round(ms - msecTenths / 10));
37928 var dateStr = d3.time.format('%Y-%m-%d')(d);
37929 var h = d.getHours();
37930 var m = d.getMinutes();
37931 var s = d.getSeconds();
37932 var msec10 = d.getUTCMilliseconds() * 10 + msecTenths;
37933
37934 return includeTime(dateStr, h, m, s, msec10);
37935};
37936
37937function includeTime(dateStr, h, m, s, msec10) {
37938 // include each part that has nonzero data in or after it
37939 if(h || m || s || msec10) {
37940 dateStr += ' ' + lpad(h, 2) + ':' + lpad(m, 2);
37941 if(s || msec10) {
37942 dateStr += ':' + lpad(s, 2);
37943 if(msec10) {
37944 var digits = 4;
37945 while(msec10 % 10 === 0) {
37946 digits -= 1;
37947 msec10 /= 10;
37948 }
37949 dateStr += '.' + lpad(msec10, digits);
37950 }
37951 }
37952 }
37953 return dateStr;
37954}
37955
37956// normalize date format to date string, in case it starts as
37957// a Date object or milliseconds
37958// optional dflt is the return value if cleaning fails
37959exports.cleanDate = function(v, dflt, calendar) {
37960 // let us use cleanDate to provide a missing default without an error
37961 if(v === BADNUM) return dflt;
37962 if(exports.isJSDate(v) || (typeof v === 'number' && isFinite(v))) {
37963 // do not allow milliseconds (old) or jsdate objects (inherently
37964 // described as gregorian dates) with world calendars
37965 if(isWorldCalendar(calendar)) {
37966 Loggers.error('JS Dates and milliseconds are incompatible with world calendars', v);
37967 return dflt;
37968 }
37969
37970 // NOTE: if someone puts in a year as a number rather than a string,
37971 // this will mistakenly convert it thinking it's milliseconds from 1970
37972 // that is: '2012' -> Jan. 1, 2012, but 2012 -> 2012 epoch milliseconds
37973 v = exports.ms2DateTimeLocal(+v);
37974 if(!v && dflt !== undefined) return dflt;
37975 } else if(!exports.isDateTime(v, calendar)) {
37976 Loggers.error('unrecognized date', v);
37977 return dflt;
37978 }
37979 return v;
37980};
37981
37982/*
37983 * Date formatting for ticks and hovertext
37984 */
37985
37986/*
37987 * modDateFormat: Support world calendars, and add one item to
37988 * d3's vocabulary:
37989 * %{n}f where n is the max number of digits of fractional seconds
37990 */
37991var fracMatch = /%\d?f/g;
37992function modDateFormat(fmt, x, formatter, calendar) {
37993 fmt = fmt.replace(fracMatch, function(match) {
37994 var digits = Math.min(+(match.charAt(1)) || 6, 6);
37995 var fracSecs = ((x / 1000 % 1) + 2)
37996 .toFixed(digits)
37997 .substr(2).replace(/0+$/, '') || '0';
37998 return fracSecs;
37999 });
38000
38001 var d = new Date(Math.floor(x + 0.05));
38002
38003 if(isWorldCalendar(calendar)) {
38004 try {
38005 fmt = Registry.getComponentMethod('calendars', 'worldCalFmt')(fmt, x, calendar);
38006 } catch(e) {
38007 return 'Invalid';
38008 }
38009 }
38010 return formatter(fmt)(d);
38011}
38012
38013/*
38014 * formatTime: create a time string from:
38015 * x: milliseconds
38016 * tr: tickround ('M', 'S', or # digits)
38017 * only supports UTC times (where every day is 24 hours and 0 is at midnight)
38018 */
38019var MAXSECONDS = [59, 59.9, 59.99, 59.999, 59.9999];
38020function formatTime(x, tr) {
38021 var timePart = mod(x + 0.05, ONEDAY);
38022
38023 var timeStr = lpad(Math.floor(timePart / ONEHOUR), 2) + ':' +
38024 lpad(mod(Math.floor(timePart / ONEMIN), 60), 2);
38025
38026 if(tr !== 'M') {
38027 if(!isNumeric(tr)) tr = 0; // should only be 'S'
38028
38029 /*
38030 * this is a weird one - and shouldn't come up unless people
38031 * monkey with tick0 in weird ways, but we need to do something!
38032 * IN PARTICULAR we had better not display garbage (see below)
38033 * for numbers we always round to the nearest increment of the
38034 * precision we're showing, and this seems like the right way to
38035 * handle seconds and milliseconds, as they have a decimal point
38036 * and people will interpret that to mean rounding like numbers.
38037 * but for larger increments we floor the value: it's always
38038 * 2013 until the ball drops on the new year. We could argue about
38039 * which field it is where we start rounding (should 12:08:59
38040 * round to 12:09 if we're stopping at minutes?) but for now I'll
38041 * say we round seconds but floor everything else. BUT that means
38042 * we need to never round up to 60 seconds, ie 23:59:60
38043 */
38044 var sec = Math.min(mod(x / ONESEC, 60), MAXSECONDS[tr]);
38045
38046 var secStr = (100 + sec).toFixed(tr).substr(1);
38047 if(tr > 0) {
38048 secStr = secStr.replace(/0+$/, '').replace(/[\.]$/, '');
38049 }
38050
38051 timeStr += ':' + secStr;
38052 }
38053 return timeStr;
38054}
38055
38056/*
38057 * formatDate: turn a date into tick or hover label text.
38058 *
38059 * x: milliseconds, the value to convert
38060 * fmt: optional, an explicit format string (d3 format, even for world calendars)
38061 * tr: tickround ('y', 'm', 'd', 'M', 'S', or # digits)
38062 * used if no explicit fmt is provided
38063 * formatter: locale-aware d3 date formatter for standard gregorian calendars
38064 * should be the result of exports.getD3DateFormat(gd)
38065 * calendar: optional string, the world calendar system to use
38066 *
38067 * returns the date/time as a string, potentially with the leading portion
38068 * on a separate line (after '\n')
38069 * Note that this means if you provide an explicit format which includes '\n'
38070 * the axis may choose to strip things after it when they don't change from
38071 * one tick to the next (as it does with automatic formatting)
38072 */
38073exports.formatDate = function(x, fmt, tr, formatter, calendar, extraFormat) {
38074 calendar = isWorldCalendar(calendar) && calendar;
38075
38076 if(!fmt) {
38077 if(tr === 'y') fmt = extraFormat.year;
38078 else if(tr === 'm') fmt = extraFormat.month;
38079 else if(tr === 'd') {
38080 fmt = extraFormat.dayMonth + '\n' + extraFormat.year;
38081 } else {
38082 return formatTime(x, tr) + '\n' + modDateFormat(extraFormat.dayMonthYear, x, formatter, calendar);
38083 }
38084 }
38085
38086 return modDateFormat(fmt, x, formatter, calendar);
38087};
38088
38089/*
38090 * incrementMonth: make a new milliseconds value from the given one,
38091 * having changed the month
38092 *
38093 * special case for world calendars: multiples of 12 are treated as years,
38094 * even for calendar systems that don't have (always or ever) 12 months/year
38095 * TODO: perhaps we need a different code for year increments to support this?
38096 *
38097 * ms (number): the initial millisecond value
38098 * dMonth (int): the (signed) number of months to shift
38099 * calendar (string): the calendar system to use
38100 *
38101 * changing month does not (and CANNOT) always preserve day, since
38102 * months have different lengths. The worst example of this is:
38103 * d = new Date(1970,0,31); d.setMonth(1) -> Feb 31 turns into Mar 3
38104 *
38105 * But we want to be able to iterate over the last day of each month,
38106 * regardless of what its number is.
38107 * So shift 3 days forward, THEN set the new month, then unshift:
38108 * 1/31 -> 2/28 (or 29) -> 3/31 -> 4/30 -> ...
38109 *
38110 * Note that odd behavior still exists if you start from the 26th-28th:
38111 * 1/28 -> 2/28 -> 3/31
38112 * but at least you can't shift any dates into the wrong month,
38113 * and ticks on these days incrementing by month would be very unusual
38114 */
38115var THREEDAYS = 3 * ONEDAY;
38116exports.incrementMonth = function(ms, dMonth, calendar) {
38117 calendar = isWorldCalendar(calendar) && calendar;
38118
38119 // pull time out and operate on pure dates, then add time back at the end
38120 // this gives maximum precision - not that we *normally* care if we're
38121 // incrementing by month, but better to be safe!
38122 var timeMs = mod(ms, ONEDAY);
38123 ms = Math.round(ms - timeMs);
38124
38125 if(calendar) {
38126 try {
38127 var dateJD = Math.round(ms / ONEDAY) + EPOCHJD;
38128 var calInstance = Registry.getComponentMethod('calendars', 'getCal')(calendar);
38129 var cDate = calInstance.fromJD(dateJD);
38130
38131 if(dMonth % 12) calInstance.add(cDate, dMonth, 'm');
38132 else calInstance.add(cDate, dMonth / 12, 'y');
38133
38134 return (cDate.toJD() - EPOCHJD) * ONEDAY + timeMs;
38135 } catch(e) {
38136 Loggers.error('invalid ms ' + ms + ' in calendar ' + calendar);
38137 // then keep going in gregorian even though the result will be 'Invalid'
38138 }
38139 }
38140
38141 var y = new Date(ms + THREEDAYS);
38142 return y.setUTCMonth(y.getUTCMonth() + dMonth) + timeMs - THREEDAYS;
38143};
38144
38145/*
38146 * findExactDates: what fraction of data is exact days, months, or years?
38147 *
38148 * data: array of millisecond values
38149 * calendar (string) the calendar to test against
38150 */
38151exports.findExactDates = function(data, calendar) {
38152 var exactYears = 0;
38153 var exactMonths = 0;
38154 var exactDays = 0;
38155 var blankCount = 0;
38156 var d;
38157 var di;
38158
38159 var calInstance = (
38160 isWorldCalendar(calendar) &&
38161 Registry.getComponentMethod('calendars', 'getCal')(calendar)
38162 );
38163
38164 for(var i = 0; i < data.length; i++) {
38165 di = data[i];
38166
38167 // not date data at all
38168 if(!isNumeric(di)) {
38169 blankCount ++;
38170 continue;
38171 }
38172
38173 // not an exact date
38174 if(di % ONEDAY) continue;
38175
38176 if(calInstance) {
38177 try {
38178 d = calInstance.fromJD(di / ONEDAY + EPOCHJD);
38179 if(d.day() === 1) {
38180 if(d.month() === 1) exactYears++;
38181 else exactMonths++;
38182 } else exactDays++;
38183 } catch(e) {
38184 // invalid date in this calendar - ignore it here.
38185 }
38186 } else {
38187 d = new Date(di);
38188 if(d.getUTCDate() === 1) {
38189 if(d.getUTCMonth() === 0) exactYears++;
38190 else exactMonths++;
38191 } else exactDays++;
38192 }
38193 }
38194 exactMonths += exactYears;
38195 exactDays += exactMonths;
38196
38197 var dataCount = data.length - blankCount;
38198
38199 return {
38200 exactYears: exactYears / dataCount,
38201 exactMonths: exactMonths / dataCount,
38202 exactDays: exactDays / dataCount
38203 };
38204};
38205
38206},{"../constants/numerical":158,"../registry":269,"./loggers":182,"./mod":185,"d3":16,"fast-isnumeric":18}],171:[function(_dereq_,module,exports){
38207/**
38208* Copyright 2012-2020, Plotly, Inc.
38209* All rights reserved.
38210*
38211* This source code is licensed under the MIT license found in the
38212* LICENSE file in the root directory of this source tree.
38213*/
38214
38215'use strict';
38216
38217var d3 = _dereq_('d3');
38218var loggers = _dereq_('./loggers');
38219
38220/**
38221 * Allow referencing a graph DOM element either directly
38222 * or by its id string
38223 *
38224 * @param {HTMLDivElement|string} gd: a graph element or its id
38225 *
38226 * @returns {HTMLDivElement} the DOM element of the graph
38227 */
38228function getGraphDiv(gd) {
38229 var gdElement;
38230
38231 if(typeof gd === 'string') {
38232 gdElement = document.getElementById(gd);
38233
38234 if(gdElement === null) {
38235 throw new Error('No DOM element with id \'' + gd + '\' exists on the page.');
38236 }
38237
38238 return gdElement;
38239 } else if(gd === null || gd === undefined) {
38240 throw new Error('DOM element provided is null or undefined');
38241 }
38242
38243 // otherwise assume that gd is a DOM element
38244 return gd;
38245}
38246
38247function isPlotDiv(el) {
38248 var el3 = d3.select(el);
38249 return el3.node() instanceof HTMLElement &&
38250 el3.size() &&
38251 el3.classed('js-plotly-plot');
38252}
38253
38254function removeElement(el) {
38255 var elParent = el && el.parentNode;
38256 if(elParent) elParent.removeChild(el);
38257}
38258
38259/**
38260 * for dynamically adding style rules
38261 * makes one stylesheet that contains all rules added
38262 * by all calls to this function
38263 */
38264function addStyleRule(selector, styleString) {
38265 addRelatedStyleRule('global', selector, styleString);
38266}
38267
38268/**
38269 * for dynamically adding style rules
38270 * to a stylesheet uniquely identified by a uid
38271 */
38272function addRelatedStyleRule(uid, selector, styleString) {
38273 var id = 'plotly.js-style-' + uid;
38274 var style = document.getElementById(id);
38275 if(!style) {
38276 style = document.createElement('style');
38277 style.setAttribute('id', id);
38278 // WebKit hack :(
38279 style.appendChild(document.createTextNode(''));
38280 document.head.appendChild(style);
38281 }
38282 var styleSheet = style.sheet;
38283
38284 if(styleSheet.insertRule) {
38285 styleSheet.insertRule(selector + '{' + styleString + '}', 0);
38286 } else if(styleSheet.addRule) {
38287 styleSheet.addRule(selector, styleString, 0);
38288 } else loggers.warn('addStyleRule failed');
38289}
38290
38291/**
38292 * to remove from the page a stylesheet identified by a given uid
38293 */
38294function deleteRelatedStyleRule(uid) {
38295 var id = 'plotly.js-style-' + uid;
38296 var style = document.getElementById(id);
38297 if(style) removeElement(style);
38298}
38299
38300module.exports = {
38301 getGraphDiv: getGraphDiv,
38302 isPlotDiv: isPlotDiv,
38303 removeElement: removeElement,
38304 addStyleRule: addStyleRule,
38305 addRelatedStyleRule: addRelatedStyleRule,
38306 deleteRelatedStyleRule: deleteRelatedStyleRule
38307};
38308
38309},{"./loggers":182,"d3":16}],172:[function(_dereq_,module,exports){
38310/**
38311* Copyright 2012-2020, Plotly, Inc.
38312* All rights reserved.
38313*
38314* This source code is licensed under the MIT license found in the
38315* LICENSE file in the root directory of this source tree.
38316*/
38317
38318
38319'use strict';
38320
38321/* global jQuery:false */
38322
38323var EventEmitter = _dereq_('events').EventEmitter;
38324
38325var Events = {
38326
38327 init: function(plotObj) {
38328 /*
38329 * If we have already instantiated an emitter for this plot
38330 * return early.
38331 */
38332 if(plotObj._ev instanceof EventEmitter) return plotObj;
38333
38334 var ev = new EventEmitter();
38335 var internalEv = new EventEmitter();
38336
38337 /*
38338 * Assign to plot._ev while we still live in a land
38339 * where plot is a DOM element with stuff attached to it.
38340 * In the future we can make plot the event emitter itself.
38341 */
38342 plotObj._ev = ev;
38343
38344 /*
38345 * Create a second event handler that will manage events *internally*.
38346 * This allows parts of plotly to respond to thing like relayout without
38347 * having to use the user-facing event handler. They cannot peacefully
38348 * coexist on the same handler because a user invoking
38349 * plotObj.removeAllListeners() would detach internal events, breaking
38350 * plotly.
38351 */
38352 plotObj._internalEv = internalEv;
38353
38354 /*
38355 * Assign bound methods from the ev to the plot object. These methods
38356 * will reference the 'this' of plot._ev even though they are methods
38357 * of plot. This will keep the event machinery away from the plot object
38358 * which currently is often a DOM element but presents an API that will
38359 * continue to function when plot becomes an emitter. Not all EventEmitter
38360 * methods have been bound to `plot` as some do not currently add value to
38361 * the Plotly event API.
38362 */
38363 plotObj.on = ev.on.bind(ev);
38364 plotObj.once = ev.once.bind(ev);
38365 plotObj.removeListener = ev.removeListener.bind(ev);
38366 plotObj.removeAllListeners = ev.removeAllListeners.bind(ev);
38367
38368 /*
38369 * Create functions for managing internal events. These are *only* triggered
38370 * by the mirroring of external events via the emit function.
38371 */
38372 plotObj._internalOn = internalEv.on.bind(internalEv);
38373 plotObj._internalOnce = internalEv.once.bind(internalEv);
38374 plotObj._removeInternalListener = internalEv.removeListener.bind(internalEv);
38375 plotObj._removeAllInternalListeners = internalEv.removeAllListeners.bind(internalEv);
38376
38377 /*
38378 * We must wrap emit to continue to support JQuery events. The idea
38379 * is to check to see if the user is using JQuery events, if they are
38380 * we emit JQuery events to trigger user handlers as well as the EventEmitter
38381 * events.
38382 */
38383 plotObj.emit = function(event, data) {
38384 if(typeof jQuery !== 'undefined') {
38385 jQuery(plotObj).trigger(event, data);
38386 }
38387
38388 ev.emit(event, data);
38389 internalEv.emit(event, data);
38390 };
38391
38392 return plotObj;
38393 },
38394
38395 /*
38396 * This function behaves like jQuery's triggerHandler. It calls
38397 * all handlers for a particular event and returns the return value
38398 * of the LAST handler. This function also triggers jQuery's
38399 * triggerHandler for backwards compatibility.
38400 */
38401 triggerHandler: function(plotObj, event, data) {
38402 var jQueryHandlerValue;
38403 var nodeEventHandlerValue;
38404
38405 /*
38406 * If jQuery exists run all its handlers for this event and
38407 * collect the return value of the LAST handler function
38408 */
38409 if(typeof jQuery !== 'undefined') {
38410 jQueryHandlerValue = jQuery(plotObj).triggerHandler(event, data);
38411 }
38412
38413 /*
38414 * Now run all the node style event handlers
38415 */
38416 var ev = plotObj._ev;
38417 if(!ev) return jQueryHandlerValue;
38418
38419 var handlers = ev._events[event];
38420 if(!handlers) return jQueryHandlerValue;
38421
38422 // making sure 'this' is the EventEmitter instance
38423 function apply(handler) {
38424 // The 'once' case, we can't just call handler() as we need
38425 // the return value here. So,
38426 // - remove handler
38427 // - call listener and grab return value!
38428 // - stash 'fired' key to not call handler twice
38429 if(handler.listener) {
38430 ev.removeListener(event, handler.listener);
38431 if(!handler.fired) {
38432 handler.fired = true;
38433 return handler.listener.apply(ev, [data]);
38434 }
38435 } else {
38436 return handler.apply(ev, [data]);
38437 }
38438 }
38439
38440 // handlers can be function or an array of functions
38441 handlers = Array.isArray(handlers) ? handlers : [handlers];
38442
38443 var i;
38444 for(i = 0; i < handlers.length - 1; i++) {
38445 apply(handlers[i]);
38446 }
38447 // now call the final handler and collect its value
38448 nodeEventHandlerValue = apply(handlers[i]);
38449
38450 /*
38451 * Return either the jQuery handler value if it exists or the
38452 * nodeEventHandler value. jQuery event value supersedes nodejs
38453 * events for backwards compatibility reasons.
38454 */
38455 return jQueryHandlerValue !== undefined ?
38456 jQueryHandlerValue :
38457 nodeEventHandlerValue;
38458 },
38459
38460 purge: function(plotObj) {
38461 delete plotObj._ev;
38462 delete plotObj.on;
38463 delete plotObj.once;
38464 delete plotObj.removeListener;
38465 delete plotObj.removeAllListeners;
38466 delete plotObj.emit;
38467
38468 delete plotObj._ev;
38469 delete plotObj._internalEv;
38470 delete plotObj._internalOn;
38471 delete plotObj._internalOnce;
38472 delete plotObj._removeInternalListener;
38473 delete plotObj._removeAllInternalListeners;
38474
38475 return plotObj;
38476 }
38477
38478};
38479
38480module.exports = Events;
38481
38482},{"events":15}],173:[function(_dereq_,module,exports){
38483/**
38484* Copyright 2012-2020, Plotly, Inc.
38485* All rights reserved.
38486*
38487* This source code is licensed under the MIT license found in the
38488* LICENSE file in the root directory of this source tree.
38489*/
38490
38491
38492'use strict';
38493
38494var isPlainObject = _dereq_('./is_plain_object.js');
38495var isArray = Array.isArray;
38496
38497function primitivesLoopSplice(source, target) {
38498 var i, value;
38499 for(i = 0; i < source.length; i++) {
38500 value = source[i];
38501 if(value !== null && typeof(value) === 'object') {
38502 return false;
38503 }
38504 if(value !== void(0)) {
38505 target[i] = value;
38506 }
38507 }
38508 return true;
38509}
38510
38511exports.extendFlat = function() {
38512 return _extend(arguments, false, false, false);
38513};
38514
38515exports.extendDeep = function() {
38516 return _extend(arguments, true, false, false);
38517};
38518
38519exports.extendDeepAll = function() {
38520 return _extend(arguments, true, true, false);
38521};
38522
38523exports.extendDeepNoArrays = function() {
38524 return _extend(arguments, true, false, true);
38525};
38526
38527/*
38528 * Inspired by https://github.com/justmoon/node-extend/blob/master/index.js
38529 * All credit to the jQuery authors for perfecting this amazing utility.
38530 *
38531 * API difference with jQuery version:
38532 * - No optional boolean (true -> deep extend) first argument,
38533 * use `extendFlat` for first-level only extend and
38534 * use `extendDeep` for a deep extend.
38535 *
38536 * Other differences with jQuery version:
38537 * - Uses a modern (and faster) isPlainObject routine.
38538 * - Expected to work with object {} and array [] arguments only.
38539 * - Does not check for circular structure.
38540 * FYI: jQuery only does a check across one level.
38541 * Warning: this might result in infinite loops.
38542 *
38543 */
38544function _extend(inputs, isDeep, keepAllKeys, noArrayCopies) {
38545 var target = inputs[0];
38546 var length = inputs.length;
38547
38548 var input, key, src, copy, copyIsArray, clone, allPrimitives;
38549
38550 // TODO does this do the right thing for typed arrays?
38551
38552 if(length === 2 && isArray(target) && isArray(inputs[1]) && target.length === 0) {
38553 allPrimitives = primitivesLoopSplice(inputs[1], target);
38554
38555 if(allPrimitives) {
38556 return target;
38557 } else {
38558 target.splice(0, target.length); // reset target and continue to next block
38559 }
38560 }
38561
38562 for(var i = 1; i < length; i++) {
38563 input = inputs[i];
38564
38565 for(key in input) {
38566 src = target[key];
38567 copy = input[key];
38568
38569 if(noArrayCopies && isArray(copy)) {
38570 // Stop early and just transfer the array if array copies are disallowed:
38571
38572 target[key] = copy;
38573 } else if(isDeep && copy && (isPlainObject(copy) || (copyIsArray = isArray(copy)))) {
38574 // recurse if we're merging plain objects or arrays
38575
38576 if(copyIsArray) {
38577 copyIsArray = false;
38578 clone = src && isArray(src) ? src : [];
38579 } else {
38580 clone = src && isPlainObject(src) ? src : {};
38581 }
38582
38583 // never move original objects, clone them
38584 target[key] = _extend([clone, copy], isDeep, keepAllKeys, noArrayCopies);
38585 } else if(typeof copy !== 'undefined' || keepAllKeys) {
38586 // don't bring in undefined values, except for extendDeepAll
38587
38588 target[key] = copy;
38589 }
38590 }
38591 }
38592
38593 return target;
38594}
38595
38596},{"./is_plain_object.js":179}],174:[function(_dereq_,module,exports){
38597/**
38598* Copyright 2012-2020, Plotly, Inc.
38599* All rights reserved.
38600*
38601* This source code is licensed under the MIT license found in the
38602* LICENSE file in the root directory of this source tree.
38603*/
38604
38605
38606'use strict';
38607
38608
38609/**
38610 * Return news array containing only the unique items
38611 * found in input array.
38612 *
38613 * IMPORTANT: Note that items are considered unique
38614 * if `String({})` is unique. For example;
38615 *
38616 * Lib.filterUnique([ { a: 1 }, { b: 2 } ])
38617 *
38618 * returns [{ a: 1 }]
38619 *
38620 * and
38621 *
38622 * Lib.filterUnique([ '1', 1 ])
38623 *
38624 * returns ['1']
38625 *
38626 *
38627 * @param {array} array base array
38628 * @return {array} new filtered array
38629 */
38630module.exports = function filterUnique(array) {
38631 var seen = {};
38632 var out = [];
38633 var j = 0;
38634
38635 for(var i = 0; i < array.length; i++) {
38636 var item = array[i];
38637
38638 if(seen[item] !== 1) {
38639 seen[item] = 1;
38640 out[j++] = item;
38641 }
38642 }
38643
38644 return out;
38645};
38646
38647},{}],175:[function(_dereq_,module,exports){
38648/**
38649* Copyright 2012-2020, Plotly, Inc.
38650* All rights reserved.
38651*
38652* This source code is licensed under the MIT license found in the
38653* LICENSE file in the root directory of this source tree.
38654*/
38655
38656'use strict';
38657
38658/** Filter out object items with visible !== true
38659 * insider array container.
38660 *
38661 * @param {array of objects} container
38662 * @return {array of objects} of length <= container
38663 *
38664 */
38665module.exports = function filterVisible(container) {
38666 var filterFn = isCalcData(container) ? calcDataFilter : baseFilter;
38667 var out = [];
38668
38669 for(var i = 0; i < container.length; i++) {
38670 var item = container[i];
38671 if(filterFn(item)) out.push(item);
38672 }
38673
38674 return out;
38675};
38676
38677function baseFilter(item) {
38678 return item.visible === true;
38679}
38680
38681function calcDataFilter(item) {
38682 var trace = item[0].trace;
38683 return trace.visible === true && trace._length !== 0;
38684}
38685
38686function isCalcData(cont) {
38687 return (
38688 Array.isArray(cont) &&
38689 Array.isArray(cont[0]) &&
38690 cont[0][0] &&
38691 cont[0][0].trace
38692 );
38693}
38694
38695},{}],176:[function(_dereq_,module,exports){
38696/**
38697* Copyright 2012-2020, Plotly, Inc.
38698* All rights reserved.
38699*
38700* This source code is licensed under the MIT license found in the
38701* LICENSE file in the root directory of this source tree.
38702*/
38703
38704'use strict';
38705
38706var mod = _dereq_('./mod').mod;
38707
38708/*
38709 * look for intersection of two line segments
38710 * (1->2 and 3->4) - returns array [x,y] if they do, null if not
38711 */
38712exports.segmentsIntersect = segmentsIntersect;
38713function segmentsIntersect(x1, y1, x2, y2, x3, y3, x4, y4) {
38714 var a = x2 - x1;
38715 var b = x3 - x1;
38716 var c = x4 - x3;
38717 var d = y2 - y1;
38718 var e = y3 - y1;
38719 var f = y4 - y3;
38720 var det = a * f - c * d;
38721 // parallel lines? intersection is undefined
38722 // ignore the case where they are colinear
38723 if(det === 0) return null;
38724 var t = (b * f - c * e) / det;
38725 var u = (b * d - a * e) / det;
38726 // segments do not intersect?
38727 if(u < 0 || u > 1 || t < 0 || t > 1) return null;
38728
38729 return {x: x1 + a * t, y: y1 + d * t};
38730}
38731
38732/*
38733 * find the minimum distance between two line segments (1->2 and 3->4)
38734 */
38735exports.segmentDistance = function segmentDistance(x1, y1, x2, y2, x3, y3, x4, y4) {
38736 if(segmentsIntersect(x1, y1, x2, y2, x3, y3, x4, y4)) return 0;
38737
38738 // the two segments and their lengths squared
38739 var x12 = x2 - x1;
38740 var y12 = y2 - y1;
38741 var x34 = x4 - x3;
38742 var y34 = y4 - y3;
38743 var ll12 = x12 * x12 + y12 * y12;
38744 var ll34 = x34 * x34 + y34 * y34;
38745
38746 // calculate distance squared, then take the sqrt at the very end
38747 var dist2 = Math.min(
38748 perpDistance2(x12, y12, ll12, x3 - x1, y3 - y1),
38749 perpDistance2(x12, y12, ll12, x4 - x1, y4 - y1),
38750 perpDistance2(x34, y34, ll34, x1 - x3, y1 - y3),
38751 perpDistance2(x34, y34, ll34, x2 - x3, y2 - y3)
38752 );
38753
38754 return Math.sqrt(dist2);
38755};
38756
38757/*
38758 * distance squared from segment ab to point c
38759 * [xab, yab] is the vector b-a
38760 * [xac, yac] is the vector c-a
38761 * llab is the length squared of (b-a), just to simplify calculation
38762 */
38763function perpDistance2(xab, yab, llab, xac, yac) {
38764 var fcAB = (xac * xab + yac * yab);
38765 if(fcAB < 0) {
38766 // point c is closer to point a
38767 return xac * xac + yac * yac;
38768 } else if(fcAB > llab) {
38769 // point c is closer to point b
38770 var xbc = xac - xab;
38771 var ybc = yac - yab;
38772 return xbc * xbc + ybc * ybc;
38773 } else {
38774 // perpendicular distance is the shortest
38775 var crossProduct = xac * yab - yac * xab;
38776 return crossProduct * crossProduct / llab;
38777 }
38778}
38779
38780// a very short-term cache for getTextLocation, just because
38781// we're often looping over the same locations multiple times
38782// invalidated as soon as we look at a different path
38783var locationCache, workingPath, workingTextWidth;
38784
38785// turn a path and position along it into x, y, and angle for the given text
38786exports.getTextLocation = function getTextLocation(path, totalPathLen, positionOnPath, textWidth) {
38787 if(path !== workingPath || textWidth !== workingTextWidth) {
38788 locationCache = {};
38789 workingPath = path;
38790 workingTextWidth = textWidth;
38791 }
38792 if(locationCache[positionOnPath]) {
38793 return locationCache[positionOnPath];
38794 }
38795
38796 // for the angle, use points on the path separated by the text width
38797 // even though due to curvature, the text will cover a bit more than that
38798 var p0 = path.getPointAtLength(mod(positionOnPath - textWidth / 2, totalPathLen));
38799 var p1 = path.getPointAtLength(mod(positionOnPath + textWidth / 2, totalPathLen));
38800 // note: atan handles 1/0 nicely
38801 var theta = Math.atan((p1.y - p0.y) / (p1.x - p0.x));
38802 // center the text at 2/3 of the center position plus 1/3 the p0/p1 midpoint
38803 // that's the average position of this segment, assuming it's roughly quadratic
38804 var pCenter = path.getPointAtLength(mod(positionOnPath, totalPathLen));
38805 var x = (pCenter.x * 4 + p0.x + p1.x) / 6;
38806 var y = (pCenter.y * 4 + p0.y + p1.y) / 6;
38807
38808 var out = {x: x, y: y, theta: theta};
38809 locationCache[positionOnPath] = out;
38810 return out;
38811};
38812
38813exports.clearLocationCache = function() {
38814 workingPath = null;
38815};
38816
38817/*
38818 * Find the segment of `path` that's within the visible area
38819 * given by `bounds` {left, right, top, bottom}, to within a
38820 * precision of `buffer` px
38821 *
38822 * returns: undefined if nothing is visible, else object:
38823 * {
38824 * min: position where the path first enters bounds, or 0 if it
38825 * starts within bounds
38826 * max: position where the path last exits bounds, or the path length
38827 * if it finishes within bounds
38828 * len: max - min, ie the length of visible path
38829 * total: the total path length - just included so the caller doesn't
38830 * need to call path.getTotalLength() again
38831 * isClosed: true iff the start and end points of the path are both visible
38832 * and are at the same point
38833 * }
38834 *
38835 * Works by starting from either end and repeatedly finding the distance from
38836 * that point to the plot area, and if it's outside the plot, moving along the
38837 * path by that distance (because the plot must be at least that far away on
38838 * the path). Note that if a path enters, exits, and re-enters the plot, we
38839 * will not capture this behavior.
38840 */
38841exports.getVisibleSegment = function getVisibleSegment(path, bounds, buffer) {
38842 var left = bounds.left;
38843 var right = bounds.right;
38844 var top = bounds.top;
38845 var bottom = bounds.bottom;
38846
38847 var pMin = 0;
38848 var pTotal = path.getTotalLength();
38849 var pMax = pTotal;
38850
38851 var pt0, ptTotal;
38852
38853 function getDistToPlot(len) {
38854 var pt = path.getPointAtLength(len);
38855
38856 // hold on to the start and end points for `closed`
38857 if(len === 0) pt0 = pt;
38858 else if(len === pTotal) ptTotal = pt;
38859
38860 var dx = (pt.x < left) ? left - pt.x : (pt.x > right ? pt.x - right : 0);
38861 var dy = (pt.y < top) ? top - pt.y : (pt.y > bottom ? pt.y - bottom : 0);
38862 return Math.sqrt(dx * dx + dy * dy);
38863 }
38864
38865 var distToPlot = getDistToPlot(pMin);
38866 while(distToPlot) {
38867 pMin += distToPlot + buffer;
38868 if(pMin > pMax) return;
38869 distToPlot = getDistToPlot(pMin);
38870 }
38871
38872 distToPlot = getDistToPlot(pMax);
38873 while(distToPlot) {
38874 pMax -= distToPlot + buffer;
38875 if(pMin > pMax) return;
38876 distToPlot = getDistToPlot(pMax);
38877 }
38878
38879 return {
38880 min: pMin,
38881 max: pMax,
38882 len: pMax - pMin,
38883 total: pTotal,
38884 isClosed: pMin === 0 && pMax === pTotal &&
38885 Math.abs(pt0.x - ptTotal.x) < 0.1 &&
38886 Math.abs(pt0.y - ptTotal.y) < 0.1
38887 };
38888};
38889
38890/**
38891 * Find point on SVG path corresponding to a given constraint coordinate
38892 *
38893 * @param {SVGPathElement} path
38894 * @param {Number} val : constraint coordinate value
38895 * @param {String} coord : 'x' or 'y' the constraint coordinate
38896 * @param {Object} opts :
38897 * - {Number} pathLength : supply total path length before hand
38898 * - {Number} tolerance
38899 * - {Number} iterationLimit
38900 * @return {SVGPoint}
38901 */
38902exports.findPointOnPath = function findPointOnPath(path, val, coord, opts) {
38903 opts = opts || {};
38904
38905 var pathLength = opts.pathLength || path.getTotalLength();
38906 var tolerance = opts.tolerance || 1e-3;
38907 var iterationLimit = opts.iterationLimit || 30;
38908
38909 // if path starts at a val greater than the path tail (like on vertical violins),
38910 // we must flip the sign of the computed diff.
38911 var mul = path.getPointAtLength(0)[coord] > path.getPointAtLength(pathLength)[coord] ? -1 : 1;
38912
38913 var i = 0;
38914 var b0 = 0;
38915 var b1 = pathLength;
38916 var mid;
38917 var pt;
38918 var diff;
38919
38920 while(i < iterationLimit) {
38921 mid = (b0 + b1) / 2;
38922 pt = path.getPointAtLength(mid);
38923 diff = pt[coord] - val;
38924
38925 if(Math.abs(diff) < tolerance) {
38926 return pt;
38927 } else {
38928 if(mul * diff > 0) {
38929 b1 = mid;
38930 } else {
38931 b0 = mid;
38932 }
38933 i++;
38934 }
38935 }
38936 return pt;
38937};
38938
38939},{"./mod":185}],177:[function(_dereq_,module,exports){
38940/**
38941* Copyright 2012-2020, Plotly, Inc.
38942* All rights reserved.
38943*
38944* This source code is licensed under the MIT license found in the
38945* LICENSE file in the root directory of this source tree.
38946*/
38947
38948'use strict';
38949
38950// Simple helper functions
38951// none of these need any external deps
38952
38953module.exports = function identity(d) { return d; };
38954
38955},{}],178:[function(_dereq_,module,exports){
38956/**
38957* Copyright 2012-2020, Plotly, Inc.
38958* All rights reserved.
38959*
38960* This source code is licensed under the MIT license found in the
38961* LICENSE file in the root directory of this source tree.
38962*/
38963
38964'use strict';
38965
38966var d3 = _dereq_('d3');
38967var isNumeric = _dereq_('fast-isnumeric');
38968
38969var numConstants = _dereq_('../constants/numerical');
38970var FP_SAFE = numConstants.FP_SAFE;
38971var BADNUM = numConstants.BADNUM;
38972
38973var lib = module.exports = {};
38974
38975lib.nestedProperty = _dereq_('./nested_property');
38976lib.keyedContainer = _dereq_('./keyed_container');
38977lib.relativeAttr = _dereq_('./relative_attr');
38978lib.isPlainObject = _dereq_('./is_plain_object');
38979lib.toLogRange = _dereq_('./to_log_range');
38980lib.relinkPrivateKeys = _dereq_('./relink_private');
38981
38982var arrayModule = _dereq_('./array');
38983lib.isTypedArray = arrayModule.isTypedArray;
38984lib.isArrayOrTypedArray = arrayModule.isArrayOrTypedArray;
38985lib.isArray1D = arrayModule.isArray1D;
38986lib.ensureArray = arrayModule.ensureArray;
38987lib.concat = arrayModule.concat;
38988lib.maxRowLength = arrayModule.maxRowLength;
38989lib.minRowLength = arrayModule.minRowLength;
38990
38991var modModule = _dereq_('./mod');
38992lib.mod = modModule.mod;
38993lib.modHalf = modModule.modHalf;
38994
38995var coerceModule = _dereq_('./coerce');
38996lib.valObjectMeta = coerceModule.valObjectMeta;
38997lib.coerce = coerceModule.coerce;
38998lib.coerce2 = coerceModule.coerce2;
38999lib.coerceFont = coerceModule.coerceFont;
39000lib.coerceHoverinfo = coerceModule.coerceHoverinfo;
39001lib.coerceSelectionMarkerOpacity = coerceModule.coerceSelectionMarkerOpacity;
39002lib.validate = coerceModule.validate;
39003
39004var datesModule = _dereq_('./dates');
39005lib.dateTime2ms = datesModule.dateTime2ms;
39006lib.isDateTime = datesModule.isDateTime;
39007lib.ms2DateTime = datesModule.ms2DateTime;
39008lib.ms2DateTimeLocal = datesModule.ms2DateTimeLocal;
39009lib.cleanDate = datesModule.cleanDate;
39010lib.isJSDate = datesModule.isJSDate;
39011lib.formatDate = datesModule.formatDate;
39012lib.incrementMonth = datesModule.incrementMonth;
39013lib.dateTick0 = datesModule.dateTick0;
39014lib.dfltRange = datesModule.dfltRange;
39015lib.findExactDates = datesModule.findExactDates;
39016lib.MIN_MS = datesModule.MIN_MS;
39017lib.MAX_MS = datesModule.MAX_MS;
39018
39019var searchModule = _dereq_('./search');
39020lib.findBin = searchModule.findBin;
39021lib.sorterAsc = searchModule.sorterAsc;
39022lib.sorterDes = searchModule.sorterDes;
39023lib.distinctVals = searchModule.distinctVals;
39024lib.roundUp = searchModule.roundUp;
39025lib.sort = searchModule.sort;
39026lib.findIndexOfMin = searchModule.findIndexOfMin;
39027
39028var statsModule = _dereq_('./stats');
39029lib.aggNums = statsModule.aggNums;
39030lib.len = statsModule.len;
39031lib.mean = statsModule.mean;
39032lib.median = statsModule.median;
39033lib.midRange = statsModule.midRange;
39034lib.variance = statsModule.variance;
39035lib.stdev = statsModule.stdev;
39036lib.interp = statsModule.interp;
39037
39038var matrixModule = _dereq_('./matrix');
39039lib.init2dArray = matrixModule.init2dArray;
39040lib.transposeRagged = matrixModule.transposeRagged;
39041lib.dot = matrixModule.dot;
39042lib.translationMatrix = matrixModule.translationMatrix;
39043lib.rotationMatrix = matrixModule.rotationMatrix;
39044lib.rotationXYMatrix = matrixModule.rotationXYMatrix;
39045lib.apply2DTransform = matrixModule.apply2DTransform;
39046lib.apply2DTransform2 = matrixModule.apply2DTransform2;
39047
39048var anglesModule = _dereq_('./angles');
39049lib.deg2rad = anglesModule.deg2rad;
39050lib.rad2deg = anglesModule.rad2deg;
39051lib.angleDelta = anglesModule.angleDelta;
39052lib.angleDist = anglesModule.angleDist;
39053lib.isFullCircle = anglesModule.isFullCircle;
39054lib.isAngleInsideSector = anglesModule.isAngleInsideSector;
39055lib.isPtInsideSector = anglesModule.isPtInsideSector;
39056lib.pathArc = anglesModule.pathArc;
39057lib.pathSector = anglesModule.pathSector;
39058lib.pathAnnulus = anglesModule.pathAnnulus;
39059
39060var anchorUtils = _dereq_('./anchor_utils');
39061lib.isLeftAnchor = anchorUtils.isLeftAnchor;
39062lib.isCenterAnchor = anchorUtils.isCenterAnchor;
39063lib.isRightAnchor = anchorUtils.isRightAnchor;
39064lib.isTopAnchor = anchorUtils.isTopAnchor;
39065lib.isMiddleAnchor = anchorUtils.isMiddleAnchor;
39066lib.isBottomAnchor = anchorUtils.isBottomAnchor;
39067
39068var geom2dModule = _dereq_('./geometry2d');
39069lib.segmentsIntersect = geom2dModule.segmentsIntersect;
39070lib.segmentDistance = geom2dModule.segmentDistance;
39071lib.getTextLocation = geom2dModule.getTextLocation;
39072lib.clearLocationCache = geom2dModule.clearLocationCache;
39073lib.getVisibleSegment = geom2dModule.getVisibleSegment;
39074lib.findPointOnPath = geom2dModule.findPointOnPath;
39075
39076var extendModule = _dereq_('./extend');
39077lib.extendFlat = extendModule.extendFlat;
39078lib.extendDeep = extendModule.extendDeep;
39079lib.extendDeepAll = extendModule.extendDeepAll;
39080lib.extendDeepNoArrays = extendModule.extendDeepNoArrays;
39081
39082var loggersModule = _dereq_('./loggers');
39083lib.log = loggersModule.log;
39084lib.warn = loggersModule.warn;
39085lib.error = loggersModule.error;
39086
39087var regexModule = _dereq_('./regex');
39088lib.counterRegex = regexModule.counter;
39089
39090var throttleModule = _dereq_('./throttle');
39091lib.throttle = throttleModule.throttle;
39092lib.throttleDone = throttleModule.done;
39093lib.clearThrottle = throttleModule.clear;
39094
39095var domModule = _dereq_('./dom');
39096lib.getGraphDiv = domModule.getGraphDiv;
39097lib.isPlotDiv = domModule.isPlotDiv;
39098lib.removeElement = domModule.removeElement;
39099lib.addStyleRule = domModule.addStyleRule;
39100lib.addRelatedStyleRule = domModule.addRelatedStyleRule;
39101lib.deleteRelatedStyleRule = domModule.deleteRelatedStyleRule;
39102
39103lib.clearResponsive = _dereq_('./clear_responsive');
39104
39105lib.makeTraceGroups = _dereq_('./make_trace_groups');
39106
39107lib._ = _dereq_('./localize');
39108
39109lib.notifier = _dereq_('./notifier');
39110
39111lib.filterUnique = _dereq_('./filter_unique');
39112lib.filterVisible = _dereq_('./filter_visible');
39113lib.pushUnique = _dereq_('./push_unique');
39114
39115lib.cleanNumber = _dereq_('./clean_number');
39116
39117lib.ensureNumber = function ensureNumber(v) {
39118 if(!isNumeric(v)) return BADNUM;
39119 v = Number(v);
39120 if(v < -FP_SAFE || v > FP_SAFE) return BADNUM;
39121 return isNumeric(v) ? Number(v) : BADNUM;
39122};
39123
39124/**
39125 * Is v a valid array index? Accepts numeric strings as well as numbers.
39126 *
39127 * @param {any} v: the value to test
39128 * @param {Optional[integer]} len: the array length we are indexing
39129 *
39130 * @return {bool}: v is a valid array index
39131 */
39132lib.isIndex = function(v, len) {
39133 if(len !== undefined && v >= len) return false;
39134 return isNumeric(v) && (v >= 0) && (v % 1 === 0);
39135};
39136
39137lib.noop = _dereq_('./noop');
39138lib.identity = _dereq_('./identity');
39139
39140/**
39141 * create an array of length 'cnt' filled with 'v' at all indices
39142 *
39143 * @param {any} v
39144 * @param {number} cnt
39145 * @return {array}
39146 */
39147lib.repeat = function(v, cnt) {
39148 var out = new Array(cnt);
39149 for(var i = 0; i < cnt; i++) {
39150 out[i] = v;
39151 }
39152 return out;
39153};
39154
39155/**
39156 * swap x and y of the same attribute in container cont
39157 * specify attr with a ? in place of x/y
39158 * you can also swap other things than x/y by providing part1 and part2
39159 */
39160lib.swapAttrs = function(cont, attrList, part1, part2) {
39161 if(!part1) part1 = 'x';
39162 if(!part2) part2 = 'y';
39163 for(var i = 0; i < attrList.length; i++) {
39164 var attr = attrList[i];
39165 var xp = lib.nestedProperty(cont, attr.replace('?', part1));
39166 var yp = lib.nestedProperty(cont, attr.replace('?', part2));
39167 var temp = xp.get();
39168 xp.set(yp.get());
39169 yp.set(temp);
39170 }
39171};
39172
39173/**
39174 * SVG painter's algo worked around with reinsertion
39175 */
39176lib.raiseToTop = function raiseToTop(elem) {
39177 elem.parentNode.appendChild(elem);
39178};
39179
39180/**
39181 * cancel a possibly pending transition; returned selection may be used by caller
39182 */
39183lib.cancelTransition = function(selection) {
39184 return selection.transition().duration(0);
39185};
39186
39187// constrain - restrict a number v to be between v0 and v1
39188lib.constrain = function(v, v0, v1) {
39189 if(v0 > v1) return Math.max(v1, Math.min(v0, v));
39190 return Math.max(v0, Math.min(v1, v));
39191};
39192
39193/**
39194 * do two bounding boxes from getBoundingClientRect,
39195 * ie {left,right,top,bottom,width,height}, overlap?
39196 * takes optional padding pixels
39197 */
39198lib.bBoxIntersect = function(a, b, pad) {
39199 pad = pad || 0;
39200 return (a.left <= b.right + pad &&
39201 b.left <= a.right + pad &&
39202 a.top <= b.bottom + pad &&
39203 b.top <= a.bottom + pad);
39204};
39205
39206/*
39207 * simpleMap: alternative to Array.map that only
39208 * passes on the element and up to 2 extra args you
39209 * provide (but not the array index or the whole array)
39210 *
39211 * array: the array to map it to
39212 * func: the function to apply
39213 * x1, x2: optional extra args
39214 */
39215lib.simpleMap = function(array, func, x1, x2, opts) {
39216 var len = array.length;
39217 var out = new Array(len);
39218 for(var i = 0; i < len; i++) out[i] = func(array[i], x1, x2, opts);
39219 return out;
39220};
39221
39222/**
39223 * Random string generator
39224 *
39225 * @param {object} existing
39226 * pass in strings to avoid as keys with truthy values
39227 * @param {int} bits
39228 * bits of information in the output string, default 24
39229 * @param {int} base
39230 * base of string representation, default 16. Should be a power of 2.
39231 */
39232lib.randstr = function randstr(existing, bits, base, _recursion) {
39233 if(!base) base = 16;
39234 if(bits === undefined) bits = 24;
39235 if(bits <= 0) return '0';
39236
39237 var digits = Math.log(Math.pow(2, bits)) / Math.log(base);
39238 var res = '';
39239 var i, b, x;
39240
39241 for(i = 2; digits === Infinity; i *= 2) {
39242 digits = Math.log(Math.pow(2, bits / i)) / Math.log(base) * i;
39243 }
39244
39245 var rem = digits - Math.floor(digits);
39246
39247 for(i = 0; i < Math.floor(digits); i++) {
39248 x = Math.floor(Math.random() * base).toString(base);
39249 res = x + res;
39250 }
39251
39252 if(rem) {
39253 b = Math.pow(base, rem);
39254 x = Math.floor(Math.random() * b).toString(base);
39255 res = x + res;
39256 }
39257
39258 var parsed = parseInt(res, base);
39259 if((existing && existing[res]) ||
39260 (parsed !== Infinity && parsed >= Math.pow(2, bits))) {
39261 if(_recursion > 10) {
39262 lib.warn('randstr failed uniqueness');
39263 return res;
39264 }
39265 return randstr(existing, bits, base, (_recursion || 0) + 1);
39266 } else return res;
39267};
39268
39269lib.OptionControl = function(opt, optname) {
39270 /*
39271 * An environment to contain all option setters and
39272 * getters that collectively modify opts.
39273 *
39274 * You can call up opts from any function in new object
39275 * as this.optname || this.opt
39276 *
39277 * See FitOpts for example of usage
39278 */
39279 if(!opt) opt = {};
39280 if(!optname) optname = 'opt';
39281
39282 var self = {};
39283 self.optionList = [];
39284
39285 self._newoption = function(optObj) {
39286 optObj[optname] = opt;
39287 self[optObj.name] = optObj;
39288 self.optionList.push(optObj);
39289 };
39290
39291 self['_' + optname] = opt;
39292 return self;
39293};
39294
39295/**
39296 * lib.smooth: smooth arrayIn by convolving with
39297 * a hann window with given full width at half max
39298 * bounce the ends in, so the output has the same length as the input
39299 */
39300lib.smooth = function(arrayIn, FWHM) {
39301 FWHM = Math.round(FWHM) || 0; // only makes sense for integers
39302 if(FWHM < 2) return arrayIn;
39303
39304 var alen = arrayIn.length;
39305 var alen2 = 2 * alen;
39306 var wlen = 2 * FWHM - 1;
39307 var w = new Array(wlen);
39308 var arrayOut = new Array(alen);
39309 var i;
39310 var j;
39311 var k;
39312 var v;
39313
39314 // first make the window array
39315 for(i = 0; i < wlen; i++) {
39316 w[i] = (1 - Math.cos(Math.PI * (i + 1) / FWHM)) / (2 * FWHM);
39317 }
39318
39319 // now do the convolution
39320 for(i = 0; i < alen; i++) {
39321 v = 0;
39322 for(j = 0; j < wlen; j++) {
39323 k = i + j + 1 - FWHM;
39324
39325 // multibounce
39326 if(k < -alen) k -= alen2 * Math.round(k / alen2);
39327 else if(k >= alen2) k -= alen2 * Math.floor(k / alen2);
39328
39329 // single bounce
39330 if(k < 0) k = - 1 - k;
39331 else if(k >= alen) k = alen2 - 1 - k;
39332
39333 v += arrayIn[k] * w[j];
39334 }
39335 arrayOut[i] = v;
39336 }
39337
39338 return arrayOut;
39339};
39340
39341/**
39342 * syncOrAsync: run a sequence of functions synchronously
39343 * as long as its returns are not promises (ie have no .then)
39344 * includes one argument arg to send to all functions...
39345 * this is mainly just to prevent us having to make wrapper functions
39346 * when the only purpose of the wrapper is to reference gd
39347 * and a final step to be executed at the end
39348 * TODO: if there's an error and everything is sync,
39349 * this doesn't happen yet because we want to make sure
39350 * that it gets reported
39351 */
39352lib.syncOrAsync = function(sequence, arg, finalStep) {
39353 var ret, fni;
39354
39355 function continueAsync() {
39356 return lib.syncOrAsync(sequence, arg, finalStep);
39357 }
39358
39359 while(sequence.length) {
39360 fni = sequence.splice(0, 1)[0];
39361 ret = fni(arg);
39362
39363 if(ret && ret.then) {
39364 return ret.then(continueAsync)
39365 .then(undefined, lib.promiseError);
39366 }
39367 }
39368
39369 return finalStep && finalStep(arg);
39370};
39371
39372
39373/**
39374 * Helper to strip trailing slash, from
39375 * http://stackoverflow.com/questions/6680825/return-string-without-trailing-slash
39376 */
39377lib.stripTrailingSlash = function(str) {
39378 if(str.substr(-1) === '/') return str.substr(0, str.length - 1);
39379 return str;
39380};
39381
39382lib.noneOrAll = function(containerIn, containerOut, attrList) {
39383 /**
39384 * some attributes come together, so if you have one of them
39385 * in the input, you should copy the default values of the others
39386 * to the input as well.
39387 */
39388 if(!containerIn) return;
39389
39390 var hasAny = false;
39391 var hasAll = true;
39392 var i;
39393 var val;
39394
39395 for(i = 0; i < attrList.length; i++) {
39396 val = containerIn[attrList[i]];
39397 if(val !== undefined && val !== null) hasAny = true;
39398 else hasAll = false;
39399 }
39400
39401 if(hasAny && !hasAll) {
39402 for(i = 0; i < attrList.length; i++) {
39403 containerIn[attrList[i]] = containerOut[attrList[i]];
39404 }
39405 }
39406};
39407
39408/** merges calcdata field (given by cdAttr) with traceAttr values
39409 *
39410 * N.B. Loop over minimum of cd.length and traceAttr.length
39411 * i.e. it does not try to fill in beyond traceAttr.length-1
39412 *
39413 * @param {array} traceAttr : trace attribute
39414 * @param {object} cd : calcdata trace
39415 * @param {string} cdAttr : calcdata key
39416 */
39417lib.mergeArray = function(traceAttr, cd, cdAttr, fn) {
39418 var hasFn = typeof fn === 'function';
39419 if(lib.isArrayOrTypedArray(traceAttr)) {
39420 var imax = Math.min(traceAttr.length, cd.length);
39421 for(var i = 0; i < imax; i++) {
39422 var v = traceAttr[i];
39423 cd[i][cdAttr] = hasFn ? fn(v) : v;
39424 }
39425 }
39426};
39427
39428// cast numbers to positive numbers, returns 0 if not greater than 0
39429lib.mergeArrayCastPositive = function(traceAttr, cd, cdAttr) {
39430 return lib.mergeArray(traceAttr, cd, cdAttr, function(v) {
39431 var w = +v;
39432 return !isFinite(w) ? 0 : w > 0 ? w : 0;
39433 });
39434};
39435
39436/** fills calcdata field (given by cdAttr) with traceAttr values
39437 * or function of traceAttr values (e.g. some fallback)
39438 *
39439 * N.B. Loops over all cd items.
39440 *
39441 * @param {array} traceAttr : trace attribute
39442 * @param {object} cd : calcdata trace
39443 * @param {string} cdAttr : calcdata key
39444 * @param {function} [fn] : optional function to apply to each array item
39445 */
39446lib.fillArray = function(traceAttr, cd, cdAttr, fn) {
39447 fn = fn || lib.identity;
39448
39449 if(lib.isArrayOrTypedArray(traceAttr)) {
39450 for(var i = 0; i < cd.length; i++) {
39451 cd[i][cdAttr] = fn(traceAttr[i]);
39452 }
39453 }
39454};
39455
39456/** Handler for trace-wide vs per-point options
39457 *
39458 * @param {object} trace : (full) trace object
39459 * @param {number} ptNumber : index of the point in question
39460 * @param {string} astr : attribute string
39461 * @param {function} [fn] : optional function to apply to each array item
39462 *
39463 * @return {any}
39464 */
39465lib.castOption = function(trace, ptNumber, astr, fn) {
39466 fn = fn || lib.identity;
39467
39468 var val = lib.nestedProperty(trace, astr).get();
39469
39470 if(lib.isArrayOrTypedArray(val)) {
39471 if(Array.isArray(ptNumber) && lib.isArrayOrTypedArray(val[ptNumber[0]])) {
39472 return fn(val[ptNumber[0]][ptNumber[1]]);
39473 } else {
39474 return fn(val[ptNumber]);
39475 }
39476 } else {
39477 return val;
39478 }
39479};
39480
39481/** Extract option from calcdata item, correctly falling back to
39482 * trace value if not found.
39483 *
39484 * @param {object} calcPt : calcdata[i][j] item
39485 * @param {object} trace : (full) trace object
39486 * @param {string} calcKey : calcdata key
39487 * @param {string} traceKey : aka trace attribute string
39488 * @return {any}
39489 */
39490lib.extractOption = function(calcPt, trace, calcKey, traceKey) {
39491 if(calcKey in calcPt) return calcPt[calcKey];
39492
39493 // fallback to trace value,
39494 // must check if value isn't itself an array
39495 // which means the trace attribute has a corresponding
39496 // calcdata key, but its value is falsy
39497 var traceVal = lib.nestedProperty(trace, traceKey).get();
39498 if(!Array.isArray(traceVal)) return traceVal;
39499};
39500
39501function makePtIndex2PtNumber(indexToPoints) {
39502 var ptIndex2ptNumber = {};
39503 for(var k in indexToPoints) {
39504 var pts = indexToPoints[k];
39505 for(var j = 0; j < pts.length; j++) {
39506 ptIndex2ptNumber[pts[j]] = +k;
39507 }
39508 }
39509 return ptIndex2ptNumber;
39510}
39511
39512/** Tag selected calcdata items
39513 *
39514 * N.B. note that point 'index' corresponds to input data array index
39515 * whereas 'number' is its post-transform version.
39516 *
39517 * @param {array} calcTrace
39518 * @param {object} trace
39519 * - selectedpoints {array}
39520 * - _indexToPoints {object}
39521 * @param {ptNumber2cdIndex} ptNumber2cdIndex (optional)
39522 * optional map object for trace types that do not have 1-to-1 point number to
39523 * calcdata item index correspondence (e.g. histogram)
39524 */
39525lib.tagSelected = function(calcTrace, trace, ptNumber2cdIndex) {
39526 var selectedpoints = trace.selectedpoints;
39527 var indexToPoints = trace._indexToPoints;
39528 var ptIndex2ptNumber;
39529
39530 // make pt index-to-number map object, which takes care of transformed traces
39531 if(indexToPoints) {
39532 ptIndex2ptNumber = makePtIndex2PtNumber(indexToPoints);
39533 }
39534
39535 function isCdIndexValid(v) {
39536 return v !== undefined && v < calcTrace.length;
39537 }
39538
39539 for(var i = 0; i < selectedpoints.length; i++) {
39540 var ptIndex = selectedpoints[i];
39541
39542 if(lib.isIndex(ptIndex) ||
39543 (lib.isArrayOrTypedArray(ptIndex) && lib.isIndex(ptIndex[0]) && lib.isIndex(ptIndex[1]))
39544 ) {
39545 var ptNumber = ptIndex2ptNumber ? ptIndex2ptNumber[ptIndex] : ptIndex;
39546 var cdIndex = ptNumber2cdIndex ? ptNumber2cdIndex[ptNumber] : ptNumber;
39547
39548 if(isCdIndexValid(cdIndex)) {
39549 calcTrace[cdIndex].selected = 1;
39550 }
39551 }
39552 }
39553};
39554
39555lib.selIndices2selPoints = function(trace) {
39556 var selectedpoints = trace.selectedpoints;
39557 var indexToPoints = trace._indexToPoints;
39558
39559 if(indexToPoints) {
39560 var ptIndex2ptNumber = makePtIndex2PtNumber(indexToPoints);
39561 var out = [];
39562
39563 for(var i = 0; i < selectedpoints.length; i++) {
39564 var ptIndex = selectedpoints[i];
39565 if(lib.isIndex(ptIndex)) {
39566 var ptNumber = ptIndex2ptNumber[ptIndex];
39567 if(lib.isIndex(ptNumber)) {
39568 out.push(ptNumber);
39569 }
39570 }
39571 }
39572
39573 return out;
39574 } else {
39575 return selectedpoints;
39576 }
39577};
39578
39579/** Returns target as set by 'target' transform attribute
39580 *
39581 * @param {object} trace : full trace object
39582 * @param {object} transformOpts : transform option object
39583 * - target (string} :
39584 * either an attribute string referencing an array in the trace object, or
39585 * a set array.
39586 *
39587 * @return {array or false} : the target array (NOT a copy!!) or false if invalid
39588 */
39589lib.getTargetArray = function(trace, transformOpts) {
39590 var target = transformOpts.target;
39591
39592 if(typeof target === 'string' && target) {
39593 var array = lib.nestedProperty(trace, target).get();
39594 return Array.isArray(array) ? array : false;
39595 } else if(Array.isArray(target)) {
39596 return target;
39597 }
39598
39599 return false;
39600};
39601
39602/**
39603 * modified version of jQuery's extend to strip out private objs and functions,
39604 * and cut arrays down to first <arraylen> or 1 elements
39605 * because extend-like algorithms are hella slow
39606 * obj2 is assumed to already be clean of these things (including no arrays)
39607 */
39608lib.minExtend = function(obj1, obj2) {
39609 var objOut = {};
39610 if(typeof obj2 !== 'object') obj2 = {};
39611 var arrayLen = 3;
39612 var keys = Object.keys(obj1);
39613 var i, k, v;
39614
39615 for(i = 0; i < keys.length; i++) {
39616 k = keys[i];
39617 v = obj1[k];
39618 if(k.charAt(0) === '_' || typeof v === 'function') continue;
39619 else if(k === 'module') objOut[k] = v;
39620 else if(Array.isArray(v)) {
39621 if(k === 'colorscale') {
39622 objOut[k] = v.slice();
39623 } else {
39624 objOut[k] = v.slice(0, arrayLen);
39625 }
39626 } else if(lib.isTypedArray(v)) {
39627 objOut[k] = v.subarray(0, arrayLen);
39628 } else if(v && (typeof v === 'object')) objOut[k] = lib.minExtend(obj1[k], obj2[k]);
39629 else objOut[k] = v;
39630 }
39631
39632 keys = Object.keys(obj2);
39633 for(i = 0; i < keys.length; i++) {
39634 k = keys[i];
39635 v = obj2[k];
39636 if(typeof v !== 'object' || !(k in objOut) || typeof objOut[k] !== 'object') {
39637 objOut[k] = v;
39638 }
39639 }
39640
39641 return objOut;
39642};
39643
39644lib.titleCase = function(s) {
39645 return s.charAt(0).toUpperCase() + s.substr(1);
39646};
39647
39648lib.containsAny = function(s, fragments) {
39649 for(var i = 0; i < fragments.length; i++) {
39650 if(s.indexOf(fragments[i]) !== -1) return true;
39651 }
39652 return false;
39653};
39654
39655lib.isIE = function() {
39656 return typeof window.navigator.msSaveBlob !== 'undefined';
39657};
39658
39659var IS_IE9_OR_BELOW_REGEX = /MSIE [1-9]\./;
39660lib.isIE9orBelow = function() {
39661 return lib.isIE() && IS_IE9_OR_BELOW_REGEX.test(window.navigator.userAgent);
39662};
39663
39664var IS_SAFARI_REGEX = /Version\/[\d\.]+.*Safari/;
39665lib.isSafari = function() {
39666 return IS_SAFARI_REGEX.test(window.navigator.userAgent);
39667};
39668
39669/**
39670 * Duck typing to recognize a d3 selection, mostly for IE9's benefit
39671 * because it doesn't handle instanceof like modern browsers
39672 */
39673lib.isD3Selection = function(obj) {
39674 return obj && (typeof obj.classed === 'function');
39675};
39676
39677/**
39678 * Append element to DOM only if not present.
39679 *
39680 * @param {d3 selection} parent : parent selection of the element in question
39681 * @param {string} nodeType : node type of element to append
39682 * @param {string} className (optional) : class name of element in question
39683 * @param {fn} enterFn (optional) : optional fn applied to entering elements only
39684 * @return {d3 selection} selection of new layer
39685 *
39686 * Previously, we were using the following pattern:
39687 *
39688 * ```
39689 * var sel = parent.selectAll('.' + className)
39690 * .data([0]);
39691 *
39692 * sel.enter().append(nodeType)
39693 * .classed(className, true);
39694 *
39695 * return sel;
39696 * ```
39697 *
39698 * in numerous places in our codebase to achieve the same behavior.
39699 *
39700 * The logic below performs much better, mostly as we are using
39701 * `.select` instead `.selectAll` that is `querySelector` instead of
39702 * `querySelectorAll`.
39703 *
39704 */
39705lib.ensureSingle = function(parent, nodeType, className, enterFn) {
39706 var sel = parent.select(nodeType + (className ? '.' + className : ''));
39707 if(sel.size()) return sel;
39708
39709 var layer = parent.append(nodeType);
39710 if(className) layer.classed(className, true);
39711 if(enterFn) layer.call(enterFn);
39712
39713 return layer;
39714};
39715
39716/**
39717 * Same as Lib.ensureSingle, but using id as selector.
39718 * This version is mostly used for clipPath nodes.
39719 *
39720 * @param {d3 selection} parent : parent selection of the element in question
39721 * @param {string} nodeType : node type of element to append
39722 * @param {string} id : id of element in question
39723 * @param {fn} enterFn (optional) : optional fn applied to entering elements only
39724 * @return {d3 selection} selection of new layer
39725 */
39726lib.ensureSingleById = function(parent, nodeType, id, enterFn) {
39727 var sel = parent.select(nodeType + '#' + id);
39728 if(sel.size()) return sel;
39729
39730 var layer = parent.append(nodeType).attr('id', id);
39731 if(enterFn) layer.call(enterFn);
39732
39733 return layer;
39734};
39735
39736/**
39737 * Converts a string path to an object.
39738 *
39739 * When given a string containing an array element, it will create a `null`
39740 * filled array of the given size.
39741 *
39742 * @example
39743 * lib.objectFromPath('nested.test[2].path', 'value');
39744 * // returns { nested: { test: [null, null, { path: 'value' }]}
39745 *
39746 * @param {string} path to nested value
39747 * @param {*} any value to be set
39748 *
39749 * @return {Object} the constructed object with a full nested path
39750 */
39751lib.objectFromPath = function(path, value) {
39752 var keys = path.split('.');
39753 var tmpObj;
39754 var obj = tmpObj = {};
39755
39756 for(var i = 0; i < keys.length; i++) {
39757 var key = keys[i];
39758 var el = null;
39759
39760 var parts = keys[i].match(/(.*)\[([0-9]+)\]/);
39761
39762 if(parts) {
39763 key = parts[1];
39764 el = parts[2];
39765
39766 tmpObj = tmpObj[key] = [];
39767
39768 if(i === keys.length - 1) {
39769 tmpObj[el] = value;
39770 } else {
39771 tmpObj[el] = {};
39772 }
39773
39774 tmpObj = tmpObj[el];
39775 } else {
39776 if(i === keys.length - 1) {
39777 tmpObj[key] = value;
39778 } else {
39779 tmpObj[key] = {};
39780 }
39781
39782 tmpObj = tmpObj[key];
39783 }
39784 }
39785
39786 return obj;
39787};
39788
39789/**
39790 * Iterate through an object in-place, converting dotted properties to objects.
39791 *
39792 * Examples:
39793 *
39794 * lib.expandObjectPaths({'nested.test.path': 'value'});
39795 * => { nested: { test: {path: 'value'}}}
39796 *
39797 * It also handles array notation, e.g.:
39798 *
39799 * lib.expandObjectPaths({'foo[1].bar': 'value'});
39800 * => { foo: [null, {bar: value}] }
39801 *
39802 * It handles merges the results when two properties are specified in parallel:
39803 *
39804 * lib.expandObjectPaths({'foo[1].bar': 10, 'foo[0].bar': 20});
39805 * => { foo: [{bar: 10}, {bar: 20}] }
39806 *
39807 * It does NOT, however, merge mulitple mutliply-nested arrays::
39808 *
39809 * lib.expandObjectPaths({'marker[1].range[1]': 5, 'marker[1].range[0]': 4})
39810 * => { marker: [null, {range: 4}] }
39811 */
39812
39813// Store this to avoid recompiling regex on *every* prop since this may happen many
39814// many times for animations. Could maybe be inside the function. Not sure about
39815// scoping vs. recompilation tradeoff, but at least it's not just inlining it into
39816// the inner loop.
39817var dottedPropertyRegex = /^([^\[\.]+)\.(.+)?/;
39818var indexedPropertyRegex = /^([^\.]+)\[([0-9]+)\](\.)?(.+)?/;
39819
39820lib.expandObjectPaths = function(data) {
39821 var match, key, prop, datum, idx, dest, trailingPath;
39822 if(typeof data === 'object' && !Array.isArray(data)) {
39823 for(key in data) {
39824 if(data.hasOwnProperty(key)) {
39825 if((match = key.match(dottedPropertyRegex))) {
39826 datum = data[key];
39827 prop = match[1];
39828
39829 delete data[key];
39830
39831 data[prop] = lib.extendDeepNoArrays(data[prop] || {}, lib.objectFromPath(key, lib.expandObjectPaths(datum))[prop]);
39832 } else if((match = key.match(indexedPropertyRegex))) {
39833 datum = data[key];
39834
39835 prop = match[1];
39836 idx = parseInt(match[2]);
39837
39838 delete data[key];
39839
39840 data[prop] = data[prop] || [];
39841
39842 if(match[3] === '.') {
39843 // This is the case where theere are subsequent properties into which
39844 // we must recurse, e.g. transforms[0].value
39845 trailingPath = match[4];
39846 dest = data[prop][idx] = data[prop][idx] || {};
39847
39848 // NB: Extend deep no arrays prevents this from working on multiple
39849 // nested properties in the same object, e.g.
39850 //
39851 // {
39852 // foo[0].bar[1].range
39853 // foo[0].bar[0].range
39854 // }
39855 //
39856 // In this case, the extendDeepNoArrays will overwrite one array with
39857 // the other, so that both properties *will not* be present in the
39858 // result. Fixing this would require a more intelligent tracking
39859 // of changes and merging than extendDeepNoArrays currently accomplishes.
39860 lib.extendDeepNoArrays(dest, lib.objectFromPath(trailingPath, lib.expandObjectPaths(datum)));
39861 } else {
39862 // This is the case where this property is the end of the line,
39863 // e.g. xaxis.range[0]
39864 data[prop][idx] = lib.expandObjectPaths(datum);
39865 }
39866 } else {
39867 data[key] = lib.expandObjectPaths(data[key]);
39868 }
39869 }
39870 }
39871 }
39872
39873 return data;
39874};
39875
39876/**
39877 * Converts value to string separated by the provided separators.
39878 *
39879 * @example
39880 * lib.numSeparate(2016, '.,');
39881 * // returns '2016'
39882 *
39883 * @example
39884 * lib.numSeparate(3000, '.,', true);
39885 * // returns '3,000'
39886 *
39887 * @example
39888 * lib.numSeparate(1234.56, '|,')
39889 * // returns '1,234|56'
39890 *
39891 * @param {string|number} value the value to be converted
39892 * @param {string} separators string of decimal, then thousands separators
39893 * @param {boolean} separatethousands boolean, 4-digit integers are separated if true
39894 *
39895 * @return {string} the value that has been separated
39896 */
39897lib.numSeparate = function(value, separators, separatethousands) {
39898 if(!separatethousands) separatethousands = false;
39899
39900 if(typeof separators !== 'string' || separators.length === 0) {
39901 throw new Error('Separator string required for formatting!');
39902 }
39903
39904 if(typeof value === 'number') {
39905 value = String(value);
39906 }
39907
39908 var thousandsRe = /(\d+)(\d{3})/;
39909 var decimalSep = separators.charAt(0);
39910 var thouSep = separators.charAt(1);
39911
39912 var x = value.split('.');
39913 var x1 = x[0];
39914 var x2 = x.length > 1 ? decimalSep + x[1] : '';
39915
39916 // Years are ignored for thousands separators
39917 if(thouSep && (x.length > 1 || x1.length > 4 || separatethousands)) {
39918 while(thousandsRe.test(x1)) {
39919 x1 = x1.replace(thousandsRe, '$1' + thouSep + '$2');
39920 }
39921 }
39922
39923 return x1 + x2;
39924};
39925
39926lib.TEMPLATE_STRING_REGEX = /%{([^\s%{}:]*)([:|\|][^}]*)?}/g;
39927var SIMPLE_PROPERTY_REGEX = /^\w*$/;
39928
39929/**
39930 * Substitute values from an object into a string
39931 *
39932 * Examples:
39933 * Lib.templateString('name: %{trace}', {trace: 'asdf'}) --> 'name: asdf'
39934 * Lib.templateString('name: %{trace[0].name}', {trace: [{name: 'asdf'}]}) --> 'name: asdf'
39935 *
39936 * @param {string} input string containing %{...} template strings
39937 * @param {obj} data object containing substitution values
39938 *
39939 * @return {string} templated string
39940 */
39941lib.templateString = function(string, obj) {
39942 // Not all that useful, but cache nestedProperty instantiation
39943 // just in case it speeds things up *slightly*:
39944 var getterCache = {};
39945
39946 return string.replace(lib.TEMPLATE_STRING_REGEX, function(dummy, key) {
39947 var v;
39948 if(SIMPLE_PROPERTY_REGEX.test(key)) {
39949 v = obj[key];
39950 } else {
39951 getterCache[key] = getterCache[key] || lib.nestedProperty(obj, key).get;
39952 v = getterCache[key]();
39953 }
39954 return lib.isValidTextValue(v) ? v : '';
39955 });
39956};
39957
39958var hovertemplateWarnings = {
39959 max: 10,
39960 count: 0,
39961 name: 'hovertemplate'
39962};
39963lib.hovertemplateString = function() {
39964 return templateFormatString.apply(hovertemplateWarnings, arguments);
39965};
39966
39967var texttemplateWarnings = {
39968 max: 10,
39969 count: 0,
39970 name: 'texttemplate'
39971};
39972lib.texttemplateString = function() {
39973 return templateFormatString.apply(texttemplateWarnings, arguments);
39974};
39975
39976var TEMPLATE_STRING_FORMAT_SEPARATOR = /^[:|\|]/;
39977/**
39978 * Substitute values from an object into a string and optionally formats them using d3-format,
39979 * or fallback to associated labels.
39980 *
39981 * Examples:
39982 * Lib.hovertemplateString('name: %{trace}', {trace: 'asdf'}) --> 'name: asdf'
39983 * Lib.hovertemplateString('name: %{trace[0].name}', {trace: [{name: 'asdf'}]}) --> 'name: asdf'
39984 * Lib.hovertemplateString('price: %{y:$.2f}', {y: 1}) --> 'price: $1.00'
39985 *
39986 * @param {string} input string containing %{...:...} template strings
39987 * @param {obj} data object containing fallback text when no formatting is specified, ex.: {yLabel: 'formattedYValue'}
39988 * @param {obj} d3 locale
39989 * @param {obj} data objects containing substitution values
39990 *
39991 * @return {string} templated string
39992 */
39993function templateFormatString(string, labels, d3locale) {
39994 var opts = this;
39995 var args = arguments;
39996 if(!labels) labels = {};
39997 // Not all that useful, but cache nestedProperty instantiation
39998 // just in case it speeds things up *slightly*:
39999 var getterCache = {};
40000
40001 return string.replace(lib.TEMPLATE_STRING_REGEX, function(match, key, format) {
40002 var obj, value, i;
40003 for(i = 3; i < args.length; i++) {
40004 obj = args[i];
40005 if(!obj) continue;
40006 if(obj.hasOwnProperty(key)) {
40007 value = obj[key];
40008 break;
40009 }
40010
40011 if(!SIMPLE_PROPERTY_REGEX.test(key)) {
40012 value = getterCache[key] || lib.nestedProperty(obj, key).get();
40013 if(value) getterCache[key] = value;
40014 }
40015 if(value !== undefined) break;
40016 }
40017
40018 if(value === undefined && opts) {
40019 if(opts.count < opts.max) {
40020 lib.warn('Variable \'' + key + '\' in ' + opts.name + ' could not be found!');
40021 value = match;
40022 }
40023
40024 if(opts.count === opts.max) {
40025 lib.warn('Too many ' + opts.name + ' warnings - additional warnings will be suppressed');
40026 }
40027 opts.count++;
40028
40029 return match;
40030 }
40031
40032 if(format) {
40033 var fmt;
40034 if(format[0] === ':') {
40035 fmt = d3locale ? d3locale.numberFormat : d3.format;
40036 value = fmt(format.replace(TEMPLATE_STRING_FORMAT_SEPARATOR, ''))(value);
40037 }
40038
40039 if(format[0] === '|') {
40040 fmt = d3locale ? d3locale.timeFormat.utc : d3.time.format.utc;
40041 var ms = lib.dateTime2ms(value);
40042 value = lib.formatDate(ms, format.replace(TEMPLATE_STRING_FORMAT_SEPARATOR, ''), false, fmt);
40043 }
40044 } else {
40045 if(labels.hasOwnProperty(key + 'Label')) value = labels[key + 'Label'];
40046 }
40047 return value;
40048 });
40049}
40050
40051/*
40052 * alphanumeric string sort, tailored for subplot IDs like scene2, scene10, x10y13 etc
40053 */
40054var char0 = 48;
40055var char9 = 57;
40056lib.subplotSort = function(a, b) {
40057 var l = Math.min(a.length, b.length) + 1;
40058 var numA = 0;
40059 var numB = 0;
40060 for(var i = 0; i < l; i++) {
40061 var charA = a.charCodeAt(i) || 0;
40062 var charB = b.charCodeAt(i) || 0;
40063 var isNumA = charA >= char0 && charA <= char9;
40064 var isNumB = charB >= char0 && charB <= char9;
40065
40066 if(isNumA) numA = 10 * numA + charA - char0;
40067 if(isNumB) numB = 10 * numB + charB - char0;
40068
40069 if(!isNumA || !isNumB) {
40070 if(numA !== numB) return numA - numB;
40071 if(charA !== charB) return charA - charB;
40072 }
40073 }
40074 return numB - numA;
40075};
40076
40077// repeatable pseudorandom generator
40078var randSeed = 2000000000;
40079
40080lib.seedPseudoRandom = function() {
40081 randSeed = 2000000000;
40082};
40083
40084lib.pseudoRandom = function() {
40085 var lastVal = randSeed;
40086 randSeed = (69069 * randSeed + 1) % 4294967296;
40087 // don't let consecutive vals be too close together
40088 // gets away from really trying to be random, in favor of better local uniformity
40089 if(Math.abs(randSeed - lastVal) < 429496729) return lib.pseudoRandom();
40090 return randSeed / 4294967296;
40091};
40092
40093
40094/** Fill hover 'pointData' container with 'correct' hover text value
40095 *
40096 * - If trace hoverinfo contains a 'text' flag and hovertext is not set,
40097 * the text elements will be seen in the hover labels.
40098 *
40099 * - If trace hoverinfo contains a 'text' flag and hovertext is set,
40100 * hovertext takes precedence over text
40101 * i.e. the hoverinfo elements will be seen in the hover labels
40102 *
40103 * @param {object} calcPt
40104 * @param {object} trace
40105 * @param {object || array} contOut (mutated here)
40106 */
40107lib.fillText = function(calcPt, trace, contOut) {
40108 var fill = Array.isArray(contOut) ?
40109 function(v) { contOut.push(v); } :
40110 function(v) { contOut.text = v; };
40111
40112 var htx = lib.extractOption(calcPt, trace, 'htx', 'hovertext');
40113 if(lib.isValidTextValue(htx)) return fill(htx);
40114
40115 var tx = lib.extractOption(calcPt, trace, 'tx', 'text');
40116 if(lib.isValidTextValue(tx)) return fill(tx);
40117};
40118
40119// accept all truthy values and 0 (which gets cast to '0' in the hover labels)
40120lib.isValidTextValue = function(v) {
40121 return v || v === 0;
40122};
40123
40124/**
40125 * @param {number} ratio
40126 * @param {number} n (number of decimal places)
40127 */
40128lib.formatPercent = function(ratio, n) {
40129 n = n || 0;
40130 var str = (Math.round(100 * ratio * Math.pow(10, n)) * Math.pow(0.1, n)).toFixed(n) + '%';
40131 for(var i = 0; i < n; i++) {
40132 if(str.indexOf('.') !== -1) {
40133 str = str.replace('0%', '%');
40134 str = str.replace('.%', '%');
40135 }
40136 }
40137 return str;
40138};
40139
40140lib.isHidden = function(gd) {
40141 var display = window.getComputedStyle(gd).display;
40142 return !display || display === 'none';
40143};
40144
40145/** Return transform text for bar bar-like rectangles and pie-like slices
40146 * @param {object} transform
40147 * - targetX: desired position on the x-axis
40148 * - targetY: desired position on the y-axis
40149 * - textX: text middle position on the x-axis
40150 * - textY: text middle position on the y-axis
40151 * - anchorX: (optional) text anchor position on the x-axis (computed from textX), zero for middle anchor
40152 * - anchorY: (optional) text anchor position on the y-axis (computed from textY), zero for middle anchor
40153 * - scale: (optional) scale applied after translate
40154 * - rotate: (optional) rotation applied after scale
40155 * - noCenter: when defined no extra arguments needed in rotation
40156 */
40157lib.getTextTransform = function(transform) {
40158 var noCenter = transform.noCenter;
40159 var textX = transform.textX;
40160 var textY = transform.textY;
40161 var targetX = transform.targetX;
40162 var targetY = transform.targetY;
40163 var anchorX = transform.anchorX || 0;
40164 var anchorY = transform.anchorY || 0;
40165 var rotate = transform.rotate;
40166 var scale = transform.scale;
40167 if(!scale) scale = 0;
40168 else if(scale > 1) scale = 1;
40169
40170 return (
40171 'translate(' +
40172 (targetX - scale * (textX + anchorX)) + ',' +
40173 (targetY - scale * (textY + anchorY)) +
40174 ')' +
40175 (scale < 1 ?
40176 'scale(' + scale + ')' : ''
40177 ) +
40178 (rotate ?
40179 'rotate(' + rotate +
40180 (noCenter ? '' : ' ' + textX + ' ' + textY) +
40181 ')' : ''
40182 )
40183 );
40184};
40185
40186lib.ensureUniformFontSize = function(gd, baseFont) {
40187 var out = lib.extendFlat({}, baseFont);
40188 out.size = Math.max(
40189 baseFont.size,
40190 gd._fullLayout.uniformtext.minsize || 0
40191 );
40192 return out;
40193};
40194
40195},{"../constants/numerical":158,"./anchor_utils":163,"./angles":164,"./array":165,"./clean_number":166,"./clear_responsive":168,"./coerce":169,"./dates":170,"./dom":171,"./extend":173,"./filter_unique":174,"./filter_visible":175,"./geometry2d":176,"./identity":177,"./is_plain_object":179,"./keyed_container":180,"./localize":181,"./loggers":182,"./make_trace_groups":183,"./matrix":184,"./mod":185,"./nested_property":186,"./noop":187,"./notifier":188,"./push_unique":191,"./regex":193,"./relative_attr":194,"./relink_private":195,"./search":196,"./stats":198,"./throttle":200,"./to_log_range":201,"d3":16,"fast-isnumeric":18}],179:[function(_dereq_,module,exports){
40196/**
40197* Copyright 2012-2020, Plotly, Inc.
40198* All rights reserved.
40199*
40200* This source code is licensed under the MIT license found in the
40201* LICENSE file in the root directory of this source tree.
40202*/
40203
40204
40205'use strict';
40206
40207// more info: http://stackoverflow.com/questions/18531624/isplainobject-thing
40208module.exports = function isPlainObject(obj) {
40209 // We need to be a little less strict in the `imagetest` container because
40210 // of how async image requests are handled.
40211 //
40212 // N.B. isPlainObject(new Constructor()) will return true in `imagetest`
40213 if(window && window.process && window.process.versions) {
40214 return Object.prototype.toString.call(obj) === '[object Object]';
40215 }
40216
40217 return (
40218 Object.prototype.toString.call(obj) === '[object Object]' &&
40219 Object.getPrototypeOf(obj) === Object.prototype
40220 );
40221};
40222
40223},{}],180:[function(_dereq_,module,exports){
40224/**
40225* Copyright 2012-2020, Plotly, Inc.
40226* All rights reserved.
40227*
40228* This source code is licensed under the MIT license found in the
40229* LICENSE file in the root directory of this source tree.
40230*/
40231
40232'use strict';
40233
40234var nestedProperty = _dereq_('./nested_property');
40235
40236var SIMPLE_PROPERTY_REGEX = /^\w*$/;
40237
40238// bitmask for deciding what's updated. Sometimes the name needs to be updated,
40239// sometimes the value needs to be updated, and sometimes both do. This is just
40240// a simple way to track what's updated such that it's a simple OR operation to
40241// assimilate new updates.
40242//
40243// The only exception is the UNSET bit that tracks when we need to explicitly
40244// unset and remove the property. This concrn arises because of the special
40245// way in which nestedProperty handles null/undefined. When you specify `null`,
40246// it prunes any unused items in the tree. I ran into some issues with it getting
40247// null vs undefined confused, so UNSET is just a bit that forces the property
40248// update to send `null`, removing the property explicitly rather than setting
40249// it to undefined.
40250var NONE = 0;
40251var NAME = 1;
40252var VALUE = 2;
40253var BOTH = 3;
40254var UNSET = 4;
40255
40256module.exports = function keyedContainer(baseObj, path, keyName, valueName) {
40257 keyName = keyName || 'name';
40258 valueName = valueName || 'value';
40259 var i, arr, baseProp;
40260 var changeTypes = {};
40261
40262 if(path && path.length) {
40263 baseProp = nestedProperty(baseObj, path);
40264 arr = baseProp.get();
40265 } else {
40266 arr = baseObj;
40267 }
40268
40269 path = path || '';
40270
40271 // Construct an index:
40272 var indexLookup = {};
40273 if(arr) {
40274 for(i = 0; i < arr.length; i++) {
40275 indexLookup[arr[i][keyName]] = i;
40276 }
40277 }
40278
40279 var isSimpleValueProp = SIMPLE_PROPERTY_REGEX.test(valueName);
40280
40281 var obj = {
40282 set: function(name, value) {
40283 var changeType = value === null ? UNSET : NONE;
40284
40285 // create the base array if necessary
40286 if(!arr) {
40287 if(!baseProp || changeType === UNSET) return;
40288
40289 arr = [];
40290 baseProp.set(arr);
40291 }
40292
40293 var idx = indexLookup[name];
40294 if(idx === undefined) {
40295 if(changeType === UNSET) return;
40296
40297 changeType = changeType | BOTH;
40298 idx = arr.length;
40299 indexLookup[name] = idx;
40300 } else if(value !== (isSimpleValueProp ? arr[idx][valueName] : nestedProperty(arr[idx], valueName).get())) {
40301 changeType = changeType | VALUE;
40302 }
40303
40304 var newValue = arr[idx] = arr[idx] || {};
40305 newValue[keyName] = name;
40306
40307 if(isSimpleValueProp) {
40308 newValue[valueName] = value;
40309 } else {
40310 nestedProperty(newValue, valueName).set(value);
40311 }
40312
40313 // If it's not an unset, force that bit to be unset. This is all related to the fact
40314 // that undefined and null are a bit specially implemented in nestedProperties.
40315 if(value !== null) {
40316 changeType = changeType & ~UNSET;
40317 }
40318
40319 changeTypes[idx] = changeTypes[idx] | changeType;
40320
40321 return obj;
40322 },
40323 get: function(name) {
40324 if(!arr) return;
40325
40326 var idx = indexLookup[name];
40327
40328 if(idx === undefined) {
40329 return undefined;
40330 } else if(isSimpleValueProp) {
40331 return arr[idx][valueName];
40332 } else {
40333 return nestedProperty(arr[idx], valueName).get();
40334 }
40335 },
40336 rename: function(name, newName) {
40337 var idx = indexLookup[name];
40338
40339 if(idx === undefined) return obj;
40340 changeTypes[idx] = changeTypes[idx] | NAME;
40341
40342 indexLookup[newName] = idx;
40343 delete indexLookup[name];
40344
40345 arr[idx][keyName] = newName;
40346
40347 return obj;
40348 },
40349 remove: function(name) {
40350 var idx = indexLookup[name];
40351
40352 if(idx === undefined) return obj;
40353
40354 var object = arr[idx];
40355 if(Object.keys(object).length > 2) {
40356 // This object contains more than just the key/value, so unset
40357 // the value without modifying the entry otherwise:
40358 changeTypes[idx] = changeTypes[idx] | VALUE;
40359 return obj.set(name, null);
40360 }
40361
40362 if(isSimpleValueProp) {
40363 for(i = idx; i < arr.length; i++) {
40364 changeTypes[i] = changeTypes[i] | BOTH;
40365 }
40366 for(i = idx; i < arr.length; i++) {
40367 indexLookup[arr[i][keyName]]--;
40368 }
40369 arr.splice(idx, 1);
40370 delete(indexLookup[name]);
40371 } else {
40372 // Perform this update *strictly* so we can check whether the result's
40373 // been pruned. If so, it's a removal. If not, it's a value unset only.
40374 nestedProperty(object, valueName).set(null);
40375
40376 // Now check if the top level nested property has any keys left. If so,
40377 // the object still has values so we only want to unset the key. If not,
40378 // the entire object can be removed since there's no other data.
40379 // var topLevelKeys = Object.keys(object[valueName.split('.')[0]] || []);
40380
40381 changeTypes[idx] = changeTypes[idx] | VALUE | UNSET;
40382 }
40383
40384 return obj;
40385 },
40386 constructUpdate: function() {
40387 var astr, idx;
40388 var update = {};
40389 var changed = Object.keys(changeTypes);
40390 for(var i = 0; i < changed.length; i++) {
40391 idx = changed[i];
40392 astr = path + '[' + idx + ']';
40393 if(arr[idx]) {
40394 if(changeTypes[idx] & NAME) {
40395 update[astr + '.' + keyName] = arr[idx][keyName];
40396 }
40397 if(changeTypes[idx] & VALUE) {
40398 if(isSimpleValueProp) {
40399 update[astr + '.' + valueName] = (changeTypes[idx] & UNSET) ? null : arr[idx][valueName];
40400 } else {
40401 update[astr + '.' + valueName] = (changeTypes[idx] & UNSET) ? null : nestedProperty(arr[idx], valueName).get();
40402 }
40403 }
40404 } else {
40405 update[astr] = null;
40406 }
40407 }
40408
40409 return update;
40410 }
40411 };
40412
40413 return obj;
40414};
40415
40416},{"./nested_property":186}],181:[function(_dereq_,module,exports){
40417/**
40418* Copyright 2012-2020, Plotly, Inc.
40419* All rights reserved.
40420*
40421* This source code is licensed under the MIT license found in the
40422* LICENSE file in the root directory of this source tree.
40423*/
40424
40425
40426'use strict';
40427
40428var Registry = _dereq_('../registry');
40429
40430/**
40431 * localize: translate a string for the current locale
40432 *
40433 * @param {object} gd: the graphDiv for context
40434 * gd._context.locale determines the language (& optional region/country)
40435 * the dictionary for each locale may either be supplied in
40436 * gd._context.locales or globally via Plotly.register
40437 * @param {string} s: the string to translate
40438 */
40439module.exports = function localize(gd, s) {
40440 var locale = gd._context.locale;
40441
40442 /*
40443 * Priority of lookup:
40444 * contextDicts[locale],
40445 * registeredDicts[locale],
40446 * contextDicts[baseLocale], (if baseLocale is distinct)
40447 * registeredDicts[baseLocale]
40448 * Return the first translation we find.
40449 * This way if you have a regionalization you are allowed to specify
40450 * only what's different from the base locale, everything else will
40451 * fall back on the base.
40452 */
40453 for(var i = 0; i < 2; i++) {
40454 var locales = gd._context.locales;
40455 for(var j = 0; j < 2; j++) {
40456 var dict = (locales[locale] || {}).dictionary;
40457 if(dict) {
40458 var out = dict[s];
40459 if(out) return out;
40460 }
40461 locales = Registry.localeRegistry;
40462 }
40463
40464 var baseLocale = locale.split('-')[0];
40465 if(baseLocale === locale) break;
40466 locale = baseLocale;
40467 }
40468
40469 return s;
40470};
40471
40472},{"../registry":269}],182:[function(_dereq_,module,exports){
40473/**
40474* Copyright 2012-2020, Plotly, Inc.
40475* All rights reserved.
40476*
40477* This source code is licensed under the MIT license found in the
40478* LICENSE file in the root directory of this source tree.
40479*/
40480
40481'use strict';
40482
40483/* eslint-disable no-console */
40484
40485var dfltConfig = _dereq_('../plot_api/plot_config').dfltConfig;
40486
40487var notifier = _dereq_('./notifier');
40488
40489var loggers = module.exports = {};
40490
40491/**
40492 * ------------------------------------------
40493 * debugging tools
40494 * ------------------------------------------
40495 */
40496
40497loggers.log = function() {
40498 var i;
40499
40500 if(dfltConfig.logging > 1) {
40501 var messages = ['LOG:'];
40502 for(i = 0; i < arguments.length; i++) {
40503 messages.push(arguments[i]);
40504 }
40505 apply(console.trace || console.log, messages);
40506 }
40507
40508 if(dfltConfig.notifyOnLogging > 1) {
40509 var lines = [];
40510 for(i = 0; i < arguments.length; i++) {
40511 lines.push(arguments[i]);
40512 }
40513 notifier(lines.join('<br>'), 'long');
40514 }
40515};
40516
40517loggers.warn = function() {
40518 var i;
40519
40520 if(dfltConfig.logging > 0) {
40521 var messages = ['WARN:'];
40522 for(i = 0; i < arguments.length; i++) {
40523 messages.push(arguments[i]);
40524 }
40525 apply(console.trace || console.log, messages);
40526 }
40527
40528 if(dfltConfig.notifyOnLogging > 0) {
40529 var lines = [];
40530 for(i = 0; i < arguments.length; i++) {
40531 lines.push(arguments[i]);
40532 }
40533 notifier(lines.join('<br>'), 'stick');
40534 }
40535};
40536
40537loggers.error = function() {
40538 var i;
40539
40540 if(dfltConfig.logging > 0) {
40541 var messages = ['ERROR:'];
40542 for(i = 0; i < arguments.length; i++) {
40543 messages.push(arguments[i]);
40544 }
40545 apply(console.error, messages);
40546 }
40547
40548 if(dfltConfig.notifyOnLogging > 0) {
40549 var lines = [];
40550 for(i = 0; i < arguments.length; i++) {
40551 lines.push(arguments[i]);
40552 }
40553 notifier(lines.join('<br>'), 'stick');
40554 }
40555};
40556
40557/*
40558 * Robust apply, for IE9 where console.log doesn't support
40559 * apply like other functions do
40560 */
40561function apply(f, args) {
40562 if(f && f.apply) {
40563 try {
40564 // `this` should always be console, since here we're always
40565 // applying a method of the console object.
40566 f.apply(console, args);
40567 return;
40568 } catch(e) { /* in case apply failed, fall back on the code below */ }
40569 }
40570
40571 // no apply - just try calling the function on each arg independently
40572 for(var i = 0; i < args.length; i++) {
40573 try {
40574 f(args[i]);
40575 } catch(e) {
40576 // still fails - last resort simple console.log
40577 console.log(args[i]);
40578 }
40579 }
40580}
40581
40582},{"../plot_api/plot_config":210,"./notifier":188}],183:[function(_dereq_,module,exports){
40583/**
40584* Copyright 2012-2020, Plotly, Inc.
40585* All rights reserved.
40586*
40587* This source code is licensed under the MIT license found in the
40588* LICENSE file in the root directory of this source tree.
40589*/
40590
40591'use strict';
40592
40593var d3 = _dereq_('d3');
40594
40595/**
40596 * General helper to manage trace groups based on calcdata
40597 *
40598 * @param {d3.selection} traceLayer: a selection containing a single group
40599 * to draw these traces into
40600 * @param {array} cdModule: array of calcdata items for this
40601 * module and subplot combination. Assumes the calcdata item for each
40602 * trace is an array with the fullData trace attached to the first item.
40603 * @param {string} cls: the class attribute to give each trace group
40604 * so you can give multiple classes separated by spaces
40605 */
40606module.exports = function makeTraceGroups(traceLayer, cdModule, cls) {
40607 var traces = traceLayer.selectAll('g.' + cls.replace(/\s/g, '.'))
40608 .data(cdModule, function(cd) { return cd[0].trace.uid; });
40609
40610 traces.exit().remove();
40611
40612 traces.enter().append('g')
40613 .attr('class', cls);
40614
40615 traces.order();
40616
40617 // stash ref node to trace group in calcdata,
40618 // useful for (fast) styleOnSelect
40619 var k = traceLayer.classed('rangeplot') ? 'nodeRangePlot3' : 'node3';
40620 traces.each(function(cd) { cd[0][k] = d3.select(this); });
40621
40622 return traces;
40623};
40624
40625},{"d3":16}],184:[function(_dereq_,module,exports){
40626/**
40627* Copyright 2012-2020, Plotly, Inc.
40628* All rights reserved.
40629*
40630* This source code is licensed under the MIT license found in the
40631* LICENSE file in the root directory of this source tree.
40632*/
40633
40634
40635'use strict';
40636
40637
40638exports.init2dArray = function(rowLength, colLength) {
40639 var array = new Array(rowLength);
40640 for(var i = 0; i < rowLength; i++) array[i] = new Array(colLength);
40641 return array;
40642};
40643
40644/**
40645 * transpose a (possibly ragged) 2d array z. inspired by
40646 * http://stackoverflow.com/questions/17428587/
40647 * transposing-a-2d-array-in-javascript
40648 */
40649exports.transposeRagged = function(z) {
40650 var maxlen = 0;
40651 var zlen = z.length;
40652 var i, j;
40653 // Maximum row length:
40654 for(i = 0; i < zlen; i++) maxlen = Math.max(maxlen, z[i].length);
40655
40656 var t = new Array(maxlen);
40657 for(i = 0; i < maxlen; i++) {
40658 t[i] = new Array(zlen);
40659 for(j = 0; j < zlen; j++) t[i][j] = z[j][i];
40660 }
40661
40662 return t;
40663};
40664
40665// our own dot function so that we don't need to include numeric
40666exports.dot = function(x, y) {
40667 if(!(x.length && y.length) || x.length !== y.length) return null;
40668
40669 var len = x.length;
40670 var out;
40671 var i;
40672
40673 if(x[0].length) {
40674 // mat-vec or mat-mat
40675 out = new Array(len);
40676 for(i = 0; i < len; i++) out[i] = exports.dot(x[i], y);
40677 } else if(y[0].length) {
40678 // vec-mat
40679 var yTranspose = exports.transposeRagged(y);
40680 out = new Array(yTranspose.length);
40681 for(i = 0; i < yTranspose.length; i++) out[i] = exports.dot(x, yTranspose[i]);
40682 } else {
40683 // vec-vec
40684 out = 0;
40685 for(i = 0; i < len; i++) out += x[i] * y[i];
40686 }
40687
40688 return out;
40689};
40690
40691// translate by (x,y)
40692exports.translationMatrix = function(x, y) {
40693 return [[1, 0, x], [0, 1, y], [0, 0, 1]];
40694};
40695
40696// rotate by alpha around (0,0)
40697exports.rotationMatrix = function(alpha) {
40698 var a = alpha * Math.PI / 180;
40699 return [[Math.cos(a), -Math.sin(a), 0],
40700 [Math.sin(a), Math.cos(a), 0],
40701 [0, 0, 1]];
40702};
40703
40704// rotate by alpha around (x,y)
40705exports.rotationXYMatrix = function(a, x, y) {
40706 return exports.dot(
40707 exports.dot(exports.translationMatrix(x, y),
40708 exports.rotationMatrix(a)),
40709 exports.translationMatrix(-x, -y));
40710};
40711
40712// applies a 2D transformation matrix to either x and y params or an [x,y] array
40713exports.apply2DTransform = function(transform) {
40714 return function() {
40715 var args = arguments;
40716 if(args.length === 3) {
40717 args = args[0];
40718 }// from map
40719 var xy = arguments.length === 1 ? args[0] : [args[0], args[1]];
40720 return exports.dot(transform, [xy[0], xy[1], 1]).slice(0, 2);
40721 };
40722};
40723
40724// applies a 2D transformation matrix to an [x1,y1,x2,y2] array (to transform a segment)
40725exports.apply2DTransform2 = function(transform) {
40726 var at = exports.apply2DTransform(transform);
40727 return function(xys) {
40728 return at(xys.slice(0, 2)).concat(at(xys.slice(2, 4)));
40729 };
40730};
40731
40732},{}],185:[function(_dereq_,module,exports){
40733/**
40734* Copyright 2012-2020, Plotly, Inc.
40735* All rights reserved.
40736*
40737* This source code is licensed under the MIT license found in the
40738* LICENSE file in the root directory of this source tree.
40739*/
40740
40741'use strict';
40742
40743/**
40744 * sanitized modulus function that always returns in the range [0, d)
40745 * rather than (-d, 0] if v is negative
40746 */
40747function mod(v, d) {
40748 var out = v % d;
40749 return out < 0 ? out + d : out;
40750}
40751
40752/**
40753 * sanitized modulus function that always returns in the range [-d/2, d/2]
40754 * rather than (-d, 0] if v is negative
40755 */
40756function modHalf(v, d) {
40757 return Math.abs(v) > (d / 2) ?
40758 v - Math.round(v / d) * d :
40759 v;
40760}
40761
40762module.exports = {
40763 mod: mod,
40764 modHalf: modHalf
40765};
40766
40767},{}],186:[function(_dereq_,module,exports){
40768/**
40769* Copyright 2012-2020, Plotly, Inc.
40770* All rights reserved.
40771*
40772* This source code is licensed under the MIT license found in the
40773* LICENSE file in the root directory of this source tree.
40774*/
40775
40776
40777'use strict';
40778
40779var isNumeric = _dereq_('fast-isnumeric');
40780var isArrayOrTypedArray = _dereq_('./array').isArrayOrTypedArray;
40781
40782/**
40783 * convert a string s (such as 'xaxis.range[0]')
40784 * representing a property of nested object into set and get methods
40785 * also return the string and object so we don't have to keep track of them
40786 * allows [-1] for an array index, to set a property inside all elements
40787 * of an array
40788 * eg if obj = {arr: [{a: 1}, {a: 2}]}
40789 * you can do p = nestedProperty(obj, 'arr[-1].a')
40790 * but you cannot set the array itself this way, to do that
40791 * just set the whole array.
40792 * eg if obj = {arr: [1, 2, 3]}
40793 * you can't do nestedProperty(obj, 'arr[-1]').set(5)
40794 * but you can do nestedProperty(obj, 'arr').set([5, 5, 5])
40795 */
40796module.exports = function nestedProperty(container, propStr) {
40797 if(isNumeric(propStr)) propStr = String(propStr);
40798 else if(typeof propStr !== 'string' ||
40799 propStr.substr(propStr.length - 4) === '[-1]') {
40800 throw 'bad property string';
40801 }
40802
40803 var j = 0;
40804 var propParts = propStr.split('.');
40805 var indexed;
40806 var indices;
40807 var i;
40808
40809 // check for parts of the nesting hierarchy that are numbers (ie array elements)
40810 while(j < propParts.length) {
40811 // look for non-bracket chars, then any number of [##] blocks
40812 indexed = String(propParts[j]).match(/^([^\[\]]*)((\[\-?[0-9]*\])+)$/);
40813 if(indexed) {
40814 if(indexed[1]) propParts[j] = indexed[1];
40815 // allow propStr to start with bracketed array indices
40816 else if(j === 0) propParts.splice(0, 1);
40817 else throw 'bad property string';
40818
40819 indices = indexed[2]
40820 .substr(1, indexed[2].length - 2)
40821 .split('][');
40822
40823 for(i = 0; i < indices.length; i++) {
40824 j++;
40825 propParts.splice(j, 0, Number(indices[i]));
40826 }
40827 }
40828 j++;
40829 }
40830
40831 if(typeof container !== 'object') {
40832 return badContainer(container, propStr, propParts);
40833 }
40834
40835 return {
40836 set: npSet(container, propParts, propStr),
40837 get: npGet(container, propParts),
40838 astr: propStr,
40839 parts: propParts,
40840 obj: container
40841 };
40842};
40843
40844function npGet(cont, parts) {
40845 return function() {
40846 var curCont = cont;
40847 var curPart;
40848 var allSame;
40849 var out;
40850 var i;
40851 var j;
40852
40853 for(i = 0; i < parts.length - 1; i++) {
40854 curPart = parts[i];
40855 if(curPart === -1) {
40856 allSame = true;
40857 out = [];
40858 for(j = 0; j < curCont.length; j++) {
40859 out[j] = npGet(curCont[j], parts.slice(i + 1))();
40860 if(out[j] !== out[0]) allSame = false;
40861 }
40862 return allSame ? out[0] : out;
40863 }
40864 if(typeof curPart === 'number' && !isArrayOrTypedArray(curCont)) {
40865 return undefined;
40866 }
40867 curCont = curCont[curPart];
40868 if(typeof curCont !== 'object' || curCont === null) {
40869 return undefined;
40870 }
40871 }
40872
40873 // only hit this if parts.length === 1
40874 if(typeof curCont !== 'object' || curCont === null) return undefined;
40875
40876 out = curCont[parts[i]];
40877 if(out === null) return undefined;
40878 return out;
40879 };
40880}
40881
40882/*
40883 * Can this value be deleted? We can delete `undefined`, and `null` except INSIDE an
40884 * *args* array.
40885 *
40886 * Previously we also deleted some `{}` and `[]`, in order to try and make set/unset
40887 * a net noop; but this causes far more complication than it's worth, and still had
40888 * lots of exceptions. See https://github.com/plotly/plotly.js/issues/1410
40889 *
40890 * *args* arrays get passed directly to API methods and we should respect null if
40891 * the user put it there, but otherwise null is deleted as we use it as code
40892 * in restyle/relayout/update for "delete this value" whereas undefined means
40893 * "ignore this edit"
40894 */
40895var ARGS_PATTERN = /(^|\.)args\[/;
40896function isDeletable(val, propStr) {
40897 return (val === undefined) || (val === null && !propStr.match(ARGS_PATTERN));
40898}
40899
40900function npSet(cont, parts, propStr) {
40901 return function(val) {
40902 var curCont = cont;
40903 var propPart = '';
40904 var containerLevels = [[cont, propPart]];
40905 var toDelete = isDeletable(val, propStr);
40906 var curPart;
40907 var i;
40908
40909 for(i = 0; i < parts.length - 1; i++) {
40910 curPart = parts[i];
40911
40912 if(typeof curPart === 'number' && !isArrayOrTypedArray(curCont)) {
40913 throw 'array index but container is not an array';
40914 }
40915
40916 // handle special -1 array index
40917 if(curPart === -1) {
40918 toDelete = !setArrayAll(curCont, parts.slice(i + 1), val, propStr);
40919 if(toDelete) break;
40920 else return;
40921 }
40922
40923 if(!checkNewContainer(curCont, curPart, parts[i + 1], toDelete)) {
40924 break;
40925 }
40926
40927 curCont = curCont[curPart];
40928
40929 if(typeof curCont !== 'object' || curCont === null) {
40930 throw 'container is not an object';
40931 }
40932
40933 propPart = joinPropStr(propPart, curPart);
40934
40935 containerLevels.push([curCont, propPart]);
40936 }
40937
40938 if(toDelete) {
40939 if(i === parts.length - 1) {
40940 delete curCont[parts[i]];
40941
40942 // The one bit of pruning we still do: drop `undefined` from the end of arrays.
40943 // In case someone has already unset previous items, continue until we hit a
40944 // non-undefined value.
40945 if(Array.isArray(curCont) && +parts[i] === curCont.length - 1) {
40946 while(curCont.length && curCont[curCont.length - 1] === undefined) {
40947 curCont.pop();
40948 }
40949 }
40950 }
40951 } else curCont[parts[i]] = val;
40952 };
40953}
40954
40955function joinPropStr(propStr, newPart) {
40956 var toAdd = newPart;
40957 if(isNumeric(newPart)) toAdd = '[' + newPart + ']';
40958 else if(propStr) toAdd = '.' + newPart;
40959
40960 return propStr + toAdd;
40961}
40962
40963// handle special -1 array index
40964function setArrayAll(containerArray, innerParts, val, propStr) {
40965 var arrayVal = isArrayOrTypedArray(val);
40966 var allSet = true;
40967 var thisVal = val;
40968 var thisPropStr = propStr.replace('-1', 0);
40969 var deleteThis = arrayVal ? false : isDeletable(val, thisPropStr);
40970 var firstPart = innerParts[0];
40971 var i;
40972
40973 for(i = 0; i < containerArray.length; i++) {
40974 thisPropStr = propStr.replace('-1', i);
40975 if(arrayVal) {
40976 thisVal = val[i % val.length];
40977 deleteThis = isDeletable(thisVal, thisPropStr);
40978 }
40979 if(deleteThis) allSet = false;
40980 if(!checkNewContainer(containerArray, i, firstPart, deleteThis)) {
40981 continue;
40982 }
40983 npSet(containerArray[i], innerParts, propStr.replace('-1', i))(thisVal);
40984 }
40985 return allSet;
40986}
40987
40988/**
40989 * make new sub-container as needed.
40990 * returns false if there's no container and none is needed
40991 * because we're only deleting an attribute
40992 */
40993function checkNewContainer(container, part, nextPart, toDelete) {
40994 if(container[part] === undefined) {
40995 if(toDelete) return false;
40996
40997 if(typeof nextPart === 'number') container[part] = [];
40998 else container[part] = {};
40999 }
41000 return true;
41001}
41002
41003function badContainer(container, propStr, propParts) {
41004 return {
41005 set: function() { throw 'bad container'; },
41006 get: function() {},
41007 astr: propStr,
41008 parts: propParts,
41009 obj: container
41010 };
41011}
41012
41013},{"./array":165,"fast-isnumeric":18}],187:[function(_dereq_,module,exports){
41014/**
41015* Copyright 2012-2020, Plotly, Inc.
41016* All rights reserved.
41017*
41018* This source code is licensed under the MIT license found in the
41019* LICENSE file in the root directory of this source tree.
41020*/
41021
41022'use strict';
41023
41024// Simple helper functions
41025// none of these need any external deps
41026
41027module.exports = function noop() {};
41028
41029},{}],188:[function(_dereq_,module,exports){
41030/**
41031* Copyright 2012-2020, Plotly, Inc.
41032* All rights reserved.
41033*
41034* This source code is licensed under the MIT license found in the
41035* LICENSE file in the root directory of this source tree.
41036*/
41037
41038
41039'use strict';
41040
41041var d3 = _dereq_('d3');
41042var isNumeric = _dereq_('fast-isnumeric');
41043
41044var NOTEDATA = [];
41045
41046/**
41047 * notifier
41048 * @param {String} text The person's user name
41049 * @param {Number} [delay=1000] The delay time in milliseconds
41050 * or 'long' which provides 2000 ms delay time.
41051 * @return {undefined} this function does not return a value
41052 */
41053module.exports = function(text, displayLength) {
41054 if(NOTEDATA.indexOf(text) !== -1) return;
41055
41056 NOTEDATA.push(text);
41057
41058 var ts = 1000;
41059 if(isNumeric(displayLength)) ts = displayLength;
41060 else if(displayLength === 'long') ts = 3000;
41061
41062 var notifierContainer = d3.select('body')
41063 .selectAll('.plotly-notifier')
41064 .data([0]);
41065 notifierContainer.enter()
41066 .append('div')
41067 .classed('plotly-notifier', true);
41068
41069 var notes = notifierContainer.selectAll('.notifier-note').data(NOTEDATA);
41070
41071 function killNote(transition) {
41072 transition
41073 .duration(700)
41074 .style('opacity', 0)
41075 .each('end', function(thisText) {
41076 var thisIndex = NOTEDATA.indexOf(thisText);
41077 if(thisIndex !== -1) NOTEDATA.splice(thisIndex, 1);
41078 d3.select(this).remove();
41079 });
41080 }
41081
41082 notes.enter().append('div')
41083 .classed('notifier-note', true)
41084 .style('opacity', 0)
41085 .each(function(thisText) {
41086 var note = d3.select(this);
41087
41088 note.append('button')
41089 .classed('notifier-close', true)
41090 .html('&times;')
41091 .on('click', function() {
41092 note.transition().call(killNote);
41093 });
41094
41095 var p = note.append('p');
41096 var lines = thisText.split(/<br\s*\/?>/g);
41097 for(var i = 0; i < lines.length; i++) {
41098 if(i) p.append('br');
41099 p.append('span').text(lines[i]);
41100 }
41101
41102 if(displayLength === 'stick') {
41103 note.transition()
41104 .duration(350)
41105 .style('opacity', 1);
41106 } else {
41107 note.transition()
41108 .duration(700)
41109 .style('opacity', 1)
41110 .transition()
41111 .delay(ts)
41112 .call(killNote);
41113 }
41114 });
41115};
41116
41117},{"d3":16,"fast-isnumeric":18}],189:[function(_dereq_,module,exports){
41118/**
41119* Copyright 2012-2020, Plotly, Inc.
41120* All rights reserved.
41121*
41122* This source code is licensed under the MIT license found in the
41123* LICENSE file in the root directory of this source tree.
41124*/
41125
41126
41127'use strict';
41128
41129var setCursor = _dereq_('./setcursor');
41130
41131var STASHATTR = 'data-savedcursor';
41132var NO_CURSOR = '!!';
41133
41134/*
41135 * works with our CSS cursor classes (see css/_cursor.scss)
41136 * to override a previous cursor set on d3 single-element selections,
41137 * by moving the name of the original cursor to the data-savedcursor attr.
41138 * omit cursor to revert to the previously set value.
41139 */
41140module.exports = function overrideCursor(el3, csr) {
41141 var savedCursor = el3.attr(STASHATTR);
41142 if(csr) {
41143 if(!savedCursor) {
41144 var classes = (el3.attr('class') || '').split(' ');
41145 for(var i = 0; i < classes.length; i++) {
41146 var cls = classes[i];
41147 if(cls.indexOf('cursor-') === 0) {
41148 el3.attr(STASHATTR, cls.substr(7))
41149 .classed(cls, false);
41150 }
41151 }
41152 if(!el3.attr(STASHATTR)) {
41153 el3.attr(STASHATTR, NO_CURSOR);
41154 }
41155 }
41156 setCursor(el3, csr);
41157 } else if(savedCursor) {
41158 el3.attr(STASHATTR, null);
41159
41160 if(savedCursor === NO_CURSOR) setCursor(el3);
41161 else setCursor(el3, savedCursor);
41162 }
41163};
41164
41165},{"./setcursor":197}],190:[function(_dereq_,module,exports){
41166/**
41167* Copyright 2012-2020, Plotly, Inc.
41168* All rights reserved.
41169*
41170* This source code is licensed under the MIT license found in the
41171* LICENSE file in the root directory of this source tree.
41172*/
41173
41174
41175'use strict';
41176
41177var dot = _dereq_('./matrix').dot;
41178var BADNUM = _dereq_('../constants/numerical').BADNUM;
41179
41180var polygon = module.exports = {};
41181
41182/**
41183 * Turn an array of [x, y] pairs into a polygon object
41184 * that can test if points are inside it
41185 *
41186 * @param ptsIn Array of [x, y] pairs
41187 *
41188 * @returns polygon Object {xmin, xmax, ymin, ymax, pts, contains}
41189 * (x|y)(min|max) are the bounding rect of the polygon
41190 * pts is the original array, with the first pair repeated at the end
41191 * contains is a function: (pt, omitFirstEdge)
41192 * pt is the [x, y] pair to test
41193 * omitFirstEdge truthy means points exactly on the first edge don't
41194 * count. This is for use adding one polygon to another so we
41195 * don't double-count the edge where they meet.
41196 * returns boolean: is pt inside the polygon (including on its edges)
41197 */
41198polygon.tester = function tester(ptsIn) {
41199 var pts = ptsIn.slice();
41200 var xmin = pts[0][0];
41201 var xmax = xmin;
41202 var ymin = pts[0][1];
41203 var ymax = ymin;
41204 var i;
41205
41206 pts.push(pts[0]);
41207 for(i = 1; i < pts.length; i++) {
41208 xmin = Math.min(xmin, pts[i][0]);
41209 xmax = Math.max(xmax, pts[i][0]);
41210 ymin = Math.min(ymin, pts[i][1]);
41211 ymax = Math.max(ymax, pts[i][1]);
41212 }
41213
41214 // do we have a rectangle? Handle this here, so we can use the same
41215 // tester for the rectangular case without sacrificing speed
41216
41217 var isRect = false;
41218 var rectFirstEdgeTest;
41219
41220 if(pts.length === 5) {
41221 if(pts[0][0] === pts[1][0]) { // vert, horz, vert, horz
41222 if(pts[2][0] === pts[3][0] &&
41223 pts[0][1] === pts[3][1] &&
41224 pts[1][1] === pts[2][1]) {
41225 isRect = true;
41226 rectFirstEdgeTest = function(pt) { return pt[0] === pts[0][0]; };
41227 }
41228 } else if(pts[0][1] === pts[1][1]) { // horz, vert, horz, vert
41229 if(pts[2][1] === pts[3][1] &&
41230 pts[0][0] === pts[3][0] &&
41231 pts[1][0] === pts[2][0]) {
41232 isRect = true;
41233 rectFirstEdgeTest = function(pt) { return pt[1] === pts[0][1]; };
41234 }
41235 }
41236 }
41237
41238 function rectContains(pt, omitFirstEdge) {
41239 var x = pt[0];
41240 var y = pt[1];
41241
41242 if(x === BADNUM || x < xmin || x > xmax || y === BADNUM || y < ymin || y > ymax) {
41243 // pt is outside the bounding box of polygon
41244 return false;
41245 }
41246 if(omitFirstEdge && rectFirstEdgeTest(pt)) return false;
41247
41248 return true;
41249 }
41250
41251 function contains(pt, omitFirstEdge) {
41252 var x = pt[0];
41253 var y = pt[1];
41254
41255 if(x === BADNUM || x < xmin || x > xmax || y === BADNUM || y < ymin || y > ymax) {
41256 // pt is outside the bounding box of polygon
41257 return false;
41258 }
41259
41260 var imax = pts.length;
41261 var x1 = pts[0][0];
41262 var y1 = pts[0][1];
41263 var crossings = 0;
41264 var i;
41265 var x0;
41266 var y0;
41267 var xmini;
41268 var ycross;
41269
41270 for(i = 1; i < imax; i++) {
41271 // find all crossings of a vertical line upward from pt with
41272 // polygon segments
41273 // crossings exactly at xmax don't count, unless the point is
41274 // exactly on the segment, then it counts as inside.
41275 x0 = x1;
41276 y0 = y1;
41277 x1 = pts[i][0];
41278 y1 = pts[i][1];
41279 xmini = Math.min(x0, x1);
41280
41281 if(x < xmini || x > Math.max(x0, x1) || y > Math.max(y0, y1)) {
41282 // outside the bounding box of this segment, it's only a crossing
41283 // if it's below the box.
41284
41285 continue;
41286 } else if(y < Math.min(y0, y1)) {
41287 // don't count the left-most point of the segment as a crossing
41288 // because we don't want to double-count adjacent crossings
41289 // UNLESS the polygon turns past vertical at exactly this x
41290 // Note that this is repeated below, but we can't factor it out
41291 // because
41292 if(x !== xmini) crossings++;
41293 } else {
41294 // inside the bounding box, check the actual line intercept
41295
41296 // vertical segment - we know already that the point is exactly
41297 // on the segment, so mark the crossing as exactly at the point.
41298 if(x1 === x0) ycross = y;
41299 // any other angle
41300 else ycross = y0 + (x - x0) * (y1 - y0) / (x1 - x0);
41301
41302 // exactly on the edge: counts as inside the polygon, unless it's the
41303 // first edge and we're omitting it.
41304 if(y === ycross) {
41305 if(i === 1 && omitFirstEdge) return false;
41306 return true;
41307 }
41308
41309 if(y <= ycross && x !== xmini) crossings++;
41310 }
41311 }
41312
41313 // if we've gotten this far, odd crossings means inside, even is outside
41314 return crossings % 2 === 1;
41315 }
41316
41317 // detect if poly is degenerate
41318 var degenerate = true;
41319 var lastPt = pts[0];
41320 for(i = 1; i < pts.length; i++) {
41321 if(lastPt[0] !== pts[i][0] || lastPt[1] !== pts[i][1]) {
41322 degenerate = false;
41323 break;
41324 }
41325 }
41326
41327 return {
41328 xmin: xmin,
41329 xmax: xmax,
41330 ymin: ymin,
41331 ymax: ymax,
41332 pts: pts,
41333 contains: isRect ? rectContains : contains,
41334 isRect: isRect,
41335 degenerate: degenerate
41336 };
41337};
41338
41339/**
41340 * Test if a segment of a points array is bent or straight
41341 *
41342 * @param pts Array of [x, y] pairs
41343 * @param start the index of the proposed start of the straight section
41344 * @param end the index of the proposed end point
41345 * @param tolerance the max distance off the line connecting start and end
41346 * before the line counts as bent
41347 * @returns boolean: true means this segment is bent, false means straight
41348 */
41349polygon.isSegmentBent = function isSegmentBent(pts, start, end, tolerance) {
41350 var startPt = pts[start];
41351 var segment = [pts[end][0] - startPt[0], pts[end][1] - startPt[1]];
41352 var segmentSquared = dot(segment, segment);
41353 var segmentLen = Math.sqrt(segmentSquared);
41354 var unitPerp = [-segment[1] / segmentLen, segment[0] / segmentLen];
41355 var i;
41356 var part;
41357 var partParallel;
41358
41359 for(i = start + 1; i < end; i++) {
41360 part = [pts[i][0] - startPt[0], pts[i][1] - startPt[1]];
41361 partParallel = dot(part, segment);
41362
41363 if(partParallel < 0 || partParallel > segmentSquared ||
41364 Math.abs(dot(part, unitPerp)) > tolerance) return true;
41365 }
41366 return false;
41367};
41368
41369/**
41370 * Make a filtering polygon, to minimize the number of segments
41371 *
41372 * @param pts Array of [x, y] pairs (must start with at least 1 pair)
41373 * @param tolerance the maximum deviation from straight allowed for
41374 * removing points to simplify the polygon
41375 *
41376 * @returns Object {addPt, raw, filtered}
41377 * addPt is a function(pt: [x, y] pair) to add a raw point and
41378 * continue filtering
41379 * raw is all the input points
41380 * filtered is the resulting filtered Array of [x, y] pairs
41381 */
41382polygon.filter = function filter(pts, tolerance) {
41383 var ptsFiltered = [pts[0]];
41384 var doneRawIndex = 0;
41385 var doneFilteredIndex = 0;
41386
41387 function addPt(pt) {
41388 pts.push(pt);
41389 var prevFilterLen = ptsFiltered.length;
41390 var iLast = doneRawIndex;
41391 ptsFiltered.splice(doneFilteredIndex + 1);
41392
41393 for(var i = iLast + 1; i < pts.length; i++) {
41394 if(i === pts.length - 1 || polygon.isSegmentBent(pts, iLast, i + 1, tolerance)) {
41395 ptsFiltered.push(pts[i]);
41396 if(ptsFiltered.length < prevFilterLen - 2) {
41397 doneRawIndex = i;
41398 doneFilteredIndex = ptsFiltered.length - 1;
41399 }
41400 iLast = i;
41401 }
41402 }
41403 }
41404
41405 if(pts.length > 1) {
41406 var lastPt = pts.pop();
41407 addPt(lastPt);
41408 }
41409
41410 return {
41411 addPt: addPt,
41412 raw: pts,
41413 filtered: ptsFiltered
41414 };
41415};
41416
41417},{"../constants/numerical":158,"./matrix":184}],191:[function(_dereq_,module,exports){
41418/**
41419* Copyright 2012-2020, Plotly, Inc.
41420* All rights reserved.
41421*
41422* This source code is licensed under the MIT license found in the
41423* LICENSE file in the root directory of this source tree.
41424*/
41425
41426'use strict';
41427
41428/**
41429 * Push array with unique items
41430 *
41431 * Ignores falsy items, except 0 so we can use it to construct arrays of indices.
41432 *
41433 * @param {array} array
41434 * array to be filled
41435 * @param {any} item
41436 * item to be or not to be inserted
41437 * @return {array}
41438 * ref to array (now possibly containing one more item)
41439 *
41440 */
41441module.exports = function pushUnique(array, item) {
41442 if(item instanceof RegExp) {
41443 var itemStr = item.toString();
41444 for(var i = 0; i < array.length; i++) {
41445 if(array[i] instanceof RegExp && array[i].toString() === itemStr) {
41446 return array;
41447 }
41448 }
41449 array.push(item);
41450 } else if((item || item === 0) && array.indexOf(item) === -1) array.push(item);
41451
41452 return array;
41453};
41454
41455},{}],192:[function(_dereq_,module,exports){
41456/**
41457* Copyright 2012-2020, Plotly, Inc.
41458* All rights reserved.
41459*
41460* This source code is licensed under the MIT license found in the
41461* LICENSE file in the root directory of this source tree.
41462*/
41463
41464'use strict';
41465
41466var Lib = _dereq_('../lib');
41467var dfltConfig = _dereq_('../plot_api/plot_config').dfltConfig;
41468
41469/**
41470 * Copy arg array *without* removing `undefined` values from objects.
41471 *
41472 * @param gd
41473 * @param args
41474 * @returns {Array}
41475 */
41476function copyArgArray(gd, args) {
41477 var copy = [];
41478 var arg;
41479
41480 for(var i = 0; i < args.length; i++) {
41481 arg = args[i];
41482
41483 if(arg === gd) copy[i] = arg;
41484 else if(typeof arg === 'object') {
41485 copy[i] = Array.isArray(arg) ?
41486 Lib.extendDeep([], arg) :
41487 Lib.extendDeepAll({}, arg);
41488 } else copy[i] = arg;
41489 }
41490
41491 return copy;
41492}
41493
41494
41495// -----------------------------------------------------
41496// Undo/Redo queue for plots
41497// -----------------------------------------------------
41498
41499
41500var queue = {};
41501
41502// TODO: disable/enable undo and redo buttons appropriately
41503
41504/**
41505 * Add an item to the undoQueue for a graphDiv
41506 *
41507 * @param gd
41508 * @param undoFunc Function undo this operation
41509 * @param undoArgs Args to supply undoFunc with
41510 * @param redoFunc Function to redo this operation
41511 * @param redoArgs Args to supply redoFunc with
41512 */
41513queue.add = function(gd, undoFunc, undoArgs, redoFunc, redoArgs) {
41514 var queueObj,
41515 queueIndex;
41516
41517 // make sure we have the queue and our position in it
41518 gd.undoQueue = gd.undoQueue || {index: 0, queue: [], sequence: false};
41519 queueIndex = gd.undoQueue.index;
41520
41521 // if we're already playing an undo or redo, or if this is an auto operation
41522 // (like pane resize... any others?) then we don't save this to the undo queue
41523 if(gd.autoplay) {
41524 if(!gd.undoQueue.inSequence) gd.autoplay = false;
41525 return;
41526 }
41527
41528 // if we're not in a sequence or are just starting, we need a new queue item
41529 if(!gd.undoQueue.sequence || gd.undoQueue.beginSequence) {
41530 queueObj = {undo: {calls: [], args: []}, redo: {calls: [], args: []}};
41531 gd.undoQueue.queue.splice(queueIndex, gd.undoQueue.queue.length - queueIndex, queueObj);
41532 gd.undoQueue.index += 1;
41533 } else {
41534 queueObj = gd.undoQueue.queue[queueIndex - 1];
41535 }
41536 gd.undoQueue.beginSequence = false;
41537
41538 // we unshift to handle calls for undo in a forward for loop later
41539 if(queueObj) {
41540 queueObj.undo.calls.unshift(undoFunc);
41541 queueObj.undo.args.unshift(undoArgs);
41542 queueObj.redo.calls.push(redoFunc);
41543 queueObj.redo.args.push(redoArgs);
41544 }
41545
41546 if(gd.undoQueue.queue.length > dfltConfig.queueLength) {
41547 gd.undoQueue.queue.shift();
41548 gd.undoQueue.index--;
41549 }
41550};
41551
41552/**
41553 * Begin a sequence of undoQueue changes
41554 *
41555 * @param gd
41556 */
41557queue.startSequence = function(gd) {
41558 gd.undoQueue = gd.undoQueue || {index: 0, queue: [], sequence: false};
41559 gd.undoQueue.sequence = true;
41560 gd.undoQueue.beginSequence = true;
41561};
41562
41563/**
41564 * Stop a sequence of undoQueue changes
41565 *
41566 * Call this *after* you're sure your undo chain has ended
41567 *
41568 * @param gd
41569 */
41570queue.stopSequence = function(gd) {
41571 gd.undoQueue = gd.undoQueue || {index: 0, queue: [], sequence: false};
41572 gd.undoQueue.sequence = false;
41573 gd.undoQueue.beginSequence = false;
41574};
41575
41576/**
41577 * Move one step back in the undo queue, and undo the object there.
41578 *
41579 * @param gd
41580 */
41581queue.undo = function undo(gd) {
41582 var queueObj, i;
41583
41584 if(gd.framework && gd.framework.isPolar) {
41585 gd.framework.undo();
41586 return;
41587 }
41588 if(gd.undoQueue === undefined ||
41589 isNaN(gd.undoQueue.index) ||
41590 gd.undoQueue.index <= 0) {
41591 return;
41592 }
41593
41594 // index is pointing to next *forward* queueObj, point to the one we're undoing
41595 gd.undoQueue.index--;
41596
41597 // get the queueObj for instructions on how to undo
41598 queueObj = gd.undoQueue.queue[gd.undoQueue.index];
41599
41600 // this sequence keeps things from adding to the queue during undo/redo
41601 gd.undoQueue.inSequence = true;
41602 for(i = 0; i < queueObj.undo.calls.length; i++) {
41603 queue.plotDo(gd, queueObj.undo.calls[i], queueObj.undo.args[i]);
41604 }
41605 gd.undoQueue.inSequence = false;
41606 gd.autoplay = false;
41607};
41608
41609/**
41610 * Redo the current object in the undo, then move forward in the queue.
41611 *
41612 * @param gd
41613 */
41614queue.redo = function redo(gd) {
41615 var queueObj, i;
41616
41617 if(gd.framework && gd.framework.isPolar) {
41618 gd.framework.redo();
41619 return;
41620 }
41621 if(gd.undoQueue === undefined ||
41622 isNaN(gd.undoQueue.index) ||
41623 gd.undoQueue.index >= gd.undoQueue.queue.length) {
41624 return;
41625 }
41626
41627 // get the queueObj for instructions on how to undo
41628 queueObj = gd.undoQueue.queue[gd.undoQueue.index];
41629
41630 // this sequence keeps things from adding to the queue during undo/redo
41631 gd.undoQueue.inSequence = true;
41632 for(i = 0; i < queueObj.redo.calls.length; i++) {
41633 queue.plotDo(gd, queueObj.redo.calls[i], queueObj.redo.args[i]);
41634 }
41635 gd.undoQueue.inSequence = false;
41636 gd.autoplay = false;
41637
41638 // index is pointing to the thing we just redid, move it
41639 gd.undoQueue.index++;
41640};
41641
41642/**
41643 * Called by undo/redo to make the actual changes.
41644 *
41645 * Not meant to be called publically, but included for mocking out in tests.
41646 *
41647 * @param gd
41648 * @param func
41649 * @param args
41650 */
41651queue.plotDo = function(gd, func, args) {
41652 gd.autoplay = true;
41653
41654 // this *won't* copy gd and it preserves `undefined` properties!
41655 args = copyArgArray(gd, args);
41656
41657 // call the supplied function
41658 func.apply(null, args);
41659};
41660
41661module.exports = queue;
41662
41663},{"../lib":178,"../plot_api/plot_config":210}],193:[function(_dereq_,module,exports){
41664/**
41665* Copyright 2012-2020, Plotly, Inc.
41666* All rights reserved.
41667*
41668* This source code is licensed under the MIT license found in the
41669* LICENSE file in the root directory of this source tree.
41670*/
41671
41672'use strict';
41673
41674/*
41675 * make a regex for matching counter ids/names ie xaxis, xaxis2, xaxis10...
41676 *
41677 * @param {string} head: the head of the pattern, eg 'x' matches 'x', 'x2', 'x10' etc.
41678 * 'xy' is a special case for cartesian subplots: it matches 'x2y3' etc
41679 * @param {Optional(string)} tail: a fixed piece after the id
41680 * eg counterRegex('scene', '.annotations') for scene2.annotations etc.
41681 * @param {boolean} openEnded: if true, the string may continue past the match.
41682 * @param {boolean} matchBeginning: if false, the string may start before the match.
41683 */
41684exports.counter = function(head, tail, openEnded, matchBeginning) {
41685 var fullTail = (tail || '') + (openEnded ? '' : '$');
41686 var startWithPrefix = matchBeginning === false ? '' : '^';
41687 if(head === 'xy') {
41688 return new RegExp(startWithPrefix + 'x([2-9]|[1-9][0-9]+)?y([2-9]|[1-9][0-9]+)?' + fullTail);
41689 }
41690 return new RegExp(startWithPrefix + head + '([2-9]|[1-9][0-9]+)?' + fullTail);
41691};
41692
41693},{}],194:[function(_dereq_,module,exports){
41694/**
41695* Copyright 2012-2020, Plotly, Inc.
41696* All rights reserved.
41697*
41698* This source code is licensed under the MIT license found in the
41699* LICENSE file in the root directory of this source tree.
41700*/
41701
41702
41703'use strict';
41704
41705// ASCEND: chop off the last nesting level - either [<n>] or .<key> - to ascend
41706// the attribute tree. the remaining attrString is in match[1]
41707var ASCEND = /^(.*)(\.[^\.\[\]]+|\[\d\])$/;
41708
41709// SIMPLEATTR: is this an un-nested attribute? (no dots or brackets)
41710var SIMPLEATTR = /^[^\.\[\]]+$/;
41711
41712/*
41713 * calculate a relative attribute string, similar to a relative path
41714 *
41715 * @param {string} baseAttr:
41716 * an attribute string, such as 'annotations[3].x'. The "current location"
41717 * is the attribute string minus the last component ('annotations[3]')
41718 * @param {string} relativeAttr:
41719 * a route to the desired attribute string, using '^' to ascend
41720 *
41721 * @return {string} attrString:
41722 * for example:
41723 * relativeAttr('annotations[3].x', 'y') = 'annotations[3].y'
41724 * relativeAttr('annotations[3].x', '^[2].z') = 'annotations[2].z'
41725 * relativeAttr('annotations[3].x', '^^margin') = 'margin'
41726 * relativeAttr('annotations[3].x', '^^margin.r') = 'margin.r'
41727 */
41728module.exports = function(baseAttr, relativeAttr) {
41729 while(relativeAttr) {
41730 var match = baseAttr.match(ASCEND);
41731
41732 if(match) baseAttr = match[1];
41733 else if(baseAttr.match(SIMPLEATTR)) baseAttr = '';
41734 else throw new Error('bad relativeAttr call:' + [baseAttr, relativeAttr]);
41735
41736 if(relativeAttr.charAt(0) === '^') relativeAttr = relativeAttr.slice(1);
41737 else break;
41738 }
41739
41740 if(baseAttr && relativeAttr.charAt(0) !== '[') {
41741 return baseAttr + '.' + relativeAttr;
41742 }
41743 return baseAttr + relativeAttr;
41744};
41745
41746},{}],195:[function(_dereq_,module,exports){
41747/**
41748* Copyright 2012-2020, Plotly, Inc.
41749* All rights reserved.
41750*
41751* This source code is licensed under the MIT license found in the
41752* LICENSE file in the root directory of this source tree.
41753*/
41754
41755
41756'use strict';
41757
41758var isArrayOrTypedArray = _dereq_('./array').isArrayOrTypedArray;
41759var isPlainObject = _dereq_('./is_plain_object');
41760
41761/**
41762 * Relink private _keys and keys with a function value from one container
41763 * to the new container.
41764 * Relink means copying if object is pass-by-value and adding a reference
41765 * if object is pass-by-ref.
41766 * This prevents deepCopying massive structures like a webgl context.
41767 */
41768module.exports = function relinkPrivateKeys(toContainer, fromContainer) {
41769 for(var k in fromContainer) {
41770 var fromVal = fromContainer[k];
41771 var toVal = toContainer[k];
41772
41773 if(toVal === fromVal) continue;
41774 if(toContainer.matches && k === '_categoriesMap') continue;
41775
41776 if(k.charAt(0) === '_' || typeof fromVal === 'function') {
41777 // if it already exists at this point, it's something
41778 // that we recreate each time around, so ignore it
41779 if(k in toContainer) continue;
41780
41781 toContainer[k] = fromVal;
41782 } else if(isArrayOrTypedArray(fromVal) && isArrayOrTypedArray(toVal) && isPlainObject(fromVal[0])) {
41783 // filter out data_array items that can contain user objects
41784 // most of the time the toVal === fromVal check will catch these early
41785 // but if the user makes new ones we also don't want to recurse in.
41786 if(k === 'customdata' || k === 'ids') continue;
41787
41788 // recurse into arrays containers
41789 var minLen = Math.min(fromVal.length, toVal.length);
41790 for(var j = 0; j < minLen; j++) {
41791 if((toVal[j] !== fromVal[j]) && isPlainObject(fromVal[j]) && isPlainObject(toVal[j])) {
41792 relinkPrivateKeys(toVal[j], fromVal[j]);
41793 }
41794 }
41795 } else if(isPlainObject(fromVal) && isPlainObject(toVal)) {
41796 // recurse into objects, but only if they still exist
41797 relinkPrivateKeys(toVal, fromVal);
41798
41799 if(!Object.keys(toVal).length) delete toContainer[k];
41800 }
41801 }
41802};
41803
41804},{"./array":165,"./is_plain_object":179}],196:[function(_dereq_,module,exports){
41805/**
41806* Copyright 2012-2020, Plotly, Inc.
41807* All rights reserved.
41808*
41809* This source code is licensed under the MIT license found in the
41810* LICENSE file in the root directory of this source tree.
41811*/
41812
41813
41814'use strict';
41815
41816var isNumeric = _dereq_('fast-isnumeric');
41817var loggers = _dereq_('./loggers');
41818var identity = _dereq_('./identity');
41819var BADNUM = _dereq_('../constants/numerical').BADNUM;
41820
41821// don't trust floating point equality - fraction of bin size to call
41822// "on the line" and ensure that they go the right way specified by
41823// linelow
41824var roundingError = 1e-9;
41825
41826
41827/**
41828 * findBin - find the bin for val - note that it can return outside the
41829 * bin range any pos. or neg. integer for linear bins, or -1 or
41830 * bins.length-1 for explicit.
41831 * bins is either an object {start,size,end} or an array length #bins+1
41832 * bins can be either increasing or decreasing but must be monotonic
41833 * for linear bins, we can just calculate. For listed bins, run a binary
41834 * search linelow (truthy) says the bin boundary should be attributed to
41835 * the lower bin rather than the default upper bin
41836 */
41837exports.findBin = function(val, bins, linelow) {
41838 if(isNumeric(bins.start)) {
41839 return linelow ?
41840 Math.ceil((val - bins.start) / bins.size - roundingError) - 1 :
41841 Math.floor((val - bins.start) / bins.size + roundingError);
41842 } else {
41843 var n1 = 0;
41844 var n2 = bins.length;
41845 var c = 0;
41846 var binSize = (n2 > 1) ? (bins[n2 - 1] - bins[0]) / (n2 - 1) : 1;
41847 var n, test;
41848 if(binSize >= 0) {
41849 test = linelow ? lessThan : lessOrEqual;
41850 } else {
41851 test = linelow ? greaterOrEqual : greaterThan;
41852 }
41853 val += binSize * roundingError * (linelow ? -1 : 1) * (binSize >= 0 ? 1 : -1);
41854 // c is just to avoid infinite loops if there's an error
41855 while(n1 < n2 && c++ < 100) {
41856 n = Math.floor((n1 + n2) / 2);
41857 if(test(bins[n], val)) n1 = n + 1;
41858 else n2 = n;
41859 }
41860 if(c > 90) loggers.log('Long binary search...');
41861 return n1 - 1;
41862 }
41863};
41864
41865function lessThan(a, b) { return a < b; }
41866function lessOrEqual(a, b) { return a <= b; }
41867function greaterThan(a, b) { return a > b; }
41868function greaterOrEqual(a, b) { return a >= b; }
41869
41870exports.sorterAsc = function(a, b) { return a - b; };
41871exports.sorterDes = function(a, b) { return b - a; };
41872
41873/**
41874 * find distinct values in an array, lumping together ones that appear to
41875 * just be off by a rounding error
41876 * return the distinct values and the minimum difference between any two
41877 */
41878exports.distinctVals = function(valsIn) {
41879 var vals = valsIn.slice(); // otherwise we sort the original array...
41880 vals.sort(exports.sorterAsc); // undefined listed in the end - also works on IE11
41881
41882 var last;
41883 for(last = vals.length - 1; last > -1; last--) {
41884 if(vals[last] !== BADNUM) break;
41885 }
41886
41887 var minDiff = (vals[last] - vals[0]) || 1;
41888 var errDiff = minDiff / (last || 1) / 10000;
41889 var newVals = [];
41890 var preV;
41891 for(var i = 0; i <= last; i++) {
41892 var v = vals[i];
41893
41894 // make sure values aren't just off by a rounding error
41895 var diff = v - preV;
41896
41897 if(preV === undefined) {
41898 newVals.push(v);
41899 preV = v;
41900 } else if(diff > errDiff) {
41901 minDiff = Math.min(minDiff, diff);
41902
41903 newVals.push(v);
41904 preV = v;
41905 }
41906 }
41907
41908 return {vals: newVals, minDiff: minDiff};
41909};
41910
41911/**
41912 * return the smallest element from (sorted) array arrayIn that's bigger than val,
41913 * or (reverse) the largest element smaller than val
41914 * used to find the best tick given the minimum (non-rounded) tick
41915 * particularly useful for date/time where things are not powers of 10
41916 * binary search is probably overkill here...
41917 */
41918exports.roundUp = function(val, arrayIn, reverse) {
41919 var low = 0;
41920 var high = arrayIn.length - 1;
41921 var mid;
41922 var c = 0;
41923 var dlow = reverse ? 0 : 1;
41924 var dhigh = reverse ? 1 : 0;
41925 var rounded = reverse ? Math.ceil : Math.floor;
41926 // c is just to avoid infinite loops if there's an error
41927 while(low < high && c++ < 100) {
41928 mid = rounded((low + high) / 2);
41929 if(arrayIn[mid] <= val) low = mid + dlow;
41930 else high = mid - dhigh;
41931 }
41932 return arrayIn[low];
41933};
41934
41935/**
41936 * Tweak to Array.sort(sortFn) that improves performance for pre-sorted arrays
41937 *
41938 * Note that newer browsers (such as Chrome v70+) are starting to pick up
41939 * on pre-sorted arrays which may render the following optimization unnecessary
41940 * in the future.
41941 *
41942 * Motivation: sometimes we need to sort arrays but the input is likely to
41943 * already be sorted. Browsers don't seem to pick up on pre-sorted arrays,
41944 * and in fact Chrome is actually *slower* sorting pre-sorted arrays than purely
41945 * random arrays. FF is at least faster if the array is pre-sorted, but still
41946 * not as fast as it could be.
41947 * Here's how this plays out sorting a length-1e6 array:
41948 *
41949 * Calls to Sort FN | Chrome bare | FF bare | Chrome tweak | FF tweak
41950 * | v68.0 Mac | v61.0 Mac| |
41951 * ------------------+---------------+-----------+----------------+------------
41952 * ordered | 30.4e6 | 10.1e6 | 1e6 | 1e6
41953 * reversed | 29.4e6 | 9.9e6 | 1e6 + reverse | 1e6 + reverse
41954 * random | ~21e6 | ~18.7e6 | ~21e6 | ~18.7e6
41955 *
41956 * So this is a substantial win for pre-sorted (ordered or exactly reversed)
41957 * arrays. Including this wrapper on an unsorted array adds a penalty that will
41958 * in general be only a few calls to the sort function. The only case this
41959 * penalty will be significant is if the array is mostly sorted but there are
41960 * a few unsorted items near the end, but the penalty is still at most N calls
41961 * out of (for N=1e6) ~20N total calls
41962 *
41963 * @param {Array} array: the array, to be sorted in place
41964 * @param {function} sortFn: As in Array.sort, function(a, b) that puts
41965 * item a before item b if the return is negative, a after b if positive,
41966 * and no change if zero.
41967 * @return {Array}: the original array, sorted in place.
41968 */
41969exports.sort = function(array, sortFn) {
41970 var notOrdered = 0;
41971 var notReversed = 0;
41972 for(var i = 1; i < array.length; i++) {
41973 var pairOrder = sortFn(array[i], array[i - 1]);
41974 if(pairOrder < 0) notOrdered = 1;
41975 else if(pairOrder > 0) notReversed = 1;
41976 if(notOrdered && notReversed) return array.sort(sortFn);
41977 }
41978 return notReversed ? array : array.reverse();
41979};
41980
41981/**
41982 * find index in array 'arr' that minimizes 'fn'
41983 *
41984 * @param {array} arr : array where to search
41985 * @param {fn (optional)} fn : function to minimize,
41986 * if not given, fn is the identity function
41987 * @return {integer}
41988 */
41989exports.findIndexOfMin = function(arr, fn) {
41990 fn = fn || identity;
41991
41992 var min = Infinity;
41993 var ind;
41994
41995 for(var i = 0; i < arr.length; i++) {
41996 var v = fn(arr[i]);
41997 if(v < min) {
41998 min = v;
41999 ind = i;
42000 }
42001 }
42002 return ind;
42003};
42004
42005},{"../constants/numerical":158,"./identity":177,"./loggers":182,"fast-isnumeric":18}],197:[function(_dereq_,module,exports){
42006/**
42007* Copyright 2012-2020, Plotly, Inc.
42008* All rights reserved.
42009*
42010* This source code is licensed under the MIT license found in the
42011* LICENSE file in the root directory of this source tree.
42012*/
42013
42014
42015'use strict';
42016
42017// works with our CSS cursor classes (see css/_cursor.scss)
42018// to apply cursors to d3 single-element selections.
42019// omit cursor to revert to the default.
42020module.exports = function setCursor(el3, csr) {
42021 (el3.attr('class') || '').split(' ').forEach(function(cls) {
42022 if(cls.indexOf('cursor-') === 0) el3.classed(cls, false);
42023 });
42024
42025 if(csr) el3.classed('cursor-' + csr, true);
42026};
42027
42028},{}],198:[function(_dereq_,module,exports){
42029/**
42030* Copyright 2012-2020, Plotly, Inc.
42031* All rights reserved.
42032*
42033* This source code is licensed under the MIT license found in the
42034* LICENSE file in the root directory of this source tree.
42035*/
42036
42037
42038'use strict';
42039
42040var isNumeric = _dereq_('fast-isnumeric');
42041var isArrayOrTypedArray = _dereq_('./array').isArrayOrTypedArray;
42042
42043/**
42044 * aggNums() returns the result of an aggregate function applied to an array of
42045 * values, where non-numerical values have been tossed out.
42046 *
42047 * @param {function} f - aggregation function (e.g., Math.min)
42048 * @param {Number} v - initial value (continuing from previous calls)
42049 * if there's no continuing value, use null for selector-type
42050 * functions (max,min), or 0 for summations
42051 * @param {Array} a - array to aggregate (may be nested, we will recurse,
42052 * but all elements must have the same dimension)
42053 * @param {Number} len - maximum length of a to aggregate
42054 * @return {Number} - result of f applied to a starting from v
42055 */
42056exports.aggNums = function(f, v, a, len) {
42057 var i,
42058 b;
42059 if(!len || len > a.length) len = a.length;
42060 if(!isNumeric(v)) v = false;
42061 if(isArrayOrTypedArray(a[0])) {
42062 b = new Array(len);
42063 for(i = 0; i < len; i++) b[i] = exports.aggNums(f, v, a[i]);
42064 a = b;
42065 }
42066
42067 for(i = 0; i < len; i++) {
42068 if(!isNumeric(v)) v = a[i];
42069 else if(isNumeric(a[i])) v = f(+v, +a[i]);
42070 }
42071 return v;
42072};
42073
42074/**
42075 * mean & std dev functions using aggNums, so it handles non-numerics nicely
42076 * even need to use aggNums instead of .length, to toss out non-numerics
42077 */
42078exports.len = function(data) {
42079 return exports.aggNums(function(a) { return a + 1; }, 0, data);
42080};
42081
42082exports.mean = function(data, len) {
42083 if(!len) len = exports.len(data);
42084 return exports.aggNums(function(a, b) { return a + b; }, 0, data) / len;
42085};
42086
42087exports.midRange = function(numArr) {
42088 if(numArr === undefined || numArr.length === 0) return undefined;
42089 return (exports.aggNums(Math.max, null, numArr) + exports.aggNums(Math.min, null, numArr)) / 2;
42090};
42091
42092exports.variance = function(data, len, mean) {
42093 if(!len) len = exports.len(data);
42094 if(!isNumeric(mean)) mean = exports.mean(data, len);
42095
42096 return exports.aggNums(function(a, b) {
42097 return a + Math.pow(b - mean, 2);
42098 }, 0, data) / len;
42099};
42100
42101exports.stdev = function(data, len, mean) {
42102 return Math.sqrt(exports.variance(data, len, mean));
42103};
42104
42105/**
42106 * median of a finite set of numbers
42107 * reference page: https://en.wikipedia.org/wiki/Median#Finite_set_of_numbers
42108**/
42109exports.median = function(data) {
42110 var b = data.slice().sort();
42111 return exports.interp(b, 0.5);
42112};
42113
42114/**
42115 * interp() computes a percentile (quantile) for a given distribution.
42116 * We interpolate the distribution (to compute quantiles, we follow method #10 here:
42117 * http://www.amstat.org/publications/jse/v14n3/langford.html).
42118 * Typically the index or rank (n * arr.length) may be non-integer.
42119 * For reference: ends are clipped to the extreme values in the array;
42120 * For box plots: index you get is half a point too high (see
42121 * http://en.wikipedia.org/wiki/Percentile#Nearest_rank) but note that this definition
42122 * indexes from 1 rather than 0, so we subtract 1/2 (instead of add).
42123 *
42124 * @param {Array} arr - This array contains the values that make up the distribution.
42125 * @param {Number} n - Between 0 and 1, n = p/100 is such that we compute the p^th percentile.
42126 * For example, the 50th percentile (or median) corresponds to n = 0.5
42127 * @return {Number} - percentile
42128 */
42129exports.interp = function(arr, n) {
42130 if(!isNumeric(n)) throw 'n should be a finite number';
42131 n = n * arr.length - 0.5;
42132 if(n < 0) return arr[0];
42133 if(n > arr.length - 1) return arr[arr.length - 1];
42134 var frac = n % 1;
42135 return frac * arr[Math.ceil(n)] + (1 - frac) * arr[Math.floor(n)];
42136};
42137
42138},{"./array":165,"fast-isnumeric":18}],199:[function(_dereq_,module,exports){
42139/**
42140* Copyright 2012-2020, Plotly, Inc.
42141* All rights reserved.
42142*
42143* This source code is licensed under the MIT license found in the
42144* LICENSE file in the root directory of this source tree.
42145*/
42146
42147
42148'use strict';
42149
42150/* global MathJax:false */
42151
42152var d3 = _dereq_('d3');
42153
42154var Lib = _dereq_('../lib');
42155var xmlnsNamespaces = _dereq_('../constants/xmlns_namespaces');
42156var LINE_SPACING = _dereq_('../constants/alignment').LINE_SPACING;
42157
42158// text converter
42159
42160function getSize(_selection, _dimension) {
42161 return _selection.node().getBoundingClientRect()[_dimension];
42162}
42163
42164var FIND_TEX = /([^$]*)([$]+[^$]*[$]+)([^$]*)/;
42165
42166exports.convertToTspans = function(_context, gd, _callback) {
42167 var str = _context.text();
42168
42169 // Until we get tex integrated more fully (so it can be used along with non-tex)
42170 // allow some elements to prohibit it by attaching 'data-notex' to the original
42171 var tex = (!_context.attr('data-notex')) &&
42172 (typeof MathJax !== 'undefined') &&
42173 str.match(FIND_TEX);
42174
42175 var parent = d3.select(_context.node().parentNode);
42176 if(parent.empty()) return;
42177 var svgClass = (_context.attr('class')) ? _context.attr('class').split(' ')[0] : 'text';
42178 svgClass += '-math';
42179 parent.selectAll('svg.' + svgClass).remove();
42180 parent.selectAll('g.' + svgClass + '-group').remove();
42181 _context.style('display', null)
42182 .attr({
42183 // some callers use data-unformatted *from the <text> element* in 'cancel'
42184 // so we need it here even if we're going to turn it into math
42185 // these two (plus style and text-anchor attributes) form the key we're
42186 // going to use for Drawing.bBox
42187 'data-unformatted': str,
42188 'data-math': 'N'
42189 });
42190
42191 function showText() {
42192 if(!parent.empty()) {
42193 svgClass = _context.attr('class') + '-math';
42194 parent.select('svg.' + svgClass).remove();
42195 }
42196 _context.text('')
42197 .style('white-space', 'pre');
42198
42199 var hasLink = buildSVGText(_context.node(), str);
42200
42201 if(hasLink) {
42202 // at least in Chrome, pointer-events does not seem
42203 // to be honored in children of <text> elements
42204 // so if we have an anchor, we have to make the
42205 // whole element respond
42206 _context.style('pointer-events', 'all');
42207 }
42208
42209 exports.positionText(_context);
42210
42211 if(_callback) _callback.call(_context);
42212 }
42213
42214 if(tex) {
42215 ((gd && gd._promises) || []).push(new Promise(function(resolve) {
42216 _context.style('display', 'none');
42217 var fontSize = parseInt(_context.node().style.fontSize, 10);
42218 var config = {fontSize: fontSize};
42219
42220 texToSVG(tex[2], config, function(_svgEl, _glyphDefs, _svgBBox) {
42221 parent.selectAll('svg.' + svgClass).remove();
42222 parent.selectAll('g.' + svgClass + '-group').remove();
42223
42224 var newSvg = _svgEl && _svgEl.select('svg');
42225 if(!newSvg || !newSvg.node()) {
42226 showText();
42227 resolve();
42228 return;
42229 }
42230
42231 var mathjaxGroup = parent.append('g')
42232 .classed(svgClass + '-group', true)
42233 .attr({
42234 'pointer-events': 'none',
42235 'data-unformatted': str,
42236 'data-math': 'Y'
42237 });
42238
42239 mathjaxGroup.node().appendChild(newSvg.node());
42240
42241 // stitch the glyph defs
42242 if(_glyphDefs && _glyphDefs.node()) {
42243 newSvg.node().insertBefore(_glyphDefs.node().cloneNode(true),
42244 newSvg.node().firstChild);
42245 }
42246
42247 newSvg.attr({
42248 'class': svgClass,
42249 height: _svgBBox.height,
42250 preserveAspectRatio: 'xMinYMin meet'
42251 })
42252 .style({overflow: 'visible', 'pointer-events': 'none'});
42253
42254 var fill = _context.node().style.fill || 'black';
42255 var g = newSvg.select('g');
42256 g.attr({fill: fill, stroke: fill});
42257
42258 var newSvgW = getSize(g, 'width');
42259 var newSvgH = getSize(g, 'height');
42260 var newX = +_context.attr('x') - newSvgW *
42261 {start: 0, middle: 0.5, end: 1}[_context.attr('text-anchor') || 'start'];
42262 // font baseline is about 1/4 fontSize below centerline
42263 var textHeight = fontSize || getSize(_context, 'height');
42264 var dy = -textHeight / 4;
42265
42266 if(svgClass[0] === 'y') {
42267 mathjaxGroup.attr({
42268 transform: 'rotate(' + [-90, +_context.attr('x'), +_context.attr('y')] +
42269 ') translate(' + [-newSvgW / 2, dy - newSvgH / 2] + ')'
42270 });
42271 newSvg.attr({x: +_context.attr('x'), y: +_context.attr('y')});
42272 } else if(svgClass[0] === 'l') {
42273 newSvg.attr({x: _context.attr('x'), y: dy - (newSvgH / 2)});
42274 } else if(svgClass[0] === 'a' && svgClass.indexOf('atitle') !== 0) {
42275 newSvg.attr({x: 0, y: dy});
42276 } else {
42277 newSvg.attr({x: newX, y: (+_context.attr('y') + dy - newSvgH / 2)});
42278 }
42279
42280 if(_callback) _callback.call(_context, mathjaxGroup);
42281 resolve(mathjaxGroup);
42282 });
42283 }));
42284 } else showText();
42285
42286 return _context;
42287};
42288
42289
42290// MathJax
42291
42292var LT_MATCH = /(<|&lt;|&#60;)/g;
42293var GT_MATCH = /(>|&gt;|&#62;)/g;
42294
42295function cleanEscapesForTex(s) {
42296 return s.replace(LT_MATCH, '\\lt ')
42297 .replace(GT_MATCH, '\\gt ');
42298}
42299
42300function texToSVG(_texString, _config, _callback) {
42301 var originalRenderer,
42302 originalConfig,
42303 originalProcessSectionDelay,
42304 tmpDiv;
42305
42306 MathJax.Hub.Queue(
42307 function() {
42308 originalConfig = Lib.extendDeepAll({}, MathJax.Hub.config);
42309
42310 originalProcessSectionDelay = MathJax.Hub.processSectionDelay;
42311 if(MathJax.Hub.processSectionDelay !== undefined) {
42312 // MathJax 2.5+
42313 MathJax.Hub.processSectionDelay = 0;
42314 }
42315
42316 return MathJax.Hub.Config({
42317 messageStyle: 'none',
42318 tex2jax: {
42319 inlineMath: [['$', '$'], ['\\(', '\\)']]
42320 },
42321 displayAlign: 'left',
42322 });
42323 },
42324 function() {
42325 // Get original renderer
42326 originalRenderer = MathJax.Hub.config.menuSettings.renderer;
42327 if(originalRenderer !== 'SVG') {
42328 return MathJax.Hub.setRenderer('SVG');
42329 }
42330 },
42331 function() {
42332 var randomID = 'math-output-' + Lib.randstr({}, 64);
42333 tmpDiv = d3.select('body').append('div')
42334 .attr({id: randomID})
42335 .style({visibility: 'hidden', position: 'absolute'})
42336 .style({'font-size': _config.fontSize + 'px'})
42337 .text(cleanEscapesForTex(_texString));
42338
42339 return MathJax.Hub.Typeset(tmpDiv.node());
42340 },
42341 function() {
42342 var glyphDefs = d3.select('body').select('#MathJax_SVG_glyphs');
42343
42344 if(tmpDiv.select('.MathJax_SVG').empty() || !tmpDiv.select('svg').node()) {
42345 Lib.log('There was an error in the tex syntax.', _texString);
42346 _callback();
42347 } else {
42348 var svgBBox = tmpDiv.select('svg').node().getBoundingClientRect();
42349 _callback(tmpDiv.select('.MathJax_SVG'), glyphDefs, svgBBox);
42350 }
42351
42352 tmpDiv.remove();
42353
42354 if(originalRenderer !== 'SVG') {
42355 return MathJax.Hub.setRenderer(originalRenderer);
42356 }
42357 },
42358 function() {
42359 if(originalProcessSectionDelay !== undefined) {
42360 MathJax.Hub.processSectionDelay = originalProcessSectionDelay;
42361 }
42362 return MathJax.Hub.Config(originalConfig);
42363 });
42364}
42365
42366var TAG_STYLES = {
42367 // would like to use baseline-shift for sub/sup but FF doesn't support it
42368 // so we need to use dy along with the uber hacky shift-back-to
42369 // baseline below
42370 sup: 'font-size:70%',
42371 sub: 'font-size:70%',
42372 b: 'font-weight:bold',
42373 i: 'font-style:italic',
42374 a: 'cursor:pointer',
42375 span: '',
42376 em: 'font-style:italic;font-weight:bold'
42377};
42378
42379// baseline shifts for sub and sup
42380var SHIFT_DY = {
42381 sub: '0.3em',
42382 sup: '-0.6em'
42383};
42384// reset baseline by adding a tspan (empty except for a zero-width space)
42385// with dy of -70% * SHIFT_DY (because font-size=70%)
42386var RESET_DY = {
42387 sub: '-0.21em',
42388 sup: '0.42em'
42389};
42390var ZERO_WIDTH_SPACE = '\u200b';
42391
42392/*
42393 * Whitelist of protocols in user-supplied urls. Mostly we want to avoid javascript
42394 * and related attack vectors. The empty items are there for IE, that in various
42395 * versions treats relative paths as having different flavors of no protocol, while
42396 * other browsers have these explicitly inherit the protocol of the page they're in.
42397 */
42398var PROTOCOLS = ['http:', 'https:', 'mailto:', '', undefined, ':'];
42399
42400var NEWLINES = exports.NEWLINES = /(\r\n?|\n)/g;
42401
42402var SPLIT_TAGS = /(<[^<>]*>)/;
42403
42404var ONE_TAG = /<(\/?)([^ >]*)(\s+(.*))?>/i;
42405
42406var BR_TAG = /<br(\s+.*)?>/i;
42407exports.BR_TAG_ALL = /<br(\s+.*)?>/gi;
42408
42409/*
42410 * style and href: pull them out of either single or double quotes. Also
42411 * - target: (_blank|_self|_parent|_top|framename)
42412 * note that you can't use target to get a popup but if you use popup,
42413 * a `framename` will be passed along as the name of the popup window.
42414 * per the spec, cannot contain whitespace.
42415 * for backward compatibility we default to '_blank'
42416 * - popup: a custom one for us to enable popup (new window) links. String
42417 * for window.open -> strWindowFeatures, like 'menubar=yes,width=500,height=550'
42418 * note that at least in Chrome, you need to give at least one property
42419 * in this string or the page will open in a new tab anyway. We follow this
42420 * convention and will not make a popup if this string is empty.
42421 * per the spec, cannot contain whitespace.
42422 *
42423 * Because we hack in other attributes with style (sub & sup), drop any trailing
42424 * semicolon in user-supplied styles so we can consistently append the tag-dependent style
42425 *
42426 * These are for tag attributes; Chrome anyway will convert entities in
42427 * attribute values, but not in attribute names
42428 * you can test this by for example:
42429 * > p = document.createElement('p')
42430 * > p.innerHTML = '<span styl&#x65;="font-color:r&#x65;d;">Hi</span>'
42431 * > p.innerHTML
42432 * <- '<span styl&#x65;="font-color:red;">Hi</span>'
42433 */
42434var STYLEMATCH = /(^|[\s"'])style\s*=\s*("([^"]*);?"|'([^']*);?')/i;
42435var HREFMATCH = /(^|[\s"'])href\s*=\s*("([^"]*)"|'([^']*)')/i;
42436var TARGETMATCH = /(^|[\s"'])target\s*=\s*("([^"\s]*)"|'([^'\s]*)')/i;
42437var POPUPMATCH = /(^|[\s"'])popup\s*=\s*("([\w=,]*)"|'([\w=,]*)')/i;
42438
42439// dedicated matcher for these quoted regexes, that can return their results
42440// in two different places
42441function getQuotedMatch(_str, re) {
42442 if(!_str) return null;
42443 var match = _str.match(re);
42444 var result = match && (match[3] || match[4]);
42445 return result && convertEntities(result);
42446}
42447
42448var COLORMATCH = /(^|;)\s*color:/;
42449
42450/**
42451 * Strip string of tags
42452 *
42453 * @param {string} _str : input string
42454 * @param {object} opts :
42455 * - len {number} max length of output string
42456 * - allowedTags {array} list of pseudo-html tags to NOT strip
42457 * @return {string}
42458 */
42459exports.plainText = function(_str, opts) {
42460 opts = opts || {};
42461
42462 var len = (opts.len !== undefined && opts.len !== -1) ? opts.len : Infinity;
42463 var allowedTags = opts.allowedTags !== undefined ? opts.allowedTags : ['br'];
42464
42465 var ellipsis = '...';
42466 var eLen = ellipsis.length;
42467
42468 var oldParts = _str.split(SPLIT_TAGS);
42469 var newParts = [];
42470 var prevTag = '';
42471 var l = 0;
42472
42473 for(var i = 0; i < oldParts.length; i++) {
42474 var p = oldParts[i];
42475 var match = p.match(ONE_TAG);
42476 var tagType = match && match[2].toLowerCase();
42477
42478 if(tagType) {
42479 // N.B. tags do not count towards string length
42480 if(allowedTags.indexOf(tagType) !== -1) {
42481 newParts.push(p);
42482 prevTag = tagType;
42483 }
42484 } else {
42485 var pLen = p.length;
42486
42487 if((l + pLen) < len) {
42488 newParts.push(p);
42489 l += pLen;
42490 } else if(l < len) {
42491 var pLen2 = len - l;
42492
42493 if(prevTag && (prevTag !== 'br' || pLen2 <= eLen || pLen <= eLen)) {
42494 newParts.pop();
42495 }
42496
42497 if(len > eLen) {
42498 newParts.push(p.substr(0, pLen2 - eLen) + ellipsis);
42499 } else {
42500 newParts.push(p.substr(0, pLen2));
42501 }
42502 break;
42503 }
42504
42505 prevTag = '';
42506 }
42507 }
42508
42509 return newParts.join('');
42510};
42511
42512/*
42513 * N.B. HTML entities are listed without the leading '&' and trailing ';'
42514 * https://www.freeformatter.com/html-entities.html
42515 *
42516 * FWIW if we wanted to support the full set, it has 2261 entries:
42517 * https://www.w3.org/TR/html5/entities.json
42518 * though I notice that some of these are duplicates and/or are missing ";"
42519 * eg: "&amp;", "&amp", "&AMP;", and "&AMP" all map to "&"
42520 * We no longer need to include numeric entities here, these are now handled
42521 * by String.fromCodePoint/fromCharCode
42522 *
42523 * Anyway the only ones that are really important to allow are the HTML special
42524 * chars <, >, and &, because these ones can trigger special processing if not
42525 * replaced by the corresponding entity.
42526 */
42527var entityToUnicode = {
42528 mu: 'μ',
42529 amp: '&',
42530 lt: '<',
42531 gt: '>',
42532 nbsp: ' ',
42533 times: '×',
42534 plusmn: '±',
42535 deg: '°'
42536};
42537
42538// NOTE: in general entities can contain uppercase too (so [a-zA-Z]) but all the
42539// ones we support use only lowercase. If we ever change that, update the regex.
42540var ENTITY_MATCH = /&(#\d+|#x[\da-fA-F]+|[a-z]+);/g;
42541function convertEntities(_str) {
42542 return _str.replace(ENTITY_MATCH, function(fullMatch, innerMatch) {
42543 var outChar;
42544 if(innerMatch.charAt(0) === '#') {
42545 // cannot use String.fromCodePoint in IE
42546 outChar = fromCodePoint(
42547 innerMatch.charAt(1) === 'x' ?
42548 parseInt(innerMatch.substr(2), 16) :
42549 parseInt(innerMatch.substr(1), 10)
42550 );
42551 } else outChar = entityToUnicode[innerMatch];
42552
42553 // as in regular HTML, if we didn't decode the entity just
42554 // leave the raw text in place.
42555 return outChar || fullMatch;
42556 });
42557}
42558exports.convertEntities = convertEntities;
42559
42560function fromCodePoint(code) {
42561 // Don't allow overflow. In Chrome this turns into � but I feel like it's
42562 // more useful to just not convert it at all.
42563 if(code > 0x10FFFF) return;
42564 var stringFromCodePoint = String.fromCodePoint;
42565 if(stringFromCodePoint) return stringFromCodePoint(code);
42566
42567 // IE doesn't have String.fromCodePoint
42568 // see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/fromCodePoint
42569 var stringFromCharCode = String.fromCharCode;
42570 if(code <= 0xFFFF) return stringFromCharCode(code);
42571 return stringFromCharCode(
42572 (code >> 10) + 0xD7C0,
42573 (code % 0x400) + 0xDC00
42574 );
42575}
42576
42577/*
42578 * buildSVGText: convert our pseudo-html into SVG tspan elements, and attach these
42579 * to containerNode
42580 *
42581 * @param {svg text element} containerNode: the <text> node to insert this text into
42582 * @param {string} str: the pseudo-html string to convert to svg
42583 *
42584 * @returns {bool}: does the result contain any links? We need to handle the text element
42585 * somewhat differently if it does, so just keep track of this when it happens.
42586 */
42587function buildSVGText(containerNode, str) {
42588 /*
42589 * Normalize behavior between IE and others wrt newlines and whitespace:pre
42590 * this combination makes IE barf https://github.com/plotly/plotly.js/issues/746
42591 * Chrome and FF display \n, \r, or \r\n as a space in this mode.
42592 * I feel like at some point we turned these into <br> but currently we don't so
42593 * I'm just going to cement what we do now in Chrome and FF
42594 */
42595 str = str.replace(NEWLINES, ' ');
42596
42597 var hasLink = false;
42598
42599 // as we're building the text, keep track of what elements we're nested inside
42600 // nodeStack will be an array of {node, type, style, href, target, popup}
42601 // where only type: 'a' gets the last 3 and node is only added when it's created
42602 var nodeStack = [];
42603 var currentNode;
42604 var currentLine = -1;
42605
42606 function newLine() {
42607 currentLine++;
42608
42609 var lineNode = document.createElementNS(xmlnsNamespaces.svg, 'tspan');
42610 d3.select(lineNode).attr({
42611 class: 'line',
42612 dy: (currentLine * LINE_SPACING) + 'em'
42613 });
42614 containerNode.appendChild(lineNode);
42615
42616 currentNode = lineNode;
42617
42618 var oldNodeStack = nodeStack;
42619 nodeStack = [{node: lineNode}];
42620
42621 if(oldNodeStack.length > 1) {
42622 for(var i = 1; i < oldNodeStack.length; i++) {
42623 enterNode(oldNodeStack[i]);
42624 }
42625 }
42626 }
42627
42628 function enterNode(nodeSpec) {
42629 var type = nodeSpec.type;
42630 var nodeAttrs = {};
42631 var nodeType;
42632
42633 if(type === 'a') {
42634 nodeType = 'a';
42635 var target = nodeSpec.target;
42636 var href = nodeSpec.href;
42637 var popup = nodeSpec.popup;
42638 if(href) {
42639 nodeAttrs = {
42640 'xlink:xlink:show': (target === '_blank' || target.charAt(0) !== '_') ? 'new' : 'replace',
42641 target: target,
42642 'xlink:xlink:href': href
42643 };
42644 if(popup) {
42645 // security: href and target are not inserted as code but
42646 // as attributes. popup is, but limited to /[A-Za-z0-9_=,]/
42647 nodeAttrs.onclick = 'window.open(this.href.baseVal,this.target.baseVal,"' +
42648 popup + '");return false;';
42649 }
42650 }
42651 } else nodeType = 'tspan';
42652
42653 if(nodeSpec.style) nodeAttrs.style = nodeSpec.style;
42654
42655 var newNode = document.createElementNS(xmlnsNamespaces.svg, nodeType);
42656
42657 if(type === 'sup' || type === 'sub') {
42658 addTextNode(currentNode, ZERO_WIDTH_SPACE);
42659 currentNode.appendChild(newNode);
42660
42661 var resetter = document.createElementNS(xmlnsNamespaces.svg, 'tspan');
42662 addTextNode(resetter, ZERO_WIDTH_SPACE);
42663 d3.select(resetter).attr('dy', RESET_DY[type]);
42664 nodeAttrs.dy = SHIFT_DY[type];
42665
42666 currentNode.appendChild(newNode);
42667 currentNode.appendChild(resetter);
42668 } else {
42669 currentNode.appendChild(newNode);
42670 }
42671
42672 d3.select(newNode).attr(nodeAttrs);
42673
42674 currentNode = nodeSpec.node = newNode;
42675 nodeStack.push(nodeSpec);
42676 }
42677
42678 function addTextNode(node, text) {
42679 node.appendChild(document.createTextNode(text));
42680 }
42681
42682 function exitNode(type) {
42683 // A bare closing tag can't close the root node. If we encounter this it
42684 // means there's an extra closing tag that can just be ignored:
42685 if(nodeStack.length === 1) {
42686 Lib.log('Ignoring unexpected end tag </' + type + '>.', str);
42687 return;
42688 }
42689
42690 var innerNode = nodeStack.pop();
42691
42692 if(type !== innerNode.type) {
42693 Lib.log('Start tag <' + innerNode.type + '> doesnt match end tag <' +
42694 type + '>. Pretending it did match.', str);
42695 }
42696 currentNode = nodeStack[nodeStack.length - 1].node;
42697 }
42698
42699 var hasLines = BR_TAG.test(str);
42700
42701 if(hasLines) newLine();
42702 else {
42703 currentNode = containerNode;
42704 nodeStack = [{node: containerNode}];
42705 }
42706
42707 var parts = str.split(SPLIT_TAGS);
42708 for(var i = 0; i < parts.length; i++) {
42709 var parti = parts[i];
42710 var match = parti.match(ONE_TAG);
42711 var tagType = match && match[2].toLowerCase();
42712 var tagStyle = TAG_STYLES[tagType];
42713
42714 if(tagType === 'br') {
42715 newLine();
42716 } else if(tagStyle === undefined) {
42717 addTextNode(currentNode, convertEntities(parti));
42718 } else {
42719 // tag - open or close
42720 if(match[1]) {
42721 exitNode(tagType);
42722 } else {
42723 var extra = match[4];
42724
42725 var nodeSpec = {type: tagType};
42726
42727 // now add style, from both the tag name and any extra css
42728 // Most of the svg css that users will care about is just like html,
42729 // but font color is different (uses fill). Let our users ignore this.
42730 var css = getQuotedMatch(extra, STYLEMATCH);
42731 if(css) {
42732 css = css.replace(COLORMATCH, '$1 fill:');
42733 if(tagStyle) css += ';' + tagStyle;
42734 } else if(tagStyle) css = tagStyle;
42735
42736 if(css) nodeSpec.style = css;
42737
42738 if(tagType === 'a') {
42739 hasLink = true;
42740
42741 var href = getQuotedMatch(extra, HREFMATCH);
42742
42743 if(href) {
42744 // check safe protocols
42745 var dummyAnchor = document.createElement('a');
42746 dummyAnchor.href = href;
42747 if(PROTOCOLS.indexOf(dummyAnchor.protocol) !== -1) {
42748 // Decode href to allow both already encoded and not encoded
42749 // URIs. Without decoding prior encoding, an already encoded
42750 // URI would be encoded twice producing a semantically different URI.
42751 nodeSpec.href = encodeURI(decodeURI(href));
42752 nodeSpec.target = getQuotedMatch(extra, TARGETMATCH) || '_blank';
42753 nodeSpec.popup = getQuotedMatch(extra, POPUPMATCH);
42754 }
42755 }
42756 }
42757
42758 enterNode(nodeSpec);
42759 }
42760 }
42761 }
42762
42763 return hasLink;
42764}
42765
42766/*
42767 * sanitizeHTML: port of buildSVGText aimed at providing a clean subset of HTML
42768 * @param {string} str: the html string to clean
42769 * @returns {string}: a cleaned and normalized version of the input,
42770 * supporting only a small subset of html
42771 */
42772exports.sanitizeHTML = function sanitizeHTML(str) {
42773 str = str.replace(NEWLINES, ' ');
42774
42775 var rootNode = document.createElement('p');
42776 var currentNode = rootNode;
42777 var nodeStack = [];
42778
42779 var parts = str.split(SPLIT_TAGS);
42780 for(var i = 0; i < parts.length; i++) {
42781 var parti = parts[i];
42782 var match = parti.match(ONE_TAG);
42783 var tagType = match && match[2].toLowerCase();
42784
42785 if(tagType in TAG_STYLES) {
42786 if(match[1]) {
42787 if(nodeStack.length) {
42788 currentNode = nodeStack.pop();
42789 }
42790 } else {
42791 var extra = match[4];
42792
42793 var css = getQuotedMatch(extra, STYLEMATCH);
42794 var nodeAttrs = css ? {style: css} : {};
42795
42796 if(tagType === 'a') {
42797 var href = getQuotedMatch(extra, HREFMATCH);
42798
42799 if(href) {
42800 var dummyAnchor = document.createElement('a');
42801 dummyAnchor.href = href;
42802 if(PROTOCOLS.indexOf(dummyAnchor.protocol) !== -1) {
42803 nodeAttrs.href = encodeURI(decodeURI(href));
42804 var target = getQuotedMatch(extra, TARGETMATCH);
42805 if(target) {
42806 nodeAttrs.target = target;
42807 }
42808 }
42809 }
42810 }
42811
42812 var newNode = document.createElement(tagType);
42813 currentNode.appendChild(newNode);
42814 d3.select(newNode).attr(nodeAttrs);
42815
42816 currentNode = newNode;
42817 nodeStack.push(newNode);
42818 }
42819 } else {
42820 currentNode.appendChild(
42821 document.createTextNode(convertEntities(parti))
42822 );
42823 }
42824 }
42825 var key = 'innerHTML'; // i.e. to avoid pass test-syntax
42826 return rootNode[key];
42827};
42828
42829exports.lineCount = function lineCount(s) {
42830 return s.selectAll('tspan.line').size() || 1;
42831};
42832
42833exports.positionText = function positionText(s, x, y) {
42834 return s.each(function() {
42835 var text = d3.select(this);
42836
42837 function setOrGet(attr, val) {
42838 if(val === undefined) {
42839 val = text.attr(attr);
42840 if(val === null) {
42841 text.attr(attr, 0);
42842 val = 0;
42843 }
42844 } else text.attr(attr, val);
42845 return val;
42846 }
42847
42848 var thisX = setOrGet('x', x);
42849 var thisY = setOrGet('y', y);
42850
42851 if(this.nodeName === 'text') {
42852 text.selectAll('tspan.line').attr({x: thisX, y: thisY});
42853 }
42854 });
42855};
42856
42857function alignHTMLWith(_base, container, options) {
42858 var alignH = options.horizontalAlign;
42859 var alignV = options.verticalAlign || 'top';
42860 var bRect = _base.node().getBoundingClientRect();
42861 var cRect = container.node().getBoundingClientRect();
42862 var thisRect;
42863 var getTop;
42864 var getLeft;
42865
42866 if(alignV === 'bottom') {
42867 getTop = function() { return bRect.bottom - thisRect.height; };
42868 } else if(alignV === 'middle') {
42869 getTop = function() { return bRect.top + (bRect.height - thisRect.height) / 2; };
42870 } else { // default: top
42871 getTop = function() { return bRect.top; };
42872 }
42873
42874 if(alignH === 'right') {
42875 getLeft = function() { return bRect.right - thisRect.width; };
42876 } else if(alignH === 'center') {
42877 getLeft = function() { return bRect.left + (bRect.width - thisRect.width) / 2; };
42878 } else { // default: left
42879 getLeft = function() { return bRect.left; };
42880 }
42881
42882 return function() {
42883 thisRect = this.node().getBoundingClientRect();
42884 this.style({
42885 top: (getTop() - cRect.top) + 'px',
42886 left: (getLeft() - cRect.left) + 'px',
42887 'z-index': 1000
42888 });
42889 return this;
42890 };
42891}
42892
42893/*
42894 * Editable title
42895 * @param {d3.selection} context: the element being edited. Normally text,
42896 * but if it isn't, you should provide the styling options
42897 * @param {object} options:
42898 * @param {div} options.gd: graphDiv
42899 * @param {d3.selection} options.delegate: item to bind events to if not this
42900 * @param {boolean} options.immediate: start editing now (true) or on click (false, default)
42901 * @param {string} options.fill: font color if not as shown
42902 * @param {string} options.background: background color if not as shown
42903 * @param {string} options.text: initial text, if not as shown
42904 * @param {string} options.horizontalAlign: alignment of the edit box wrt. the bound element
42905 * @param {string} options.verticalAlign: alignment of the edit box wrt. the bound element
42906 */
42907
42908exports.makeEditable = function(context, options) {
42909 var gd = options.gd;
42910 var _delegate = options.delegate;
42911 var dispatch = d3.dispatch('edit', 'input', 'cancel');
42912 var handlerElement = _delegate || context;
42913
42914 context.style({'pointer-events': _delegate ? 'none' : 'all'});
42915
42916 if(context.size() !== 1) throw new Error('boo');
42917
42918 function handleClick() {
42919 appendEditable();
42920 context.style({opacity: 0});
42921 // also hide any mathjax svg
42922 var svgClass = handlerElement.attr('class');
42923 var mathjaxClass;
42924 if(svgClass) mathjaxClass = '.' + svgClass.split(' ')[0] + '-math-group';
42925 else mathjaxClass = '[class*=-math-group]';
42926 if(mathjaxClass) {
42927 d3.select(context.node().parentNode).select(mathjaxClass).style({opacity: 0});
42928 }
42929 }
42930
42931 function selectElementContents(_el) {
42932 var el = _el.node();
42933 var range = document.createRange();
42934 range.selectNodeContents(el);
42935 var sel = window.getSelection();
42936 sel.removeAllRanges();
42937 sel.addRange(range);
42938 el.focus();
42939 }
42940
42941 function appendEditable() {
42942 var plotDiv = d3.select(gd);
42943 var container = plotDiv.select('.svg-container');
42944 var div = container.append('div');
42945 var cStyle = context.node().style;
42946 var fontSize = parseFloat(cStyle.fontSize || 12);
42947
42948 var initialText = options.text;
42949 if(initialText === undefined) initialText = context.attr('data-unformatted');
42950
42951 div.classed('plugin-editable editable', true)
42952 .style({
42953 position: 'absolute',
42954 'font-family': cStyle.fontFamily || 'Arial',
42955 'font-size': fontSize,
42956 color: options.fill || cStyle.fill || 'black',
42957 opacity: 1,
42958 'background-color': options.background || 'transparent',
42959 outline: '#ffffff33 1px solid',
42960 margin: [-fontSize / 8 + 1, 0, 0, -1].join('px ') + 'px',
42961 padding: '0',
42962 'box-sizing': 'border-box'
42963 })
42964 .attr({contenteditable: true})
42965 .text(initialText)
42966 .call(alignHTMLWith(context, container, options))
42967 .on('blur', function() {
42968 gd._editing = false;
42969 context.text(this.textContent)
42970 .style({opacity: 1});
42971 var svgClass = d3.select(this).attr('class');
42972 var mathjaxClass;
42973 if(svgClass) mathjaxClass = '.' + svgClass.split(' ')[0] + '-math-group';
42974 else mathjaxClass = '[class*=-math-group]';
42975 if(mathjaxClass) {
42976 d3.select(context.node().parentNode).select(mathjaxClass).style({opacity: 0});
42977 }
42978 var text = this.textContent;
42979 d3.select(this).transition().duration(0).remove();
42980 d3.select(document).on('mouseup', null);
42981 dispatch.edit.call(context, text);
42982 })
42983 .on('focus', function() {
42984 var editDiv = this;
42985 gd._editing = true;
42986 d3.select(document).on('mouseup', function() {
42987 if(d3.event.target === editDiv) return false;
42988 if(document.activeElement === div.node()) div.node().blur();
42989 });
42990 })
42991 .on('keyup', function() {
42992 if(d3.event.which === 27) {
42993 gd._editing = false;
42994 context.style({opacity: 1});
42995 d3.select(this)
42996 .style({opacity: 0})
42997 .on('blur', function() { return false; })
42998 .transition().remove();
42999 dispatch.cancel.call(context, this.textContent);
43000 } else {
43001 dispatch.input.call(context, this.textContent);
43002 d3.select(this).call(alignHTMLWith(context, container, options));
43003 }
43004 })
43005 .on('keydown', function() {
43006 if(d3.event.which === 13) this.blur();
43007 })
43008 .call(selectElementContents);
43009 }
43010
43011 if(options.immediate) handleClick();
43012 else handlerElement.on('click', handleClick);
43013
43014 return d3.rebind(context, dispatch, 'on');
43015};
43016
43017},{"../constants/alignment":154,"../constants/xmlns_namespaces":159,"../lib":178,"d3":16}],200:[function(_dereq_,module,exports){
43018/**
43019* Copyright 2012-2020, Plotly, Inc.
43020* All rights reserved.
43021*
43022* This source code is licensed under the MIT license found in the
43023* LICENSE file in the root directory of this source tree.
43024*/
43025
43026'use strict';
43027
43028var timerCache = {};
43029
43030/**
43031 * Throttle a callback. `callback` executes synchronously only if
43032 * more than `minInterval` milliseconds have already elapsed since the latest
43033 * call (if any). Otherwise we wait until `minInterval` is over and execute the
43034 * last callback received while waiting.
43035 * So the first and last events in a train are always executed (eventually)
43036 * but some of the events in the middle can be dropped.
43037 *
43038 * @param {string} id: an identifier to mark events to throttle together
43039 * @param {number} minInterval: minimum time, in milliseconds, between
43040 * invocations of `callback`
43041 * @param {function} callback: the function to throttle. `callback` itself
43042 * should be a purely synchronous function.
43043 */
43044exports.throttle = function throttle(id, minInterval, callback) {
43045 var cache = timerCache[id];
43046 var now = Date.now();
43047
43048 if(!cache) {
43049 /*
43050 * Throw out old items before making a new one, to prevent the cache
43051 * getting overgrown, for example from old plots that have been replaced.
43052 * 1 minute age is arbitrary.
43053 */
43054 for(var idi in timerCache) {
43055 if(timerCache[idi].ts < now - 60000) {
43056 delete timerCache[idi];
43057 }
43058 }
43059 cache = timerCache[id] = {ts: 0, timer: null};
43060 }
43061
43062 _clearTimeout(cache);
43063
43064 function exec() {
43065 callback();
43066 cache.ts = Date.now();
43067 if(cache.onDone) {
43068 cache.onDone();
43069 cache.onDone = null;
43070 }
43071 }
43072
43073 if(now > cache.ts + minInterval) {
43074 exec();
43075 return;
43076 }
43077
43078 cache.timer = setTimeout(function() {
43079 exec();
43080 cache.timer = null;
43081 }, minInterval);
43082};
43083
43084exports.done = function(id) {
43085 var cache = timerCache[id];
43086 if(!cache || !cache.timer) return Promise.resolve();
43087
43088 return new Promise(function(resolve) {
43089 var previousOnDone = cache.onDone;
43090 cache.onDone = function onDone() {
43091 if(previousOnDone) previousOnDone();
43092 resolve();
43093 cache.onDone = null;
43094 };
43095 });
43096};
43097
43098/**
43099 * Clear the throttle cache for one or all timers
43100 * @param {optional string} id:
43101 * if provided, clear just this timer
43102 * if omitted, clear all timers (mainly useful for testing)
43103 */
43104exports.clear = function(id) {
43105 if(id) {
43106 _clearTimeout(timerCache[id]);
43107 delete timerCache[id];
43108 } else {
43109 for(var idi in timerCache) exports.clear(idi);
43110 }
43111};
43112
43113function _clearTimeout(cache) {
43114 if(cache && cache.timer !== null) {
43115 clearTimeout(cache.timer);
43116 cache.timer = null;
43117 }
43118}
43119
43120},{}],201:[function(_dereq_,module,exports){
43121/**
43122* Copyright 2012-2020, Plotly, Inc.
43123* All rights reserved.
43124*
43125* This source code is licensed under the MIT license found in the
43126* LICENSE file in the root directory of this source tree.
43127*/
43128
43129'use strict';
43130
43131var isNumeric = _dereq_('fast-isnumeric');
43132
43133/**
43134 * convert a linear value into a logged value, folding negative numbers into
43135 * the given range
43136 */
43137module.exports = function toLogRange(val, range) {
43138 if(val > 0) return Math.log(val) / Math.LN10;
43139
43140 // move a negative value reference to a log axis - just put the
43141 // result at the lowest range value on the plot (or if the range also went negative,
43142 // one millionth of the top of the range)
43143 var newVal = Math.log(Math.min(range[0], range[1])) / Math.LN10;
43144 if(!isNumeric(newVal)) newVal = Math.log(Math.max(range[0], range[1])) / Math.LN10 - 6;
43145 return newVal;
43146};
43147
43148},{"fast-isnumeric":18}],202:[function(_dereq_,module,exports){
43149/**
43150* Copyright 2012-2020, Plotly, Inc.
43151* All rights reserved.
43152*
43153* This source code is licensed under the MIT license found in the
43154* LICENSE file in the root directory of this source tree.
43155*/
43156
43157'use strict';
43158
43159module.exports = {
43160 moduleType: 'locale',
43161 name: 'en-US',
43162 dictionary: {
43163 'Click to enter Colorscale title': 'Click to enter Colorscale title'
43164 },
43165 format: {
43166 date: '%m/%d/%Y'
43167 }
43168};
43169
43170},{}],203:[function(_dereq_,module,exports){
43171/**
43172* Copyright 2012-2020, Plotly, Inc.
43173* All rights reserved.
43174*
43175* This source code is licensed under the MIT license found in the
43176* LICENSE file in the root directory of this source tree.
43177*/
43178
43179'use strict';
43180
43181module.exports = {
43182 moduleType: 'locale',
43183 name: 'en',
43184 dictionary: {
43185 'Click to enter Colorscale title': 'Click to enter Colourscale title'
43186 },
43187 format: {
43188 days: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
43189 shortDays: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
43190 months: [
43191 'January', 'February', 'March', 'April', 'May', 'June',
43192 'July', 'August', 'September', 'October', 'November', 'December'
43193 ],
43194 shortMonths: [
43195 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
43196 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'
43197 ],
43198 periods: ['AM', 'PM'],
43199 dateTime: '%a %b %e %X %Y',
43200 date: '%d/%m/%Y',
43201 time: '%H:%M:%S',
43202 decimal: '.',
43203 thousands: ',',
43204 grouping: [3],
43205 currency: ['$', ''],
43206 year: '%Y',
43207 month: '%b %Y',
43208 dayMonth: '%b %-d',
43209 dayMonthYear: '%b %-d, %Y'
43210 }
43211};
43212
43213},{}],204:[function(_dereq_,module,exports){
43214/**
43215* Copyright 2012-2020, Plotly, Inc.
43216* All rights reserved.
43217*
43218* This source code is licensed under the MIT license found in the
43219* LICENSE file in the root directory of this source tree.
43220*/
43221
43222
43223'use strict';
43224
43225var Registry = _dereq_('../registry');
43226
43227/*
43228 * containerArrayMatch: does this attribute string point into a
43229 * layout container array?
43230 *
43231 * @param {String} astr: an attribute string, like *annotations[2].text*
43232 *
43233 * @returns {Object | false} Returns false if `astr` doesn't match a container
43234 * array. If it does, returns:
43235 * {array: {String}, index: {Number}, property: {String}}
43236 * ie the attribute string for the array, the index within the array (or ''
43237 * if the whole array) and the property within that (or '' if the whole array
43238 * or the whole object)
43239 */
43240module.exports = function containerArrayMatch(astr) {
43241 var rootContainers = Registry.layoutArrayContainers;
43242 var regexpContainers = Registry.layoutArrayRegexes;
43243 var rootPart = astr.split('[')[0];
43244 var arrayStr;
43245 var match;
43246
43247 // look for regexp matches first, because they may be nested inside root matches
43248 // eg updatemenus[i].buttons is nested inside updatemenus
43249 for(var i = 0; i < regexpContainers.length; i++) {
43250 match = astr.match(regexpContainers[i]);
43251 if(match && match.index === 0) {
43252 arrayStr = match[0];
43253 break;
43254 }
43255 }
43256
43257 // now look for root matches
43258 if(!arrayStr) arrayStr = rootContainers[rootContainers.indexOf(rootPart)];
43259
43260 if(!arrayStr) return false;
43261
43262 var tail = astr.substr(arrayStr.length);
43263 if(!tail) return {array: arrayStr, index: '', property: ''};
43264
43265 match = tail.match(/^\[(0|[1-9][0-9]*)\](\.(.+))?$/);
43266 if(!match) return false;
43267
43268 return {array: arrayStr, index: Number(match[1]), property: match[3] || ''};
43269};
43270
43271},{"../registry":269}],205:[function(_dereq_,module,exports){
43272/**
43273* Copyright 2012-2020, Plotly, Inc.
43274* All rights reserved.
43275*
43276* This source code is licensed under the MIT license found in the
43277* LICENSE file in the root directory of this source tree.
43278*/
43279
43280'use strict';
43281
43282var Lib = _dereq_('../lib');
43283var extendFlat = Lib.extendFlat;
43284var isPlainObject = Lib.isPlainObject;
43285
43286var traceOpts = {
43287 valType: 'flaglist',
43288 extras: ['none'],
43289 flags: ['calc', 'clearAxisTypes', 'plot', 'style', 'markerSize', 'colorbars'],
43290
43291};
43292
43293var layoutOpts = {
43294 valType: 'flaglist',
43295 extras: ['none'],
43296 flags: [
43297 'calc', 'plot', 'legend', 'ticks', 'axrange',
43298 'layoutstyle', 'modebar', 'camera', 'arraydraw', 'colorbars'
43299 ],
43300
43301};
43302
43303// flags for inside restyle/relayout include a few extras
43304// that shouldn't be used in attributes, to deal with certain
43305// combinations and conditionals efficiently
43306var traceEditTypeFlags = traceOpts.flags.slice()
43307 .concat(['fullReplot']);
43308
43309var layoutEditTypeFlags = layoutOpts.flags.slice()
43310 .concat('layoutReplot');
43311
43312module.exports = {
43313 traces: traceOpts,
43314 layout: layoutOpts,
43315 /*
43316 * default (all false) edit flags for restyle (traces)
43317 * creates a new object each call, so the caller can mutate freely
43318 */
43319 traceFlags: function() { return falseObj(traceEditTypeFlags); },
43320
43321 /*
43322 * default (all false) edit flags for relayout
43323 * creates a new object each call, so the caller can mutate freely
43324 */
43325 layoutFlags: function() { return falseObj(layoutEditTypeFlags); },
43326
43327 /*
43328 * update `flags` with the `editType` values found in `attr`
43329 */
43330 update: function(flags, attr) {
43331 var editType = attr.editType;
43332 if(editType && editType !== 'none') {
43333 var editTypeParts = editType.split('+');
43334 for(var i = 0; i < editTypeParts.length; i++) {
43335 flags[editTypeParts[i]] = true;
43336 }
43337 }
43338 },
43339
43340 overrideAll: overrideAll
43341};
43342
43343function falseObj(keys) {
43344 var out = {};
43345 for(var i = 0; i < keys.length; i++) out[keys[i]] = false;
43346 return out;
43347}
43348
43349/**
43350 * For attributes that are largely copied from elsewhere into a plot type that doesn't
43351 * support partial redraws - overrides the editType field of all attributes in the object
43352 *
43353 * @param {object} attrs: the attributes to override. Will not be mutated.
43354 * @param {string} editTypeOverride: the new editType to use
43355 * @param {'nested'|'from-root'} overrideContainers:
43356 * - 'nested' will override editType for nested containers but not the root.
43357 * - 'from-root' will also override editType of the root container.
43358 * Containers below the absolute top level (trace or layout root) DO need an
43359 * editType even if they are not `valObject`s themselves (eg `scatter.marker`)
43360 * to handle the case where you edit the whole container.
43361 *
43362 * @return {object} a new attributes object with `editType` modified as directed
43363 */
43364function overrideAll(attrs, editTypeOverride, overrideContainers) {
43365 var out = extendFlat({}, attrs);
43366 for(var key in out) {
43367 var attr = out[key];
43368 if(isPlainObject(attr)) {
43369 out[key] = overrideOne(attr, editTypeOverride, overrideContainers, key);
43370 }
43371 }
43372 if(overrideContainers === 'from-root') out.editType = editTypeOverride;
43373
43374 return out;
43375}
43376
43377function overrideOne(attr, editTypeOverride, overrideContainers, key) {
43378 if(attr.valType) {
43379 var out = extendFlat({}, attr);
43380 out.editType = editTypeOverride;
43381
43382 if(Array.isArray(attr.items)) {
43383 out.items = new Array(attr.items.length);
43384 for(var i = 0; i < attr.items.length; i++) {
43385 out.items[i] = overrideOne(attr.items[i], editTypeOverride, 'from-root');
43386 }
43387 }
43388 return out;
43389 } else {
43390 // don't provide an editType for the _deprecated container
43391 return overrideAll(attr, editTypeOverride,
43392 (key.charAt(0) === '_') ? 'nested' : 'from-root');
43393 }
43394}
43395
43396},{"../lib":178}],206:[function(_dereq_,module,exports){
43397/**
43398* Copyright 2012-2020, Plotly, Inc.
43399* All rights reserved.
43400*
43401* This source code is licensed under the MIT license found in the
43402* LICENSE file in the root directory of this source tree.
43403*/
43404
43405'use strict';
43406
43407var isNumeric = _dereq_('fast-isnumeric');
43408var m4FromQuat = _dereq_('gl-mat4/fromQuat');
43409
43410var Registry = _dereq_('../registry');
43411var Lib = _dereq_('../lib');
43412var Plots = _dereq_('../plots/plots');
43413var AxisIds = _dereq_('../plots/cartesian/axis_ids');
43414var Color = _dereq_('../components/color');
43415
43416var cleanId = AxisIds.cleanId;
43417var getFromTrace = AxisIds.getFromTrace;
43418var traceIs = Registry.traceIs;
43419
43420// clear the promise queue if one of them got rejected
43421exports.clearPromiseQueue = function(gd) {
43422 if(Array.isArray(gd._promises) && gd._promises.length > 0) {
43423 Lib.log('Clearing previous rejected promises from queue.');
43424 }
43425
43426 gd._promises = [];
43427};
43428
43429// make a few changes to the layout right away
43430// before it gets used for anything
43431// backward compatibility and cleanup of nonstandard options
43432exports.cleanLayout = function(layout) {
43433 var i, j;
43434
43435 if(!layout) layout = {};
43436
43437 // cannot have (x|y)axis1, numbering goes axis, axis2, axis3...
43438 if(layout.xaxis1) {
43439 if(!layout.xaxis) layout.xaxis = layout.xaxis1;
43440 delete layout.xaxis1;
43441 }
43442 if(layout.yaxis1) {
43443 if(!layout.yaxis) layout.yaxis = layout.yaxis1;
43444 delete layout.yaxis1;
43445 }
43446 if(layout.scene1) {
43447 if(!layout.scene) layout.scene = layout.scene1;
43448 delete layout.scene1;
43449 }
43450
43451 var axisAttrRegex = (Plots.subplotsRegistry.cartesian || {}).attrRegex;
43452 var polarAttrRegex = (Plots.subplotsRegistry.polar || {}).attrRegex;
43453 var ternaryAttrRegex = (Plots.subplotsRegistry.ternary || {}).attrRegex;
43454 var sceneAttrRegex = (Plots.subplotsRegistry.gl3d || {}).attrRegex;
43455
43456 var keys = Object.keys(layout);
43457 for(i = 0; i < keys.length; i++) {
43458 var key = keys[i];
43459
43460 if(axisAttrRegex && axisAttrRegex.test(key)) {
43461 // modifications to cartesian axes
43462
43463 var ax = layout[key];
43464 if(ax.anchor && ax.anchor !== 'free') {
43465 ax.anchor = cleanId(ax.anchor);
43466 }
43467 if(ax.overlaying) ax.overlaying = cleanId(ax.overlaying);
43468
43469 // old method of axis type - isdate and islog (before category existed)
43470 if(!ax.type) {
43471 if(ax.isdate) ax.type = 'date';
43472 else if(ax.islog) ax.type = 'log';
43473 else if(ax.isdate === false && ax.islog === false) ax.type = 'linear';
43474 }
43475 if(ax.autorange === 'withzero' || ax.autorange === 'tozero') {
43476 ax.autorange = true;
43477 ax.rangemode = 'tozero';
43478 }
43479 delete ax.islog;
43480 delete ax.isdate;
43481 delete ax.categories; // replaced by _categories
43482
43483 // prune empty domain arrays made before the new nestedProperty
43484 if(emptyContainer(ax, 'domain')) delete ax.domain;
43485
43486 // autotick -> tickmode
43487 if(ax.autotick !== undefined) {
43488 if(ax.tickmode === undefined) {
43489 ax.tickmode = ax.autotick ? 'auto' : 'linear';
43490 }
43491 delete ax.autotick;
43492 }
43493
43494 cleanTitle(ax);
43495 } else if(polarAttrRegex && polarAttrRegex.test(key)) {
43496 // modifications for polar
43497
43498 var polar = layout[key];
43499 cleanTitle(polar.radialaxis);
43500 } else if(ternaryAttrRegex && ternaryAttrRegex.test(key)) {
43501 // modifications for ternary
43502
43503 var ternary = layout[key];
43504 cleanTitle(ternary.aaxis);
43505 cleanTitle(ternary.baxis);
43506 cleanTitle(ternary.caxis);
43507 } else if(sceneAttrRegex && sceneAttrRegex.test(key)) {
43508 // modifications for 3D scenes
43509
43510 var scene = layout[key];
43511
43512 // clean old Camera coords
43513 var cameraposition = scene.cameraposition;
43514
43515 if(Array.isArray(cameraposition) && cameraposition[0].length === 4) {
43516 var rotation = cameraposition[0];
43517 var center = cameraposition[1];
43518 var radius = cameraposition[2];
43519 var mat = m4FromQuat([], rotation);
43520 var eye = [];
43521
43522 for(j = 0; j < 3; ++j) {
43523 eye[j] = center[j] + radius * mat[2 + 4 * j];
43524 }
43525
43526 scene.camera = {
43527 eye: {x: eye[0], y: eye[1], z: eye[2]},
43528 center: {x: center[0], y: center[1], z: center[2]},
43529 up: {x: 0, y: 0, z: 1} // we just ignore calculating camera z up in this case
43530 };
43531
43532 delete scene.cameraposition;
43533 }
43534
43535 // clean axis titles
43536 cleanTitle(scene.xaxis);
43537 cleanTitle(scene.yaxis);
43538 cleanTitle(scene.zaxis);
43539 }
43540 }
43541
43542 var annotationsLen = Array.isArray(layout.annotations) ? layout.annotations.length : 0;
43543 for(i = 0; i < annotationsLen; i++) {
43544 var ann = layout.annotations[i];
43545
43546 if(!Lib.isPlainObject(ann)) continue;
43547
43548 if(ann.ref) {
43549 if(ann.ref === 'paper') {
43550 ann.xref = 'paper';
43551 ann.yref = 'paper';
43552 } else if(ann.ref === 'data') {
43553 ann.xref = 'x';
43554 ann.yref = 'y';
43555 }
43556 delete ann.ref;
43557 }
43558
43559 cleanAxRef(ann, 'xref');
43560 cleanAxRef(ann, 'yref');
43561 }
43562
43563 var shapesLen = Array.isArray(layout.shapes) ? layout.shapes.length : 0;
43564 for(i = 0; i < shapesLen; i++) {
43565 var shape = layout.shapes[i];
43566
43567 if(!Lib.isPlainObject(shape)) continue;
43568
43569 cleanAxRef(shape, 'xref');
43570 cleanAxRef(shape, 'yref');
43571 }
43572
43573 var legend = layout.legend;
43574 if(legend) {
43575 // check for old-style legend positioning (x or y is +/- 100)
43576 if(legend.x > 3) {
43577 legend.x = 1.02;
43578 legend.xanchor = 'left';
43579 } else if(legend.x < -2) {
43580 legend.x = -0.02;
43581 legend.xanchor = 'right';
43582 }
43583
43584 if(legend.y > 3) {
43585 legend.y = 1.02;
43586 legend.yanchor = 'bottom';
43587 } else if(legend.y < -2) {
43588 legend.y = -0.02;
43589 legend.yanchor = 'top';
43590 }
43591 }
43592
43593 // clean plot title
43594 cleanTitle(layout);
43595
43596 /*
43597 * Moved from rotate -> orbit for dragmode
43598 */
43599 if(layout.dragmode === 'rotate') layout.dragmode = 'orbit';
43600
43601 // sanitize rgb(fractions) and rgba(fractions) that old tinycolor
43602 // supported, but new tinycolor does not because they're not valid css
43603 Color.clean(layout);
43604
43605 // clean the layout container in layout.template
43606 if(layout.template && layout.template.layout) {
43607 exports.cleanLayout(layout.template.layout);
43608 }
43609
43610 return layout;
43611};
43612
43613function cleanAxRef(container, attr) {
43614 var valIn = container[attr];
43615 var axLetter = attr.charAt(0);
43616 if(valIn && valIn !== 'paper') {
43617 container[attr] = cleanId(valIn, axLetter);
43618 }
43619}
43620
43621/**
43622 * Cleans up old title attribute structure (flat) in favor of the new one (nested).
43623 *
43624 * @param {Object} titleContainer - an object potentially including deprecated title attributes
43625 */
43626function cleanTitle(titleContainer) {
43627 if(titleContainer) {
43628 // title -> title.text
43629 // (although title used to be a string attribute,
43630 // numbers are accepted as well)
43631 if(typeof titleContainer.title === 'string' || typeof titleContainer.title === 'number') {
43632 titleContainer.title = {
43633 text: titleContainer.title
43634 };
43635 }
43636
43637 rewireAttr('titlefont', 'font');
43638 rewireAttr('titleposition', 'position');
43639 rewireAttr('titleside', 'side');
43640 rewireAttr('titleoffset', 'offset');
43641 }
43642
43643 function rewireAttr(oldAttrName, newAttrName) {
43644 var oldAttrSet = titleContainer[oldAttrName];
43645 var newAttrSet = titleContainer.title && titleContainer.title[newAttrName];
43646
43647 if(oldAttrSet && !newAttrSet) {
43648 // Ensure title object exists
43649 if(!titleContainer.title) {
43650 titleContainer.title = {};
43651 }
43652
43653 titleContainer.title[newAttrName] = titleContainer[oldAttrName];
43654 delete titleContainer[oldAttrName];
43655 }
43656 }
43657}
43658
43659/*
43660 * cleanData: Make a few changes to the data for backward compatibility
43661 * before it gets used for anything. Modifies the data traces users provide.
43662 *
43663 * Important: if you're going to add something here that modifies a data array,
43664 * update it in place so the new array === the old one.
43665 */
43666exports.cleanData = function(data) {
43667 for(var tracei = 0; tracei < data.length; tracei++) {
43668 var trace = data[tracei];
43669 var i;
43670
43671 // use xbins to bin data in x, and ybins to bin data in y
43672 if(trace.type === 'histogramy' && 'xbins' in trace && !('ybins' in trace)) {
43673 trace.ybins = trace.xbins;
43674 delete trace.xbins;
43675 }
43676
43677 // error_y.opacity is obsolete - merge into color
43678 if(trace.error_y && 'opacity' in trace.error_y) {
43679 var dc = Color.defaults;
43680 var yeColor = trace.error_y.color || (traceIs(trace, 'bar') ?
43681 Color.defaultLine :
43682 dc[tracei % dc.length]);
43683 trace.error_y.color = Color.addOpacity(
43684 Color.rgb(yeColor),
43685 Color.opacity(yeColor) * trace.error_y.opacity);
43686 delete trace.error_y.opacity;
43687 }
43688
43689 // convert bardir to orientation, and put the data into
43690 // the axes it's eventually going to be used with
43691 if('bardir' in trace) {
43692 if(trace.bardir === 'h' && (traceIs(trace, 'bar') ||
43693 trace.type.substr(0, 9) === 'histogram')) {
43694 trace.orientation = 'h';
43695 exports.swapXYData(trace);
43696 }
43697 delete trace.bardir;
43698 }
43699
43700 // now we have only one 1D histogram type, and whether
43701 // it uses x or y data depends on trace.orientation
43702 if(trace.type === 'histogramy') exports.swapXYData(trace);
43703 if(trace.type === 'histogramx' || trace.type === 'histogramy') {
43704 trace.type = 'histogram';
43705 }
43706
43707 // scl->scale, reversescl->reversescale
43708 if('scl' in trace && !('colorscale' in trace)) {
43709 trace.colorscale = trace.scl;
43710 delete trace.scl;
43711 }
43712 if('reversescl' in trace && !('reversescale' in trace)) {
43713 trace.reversescale = trace.reversescl;
43714 delete trace.reversescl;
43715 }
43716
43717 // axis ids x1 -> x, y1-> y
43718 if(trace.xaxis) trace.xaxis = cleanId(trace.xaxis, 'x');
43719 if(trace.yaxis) trace.yaxis = cleanId(trace.yaxis, 'y');
43720
43721 // scene ids scene1 -> scene
43722 if(traceIs(trace, 'gl3d') && trace.scene) {
43723 trace.scene = Plots.subplotsRegistry.gl3d.cleanId(trace.scene);
43724 }
43725
43726 if(!traceIs(trace, 'pie-like') && !traceIs(trace, 'bar-like')) {
43727 if(Array.isArray(trace.textposition)) {
43728 for(i = 0; i < trace.textposition.length; i++) {
43729 trace.textposition[i] = cleanTextPosition(trace.textposition[i]);
43730 }
43731 } else if(trace.textposition) {
43732 trace.textposition = cleanTextPosition(trace.textposition);
43733 }
43734 }
43735
43736 // fix typo in colorscale definition
43737 var _module = Registry.getModule(trace);
43738 if(_module && _module.colorbar) {
43739 var containerName = _module.colorbar.container;
43740 var container = containerName ? trace[containerName] : trace;
43741 if(container && container.colorscale) {
43742 if(container.colorscale === 'YIGnBu') container.colorscale = 'YlGnBu';
43743 if(container.colorscale === 'YIOrRd') container.colorscale = 'YlOrRd';
43744 }
43745 }
43746
43747 // fix typo in surface 'highlight*' definitions
43748 if(trace.type === 'surface' && Lib.isPlainObject(trace.contours)) {
43749 var dims = ['x', 'y', 'z'];
43750
43751 for(i = 0; i < dims.length; i++) {
43752 var opts = trace.contours[dims[i]];
43753
43754 if(!Lib.isPlainObject(opts)) continue;
43755
43756 if(opts.highlightColor) {
43757 opts.highlightcolor = opts.highlightColor;
43758 delete opts.highlightColor;
43759 }
43760
43761 if(opts.highlightWidth) {
43762 opts.highlightwidth = opts.highlightWidth;
43763 delete opts.highlightWidth;
43764 }
43765 }
43766 }
43767
43768 // fixes from converting finance from transforms to real trace types
43769 if(trace.type === 'candlestick' || trace.type === 'ohlc') {
43770 var increasingShowlegend = (trace.increasing || {}).showlegend !== false;
43771 var decreasingShowlegend = (trace.decreasing || {}).showlegend !== false;
43772 var increasingName = cleanFinanceDir(trace.increasing);
43773 var decreasingName = cleanFinanceDir(trace.decreasing);
43774
43775 // now figure out something smart to do with the separate direction
43776 // names we removed
43777 if((increasingName !== false) && (decreasingName !== false)) {
43778 // both sub-names existed: base name previously had no effect
43779 // so ignore it and try to find a shared part of the sub-names
43780
43781 var newName = commonPrefix(
43782 increasingName, decreasingName,
43783 increasingShowlegend, decreasingShowlegend
43784 );
43785 // if no common part, leave whatever name was (or wasn't) there
43786 if(newName) trace.name = newName;
43787 } else if((increasingName || decreasingName) && !trace.name) {
43788 // one sub-name existed but not the base name - just use the sub-name
43789 trace.name = increasingName || decreasingName;
43790 }
43791 }
43792
43793 // transforms backward compatibility fixes
43794 if(Array.isArray(trace.transforms)) {
43795 var transforms = trace.transforms;
43796
43797 for(i = 0; i < transforms.length; i++) {
43798 var transform = transforms[i];
43799
43800 if(!Lib.isPlainObject(transform)) continue;
43801
43802 switch(transform.type) {
43803 case 'filter':
43804 if(transform.filtersrc) {
43805 transform.target = transform.filtersrc;
43806 delete transform.filtersrc;
43807 }
43808
43809 if(transform.calendar) {
43810 if(!transform.valuecalendar) {
43811 transform.valuecalendar = transform.calendar;
43812 }
43813 delete transform.calendar;
43814 }
43815 break;
43816
43817 case 'groupby':
43818 // Name has changed from `style` to `styles`, so use `style` but prefer `styles`:
43819 transform.styles = transform.styles || transform.style;
43820
43821 if(transform.styles && !Array.isArray(transform.styles)) {
43822 var prevStyles = transform.styles;
43823 var styleKeys = Object.keys(prevStyles);
43824
43825 transform.styles = [];
43826 for(var j = 0; j < styleKeys.length; j++) {
43827 transform.styles.push({
43828 target: styleKeys[j],
43829 value: prevStyles[styleKeys[j]]
43830 });
43831 }
43832 }
43833 break;
43834 }
43835 }
43836 }
43837
43838 // prune empty containers made before the new nestedProperty
43839 if(emptyContainer(trace, 'line')) delete trace.line;
43840 if('marker' in trace) {
43841 if(emptyContainer(trace.marker, 'line')) delete trace.marker.line;
43842 if(emptyContainer(trace, 'marker')) delete trace.marker;
43843 }
43844
43845 // sanitize rgb(fractions) and rgba(fractions) that old tinycolor
43846 // supported, but new tinycolor does not because they're not valid css
43847 Color.clean(trace);
43848
43849 // remove obsolete autobin(x|y) attributes, but only if true
43850 // if false, this needs to happen in Histogram.calc because it
43851 // can be a one-time autobin so we need to know the results before
43852 // we can push them back into the trace.
43853 if(trace.autobinx) {
43854 delete trace.autobinx;
43855 delete trace.xbins;
43856 }
43857 if(trace.autobiny) {
43858 delete trace.autobiny;
43859 delete trace.ybins;
43860 }
43861
43862 cleanTitle(trace);
43863 if(trace.colorbar) cleanTitle(trace.colorbar);
43864 if(trace.marker && trace.marker.colorbar) cleanTitle(trace.marker.colorbar);
43865 if(trace.line && trace.line.colorbar) cleanTitle(trace.line.colorbar);
43866 if(trace.aaxis) cleanTitle(trace.aaxis);
43867 if(trace.baxis) cleanTitle(trace.baxis);
43868 }
43869};
43870
43871function cleanFinanceDir(dirContainer) {
43872 if(!Lib.isPlainObject(dirContainer)) return false;
43873
43874 var dirName = dirContainer.name;
43875
43876 delete dirContainer.name;
43877 delete dirContainer.showlegend;
43878
43879 return (typeof dirName === 'string' || typeof dirName === 'number') && String(dirName);
43880}
43881
43882function commonPrefix(name1, name2, show1, show2) {
43883 // if only one is shown in the legend, use that
43884 if(show1 && !show2) return name1;
43885 if(show2 && !show1) return name2;
43886
43887 // if both or neither are in the legend, check if one is blank (or whitespace)
43888 // and use the other one
43889 // note that hover labels can still use the name even if the legend doesn't
43890 if(!name1.trim()) return name2;
43891 if(!name2.trim()) return name1;
43892
43893 var minLen = Math.min(name1.length, name2.length);
43894 var i;
43895 for(i = 0; i < minLen; i++) {
43896 if(name1.charAt(i) !== name2.charAt(i)) break;
43897 }
43898
43899 var out = name1.substr(0, i);
43900 return out.trim();
43901}
43902
43903// textposition - support partial attributes (ie just 'top')
43904// and incorrect use of middle / center etc.
43905function cleanTextPosition(textposition) {
43906 var posY = 'middle';
43907 var posX = 'center';
43908
43909 if(typeof textposition === 'string') {
43910 if(textposition.indexOf('top') !== -1) posY = 'top';
43911 else if(textposition.indexOf('bottom') !== -1) posY = 'bottom';
43912
43913 if(textposition.indexOf('left') !== -1) posX = 'left';
43914 else if(textposition.indexOf('right') !== -1) posX = 'right';
43915 }
43916
43917 return posY + ' ' + posX;
43918}
43919
43920function emptyContainer(outer, innerStr) {
43921 return (innerStr in outer) &&
43922 (typeof outer[innerStr] === 'object') &&
43923 (Object.keys(outer[innerStr]).length === 0);
43924}
43925
43926
43927// swap all the data and data attributes associated with x and y
43928exports.swapXYData = function(trace) {
43929 var i;
43930 Lib.swapAttrs(trace, ['?', '?0', 'd?', '?bins', 'nbins?', 'autobin?', '?src', 'error_?']);
43931 if(Array.isArray(trace.z) && Array.isArray(trace.z[0])) {
43932 if(trace.transpose) delete trace.transpose;
43933 else trace.transpose = true;
43934 }
43935 if(trace.error_x && trace.error_y) {
43936 var errorY = trace.error_y;
43937 var copyYstyle = ('copy_ystyle' in errorY) ?
43938 errorY.copy_ystyle :
43939 !(errorY.color || errorY.thickness || errorY.width);
43940 Lib.swapAttrs(trace, ['error_?.copy_ystyle']);
43941 if(copyYstyle) {
43942 Lib.swapAttrs(trace, ['error_?.color', 'error_?.thickness', 'error_?.width']);
43943 }
43944 }
43945 if(typeof trace.hoverinfo === 'string') {
43946 var hoverInfoParts = trace.hoverinfo.split('+');
43947 for(i = 0; i < hoverInfoParts.length; i++) {
43948 if(hoverInfoParts[i] === 'x') hoverInfoParts[i] = 'y';
43949 else if(hoverInfoParts[i] === 'y') hoverInfoParts[i] = 'x';
43950 }
43951 trace.hoverinfo = hoverInfoParts.join('+');
43952 }
43953};
43954
43955// coerce traceIndices input to array of trace indices
43956exports.coerceTraceIndices = function(gd, traceIndices) {
43957 if(isNumeric(traceIndices)) {
43958 return [traceIndices];
43959 } else if(!Array.isArray(traceIndices) || !traceIndices.length) {
43960 return gd.data.map(function(_, i) { return i; });
43961 } else if(Array.isArray(traceIndices)) {
43962 var traceIndicesOut = [];
43963 for(var i = 0; i < traceIndices.length; i++) {
43964 if(Lib.isIndex(traceIndices[i], gd.data.length)) {
43965 traceIndicesOut.push(traceIndices[i]);
43966 } else {
43967 Lib.warn('trace index (', traceIndices[i], ') is not a number or is out of bounds');
43968 }
43969 }
43970 return traceIndicesOut;
43971 }
43972
43973 return traceIndices;
43974};
43975
43976/**
43977 * Manages logic around array container item creation / deletion / update
43978 * that nested property alone can't handle.
43979 *
43980 * @param {Object} np
43981 * nested property of update attribute string about trace or layout object
43982 * @param {*} newVal
43983 * update value passed to restyle / relayout / update
43984 * @param {Object} undoit
43985 * undo hash (N.B. undoit may be mutated here).
43986 *
43987 */
43988exports.manageArrayContainers = function(np, newVal, undoit) {
43989 var obj = np.obj;
43990 var parts = np.parts;
43991 var pLength = parts.length;
43992 var pLast = parts[pLength - 1];
43993
43994 var pLastIsNumber = isNumeric(pLast);
43995
43996 if(pLastIsNumber && newVal === null) {
43997 // delete item
43998
43999 // Clear item in array container when new value is null
44000 var contPath = parts.slice(0, pLength - 1).join('.');
44001 var cont = Lib.nestedProperty(obj, contPath).get();
44002 cont.splice(pLast, 1);
44003
44004 // Note that nested property clears null / undefined at end of
44005 // array container, but not within them.
44006 } else if(pLastIsNumber && np.get() === undefined) {
44007 // create item
44008
44009 // When adding a new item, make sure undo command will remove it
44010 if(np.get() === undefined) undoit[np.astr] = null;
44011
44012 np.set(newVal);
44013 } else {
44014 // update item
44015
44016 // If the last part of attribute string isn't a number,
44017 // np.set is all we need.
44018 np.set(newVal);
44019 }
44020};
44021
44022/*
44023 * Match the part to strip off to turn an attribute into its parent
44024 * really it should be either '.some_characters' or '[number]'
44025 * but we're a little more permissive here and match either
44026 * '.not_brackets_or_dot' or '[not_brackets_or_dot]'
44027 */
44028var ATTR_TAIL_RE = /(\.[^\[\]\.]+|\[[^\[\]\.]+\])$/;
44029
44030function getParent(attr) {
44031 var tail = attr.search(ATTR_TAIL_RE);
44032 if(tail > 0) return attr.substr(0, tail);
44033}
44034
44035/*
44036 * hasParent: does an attribute object contain a parent of the given attribute?
44037 * for example, given 'images[2].x' do we also have 'images' or 'images[2]'?
44038 *
44039 * @param {Object} aobj
44040 * update object, whose keys are attribute strings and values are their new settings
44041 * @param {string} attr
44042 * the attribute string to test against
44043 * @returns {Boolean}
44044 * is a parent of attr present in aobj?
44045 */
44046exports.hasParent = function(aobj, attr) {
44047 var attrParent = getParent(attr);
44048 while(attrParent) {
44049 if(attrParent in aobj) return true;
44050 attrParent = getParent(attrParent);
44051 }
44052 return false;
44053};
44054
44055/**
44056 * Empty out types for all axes containing these traces so we auto-set them again
44057 *
44058 * @param {object} gd
44059 * @param {[integer]} traces: trace indices to search for axes to clear the types of
44060 * @param {object} layoutUpdate: any update being done concurrently to the layout,
44061 * which may supercede clearing the axis types
44062 */
44063var axLetters = ['x', 'y', 'z'];
44064exports.clearAxisTypes = function(gd, traces, layoutUpdate) {
44065 for(var i = 0; i < traces.length; i++) {
44066 var trace = gd._fullData[i];
44067 for(var j = 0; j < 3; j++) {
44068 var ax = getFromTrace(gd, trace, axLetters[j]);
44069
44070 // do not clear log type - that's never an auto result so must have been intentional
44071 if(ax && ax.type !== 'log') {
44072 var axAttr = ax._name;
44073 var sceneName = ax._id.substr(1);
44074 if(sceneName.substr(0, 5) === 'scene') {
44075 if(layoutUpdate[sceneName] !== undefined) continue;
44076 axAttr = sceneName + '.' + axAttr;
44077 }
44078 var typeAttr = axAttr + '.type';
44079
44080 if(layoutUpdate[axAttr] === undefined && layoutUpdate[typeAttr] === undefined) {
44081 Lib.nestedProperty(gd.layout, typeAttr).set(null);
44082 }
44083 }
44084 }
44085 }
44086};
44087
44088},{"../components/color":52,"../lib":178,"../plots/cartesian/axis_ids":225,"../plots/plots":256,"../registry":269,"fast-isnumeric":18,"gl-mat4/fromQuat":19}],207:[function(_dereq_,module,exports){
44089/**
44090* Copyright 2012-2020, Plotly, Inc.
44091* All rights reserved.
44092*
44093* This source code is licensed under the MIT license found in the
44094* LICENSE file in the root directory of this source tree.
44095*/
44096
44097'use strict';
44098
44099var main = _dereq_('./plot_api');
44100
44101exports.plot = main.plot;
44102exports.newPlot = main.newPlot;
44103exports.restyle = main.restyle;
44104exports.relayout = main.relayout;
44105exports.redraw = main.redraw;
44106exports.update = main.update;
44107exports._guiRestyle = main._guiRestyle;
44108exports._guiRelayout = main._guiRelayout;
44109exports._guiUpdate = main._guiUpdate;
44110exports._storeDirectGUIEdit = main._storeDirectGUIEdit;
44111exports.react = main.react;
44112exports.extendTraces = main.extendTraces;
44113exports.prependTraces = main.prependTraces;
44114exports.addTraces = main.addTraces;
44115exports.deleteTraces = main.deleteTraces;
44116exports.moveTraces = main.moveTraces;
44117exports.purge = main.purge;
44118exports.addFrames = main.addFrames;
44119exports.deleteFrames = main.deleteFrames;
44120exports.animate = main.animate;
44121exports.setPlotConfig = main.setPlotConfig;
44122
44123exports.toImage = _dereq_('./to_image');
44124exports.validate = _dereq_('./validate');
44125exports.downloadImage = _dereq_('../snapshot/download');
44126
44127var templateApi = _dereq_('./template_api');
44128exports.makeTemplate = templateApi.makeTemplate;
44129exports.validateTemplate = templateApi.validateTemplate;
44130
44131},{"../snapshot/download":271,"./plot_api":209,"./template_api":214,"./to_image":215,"./validate":216}],208:[function(_dereq_,module,exports){
44132/**
44133* Copyright 2012-2020, Plotly, Inc.
44134* All rights reserved.
44135*
44136* This source code is licensed under the MIT license found in the
44137* LICENSE file in the root directory of this source tree.
44138*/
44139
44140
44141'use strict';
44142
44143var isPlainObject = _dereq_('../lib/is_plain_object');
44144var noop = _dereq_('../lib/noop');
44145var Loggers = _dereq_('../lib/loggers');
44146var sorterAsc = _dereq_('../lib/search').sorterAsc;
44147var Registry = _dereq_('../registry');
44148
44149
44150exports.containerArrayMatch = _dereq_('./container_array_match');
44151
44152var isAddVal = exports.isAddVal = function isAddVal(val) {
44153 return val === 'add' || isPlainObject(val);
44154};
44155
44156var isRemoveVal = exports.isRemoveVal = function isRemoveVal(val) {
44157 return val === null || val === 'remove';
44158};
44159
44160/*
44161 * applyContainerArrayChanges: for managing arrays of layout components in relayout
44162 * handles them all with a consistent interface.
44163 *
44164 * Here are the supported actions -> relayout calls -> edits we get here
44165 * (as prepared in _relayout):
44166 *
44167 * add an empty obj -> {'annotations[2]': 'add'} -> {2: {'': 'add'}}
44168 * add a specific obj -> {'annotations[2]': {attrs}} -> {2: {'': {attrs}}}
44169 * delete an obj -> {'annotations[2]': 'remove'} -> {2: {'': 'remove'}}
44170 * -> {'annotations[2]': null} -> {2: {'': null}}
44171 * delete the whole array -> {'annotations': 'remove'} -> {'': {'': 'remove'}}
44172 * -> {'annotations': null} -> {'': {'': null}}
44173 * edit an object -> {'annotations[2].text': 'boo'} -> {2: {'text': 'boo'}}
44174 *
44175 * You can combine many edits to different objects. Objects are added and edited
44176 * in ascending order, then removed in descending order.
44177 * For example, starting with [a, b, c], if you want to:
44178 * - replace b with d:
44179 * {'annotations[1]': d, 'annotations[2]': null} (b is item 2 after adding d)
44180 * - add a new item d between a and b, and edit b:
44181 * {'annotations[1]': d, 'annotations[2].x': newX} (b is item 2 after adding d)
44182 * - delete b and edit c:
44183 * {'annotations[1]': null, 'annotations[2].x': newX} (c is edited before b is removed)
44184 *
44185 * You CANNOT combine adding/deleting an item at index `i` with edits to the same index `i`
44186 * You CANNOT combine replacing/deleting the whole array with anything else (for the same array).
44187 *
44188 * @param {HTMLDivElement} gd
44189 * the DOM element of the graph container div
44190 * @param {Lib.nestedProperty} componentType: the array we are editing
44191 * @param {Object} edits
44192 * the changes to make; keys are indices to edit, values are themselves objects:
44193 * {attr: newValue} of changes to make to that index (with add/remove behavior
44194 * in special values of the empty attr)
44195 * @param {Object} flags
44196 * the flags for which actions we're going to perform to display these (and
44197 * any other) changes. If we're already `recalc`ing, we don't need to redraw
44198 * individual items
44199 * @param {function} _nestedProperty
44200 * a (possibly modified for gui edits) nestedProperty constructor
44201 * The modified version takes a 3rd argument, for a prefix to the attribute
44202 * string necessary for storing GUI edits
44203 *
44204 * @returns {bool} `true` if it managed to complete drawing of the changes
44205 * `false` would mean the parent should replot.
44206 */
44207exports.applyContainerArrayChanges = function applyContainerArrayChanges(gd, np, edits, flags, _nestedProperty) {
44208 var componentType = np.astr;
44209 var supplyComponentDefaults = Registry.getComponentMethod(componentType, 'supplyLayoutDefaults');
44210 var draw = Registry.getComponentMethod(componentType, 'draw');
44211 var drawOne = Registry.getComponentMethod(componentType, 'drawOne');
44212 var replotLater = flags.replot || flags.recalc || (supplyComponentDefaults === noop) || (draw === noop);
44213 var layout = gd.layout;
44214 var fullLayout = gd._fullLayout;
44215
44216 if(edits['']) {
44217 if(Object.keys(edits).length > 1) {
44218 Loggers.warn('Full array edits are incompatible with other edits',
44219 componentType);
44220 }
44221
44222 var fullVal = edits[''][''];
44223
44224 if(isRemoveVal(fullVal)) np.set(null);
44225 else if(Array.isArray(fullVal)) np.set(fullVal);
44226 else {
44227 Loggers.warn('Unrecognized full array edit value', componentType, fullVal);
44228 return true;
44229 }
44230
44231 if(replotLater) return false;
44232
44233 supplyComponentDefaults(layout, fullLayout);
44234 draw(gd);
44235 return true;
44236 }
44237
44238 var componentNums = Object.keys(edits).map(Number).sort(sorterAsc);
44239 var componentArrayIn = np.get();
44240 var componentArray = componentArrayIn || [];
44241 // componentArrayFull is used just to keep splices in line between
44242 // full and input arrays, so private keys can be copied over after
44243 // redoing supplyDefaults
44244 // TODO: this assumes componentArray is in gd.layout - which will not be
44245 // true after we extend this to restyle
44246 var componentArrayFull = _nestedProperty(fullLayout, componentType).get();
44247
44248 var deletes = [];
44249 var firstIndexChange = -1;
44250 var maxIndex = componentArray.length;
44251 var i;
44252 var j;
44253 var componentNum;
44254 var objEdits;
44255 var objKeys;
44256 var objVal;
44257 var adding, prefix;
44258
44259 // first make the add and edit changes
44260 for(i = 0; i < componentNums.length; i++) {
44261 componentNum = componentNums[i];
44262 objEdits = edits[componentNum];
44263 objKeys = Object.keys(objEdits);
44264 objVal = objEdits[''],
44265 adding = isAddVal(objVal);
44266
44267 if(componentNum < 0 || componentNum > componentArray.length - (adding ? 0 : 1)) {
44268 Loggers.warn('index out of range', componentType, componentNum);
44269 continue;
44270 }
44271
44272 if(objVal !== undefined) {
44273 if(objKeys.length > 1) {
44274 Loggers.warn(
44275 'Insertion & removal are incompatible with edits to the same index.',
44276 componentType, componentNum);
44277 }
44278
44279 if(isRemoveVal(objVal)) {
44280 deletes.push(componentNum);
44281 } else if(adding) {
44282 if(objVal === 'add') objVal = {};
44283 componentArray.splice(componentNum, 0, objVal);
44284 if(componentArrayFull) componentArrayFull.splice(componentNum, 0, {});
44285 } else {
44286 Loggers.warn('Unrecognized full object edit value',
44287 componentType, componentNum, objVal);
44288 }
44289
44290 if(firstIndexChange === -1) firstIndexChange = componentNum;
44291 } else {
44292 for(j = 0; j < objKeys.length; j++) {
44293 prefix = componentType + '[' + componentNum + '].';
44294 _nestedProperty(componentArray[componentNum], objKeys[j], prefix)
44295 .set(objEdits[objKeys[j]]);
44296 }
44297 }
44298 }
44299
44300 // now do deletes
44301 for(i = deletes.length - 1; i >= 0; i--) {
44302 componentArray.splice(deletes[i], 1);
44303 // TODO: this drops private keys that had been stored in componentArrayFull
44304 // does this have any ill effects?
44305 if(componentArrayFull) componentArrayFull.splice(deletes[i], 1);
44306 }
44307
44308 if(!componentArray.length) np.set(null);
44309 else if(!componentArrayIn) np.set(componentArray);
44310
44311 if(replotLater) return false;
44312
44313 supplyComponentDefaults(layout, fullLayout);
44314
44315 // finally draw all the components we need to
44316 // if we added or removed any, redraw all after it
44317 if(drawOne !== noop) {
44318 var indicesToDraw;
44319 if(firstIndexChange === -1) {
44320 // there's no re-indexing to do, so only redraw components that changed
44321 indicesToDraw = componentNums;
44322 } else {
44323 // in case the component array was shortened, we still need do call
44324 // drawOne on the latter items so they get properly removed
44325 maxIndex = Math.max(componentArray.length, maxIndex);
44326 indicesToDraw = [];
44327 for(i = 0; i < componentNums.length; i++) {
44328 componentNum = componentNums[i];
44329 if(componentNum >= firstIndexChange) break;
44330 indicesToDraw.push(componentNum);
44331 }
44332 for(i = firstIndexChange; i < maxIndex; i++) {
44333 indicesToDraw.push(i);
44334 }
44335 }
44336 for(i = 0; i < indicesToDraw.length; i++) {
44337 drawOne(gd, indicesToDraw[i]);
44338 }
44339 } else draw(gd);
44340
44341 return true;
44342};
44343
44344},{"../lib/is_plain_object":179,"../lib/loggers":182,"../lib/noop":187,"../lib/search":196,"../registry":269,"./container_array_match":204}],209:[function(_dereq_,module,exports){
44345/**
44346* Copyright 2012-2020, Plotly, Inc.
44347* All rights reserved.
44348*
44349* This source code is licensed under the MIT license found in the
44350* LICENSE file in the root directory of this source tree.
44351*/
44352
44353'use strict';
44354
44355var d3 = _dereq_('d3');
44356var isNumeric = _dereq_('fast-isnumeric');
44357var hasHover = _dereq_('has-hover');
44358
44359var Lib = _dereq_('../lib');
44360var nestedProperty = Lib.nestedProperty;
44361
44362var Events = _dereq_('../lib/events');
44363var Queue = _dereq_('../lib/queue');
44364
44365var Registry = _dereq_('../registry');
44366var PlotSchema = _dereq_('./plot_schema');
44367var Plots = _dereq_('../plots/plots');
44368var Polar = _dereq_('../plots/polar/legacy');
44369
44370var Axes = _dereq_('../plots/cartesian/axes');
44371var Drawing = _dereq_('../components/drawing');
44372var Color = _dereq_('../components/color');
44373var initInteractions = _dereq_('../plots/cartesian/graph_interact').initInteractions;
44374var xmlnsNamespaces = _dereq_('../constants/xmlns_namespaces');
44375var svgTextUtils = _dereq_('../lib/svg_text_utils');
44376var clearSelect = _dereq_('../plots/cartesian/select').clearSelect;
44377
44378var dfltConfig = _dereq_('./plot_config').dfltConfig;
44379var manageArrays = _dereq_('./manage_arrays');
44380var helpers = _dereq_('./helpers');
44381var subroutines = _dereq_('./subroutines');
44382var editTypes = _dereq_('./edit_types');
44383
44384var AX_NAME_PATTERN = _dereq_('../plots/cartesian/constants').AX_NAME_PATTERN;
44385
44386var numericNameWarningCount = 0;
44387var numericNameWarningCountLimit = 5;
44388
44389/**
44390 * Main plot-creation function
44391 *
44392 * @param {string id or DOM element} gd
44393 * the id or DOM element of the graph container div
44394 * @param {array of objects} data
44395 * array of traces, containing the data and display information for each trace
44396 * @param {object} layout
44397 * object describing the overall display of the plot,
44398 * all the stuff that doesn't pertain to any individual trace
44399 * @param {object} config
44400 * configuration options (see ./plot_config.js for more info)
44401 *
44402 * OR
44403 *
44404 * @param {string id or DOM element} gd
44405 * the id or DOM element of the graph container div
44406 * @param {object} figure
44407 * object containing `data`, `layout`, `config`, and `frames` members
44408 *
44409 */
44410function plot(gd, data, layout, config) {
44411 var frames;
44412
44413 gd = Lib.getGraphDiv(gd);
44414
44415 // Events.init is idempotent and bails early if gd has already been init'd
44416 Events.init(gd);
44417
44418 if(Lib.isPlainObject(data)) {
44419 var obj = data;
44420 data = obj.data;
44421 layout = obj.layout;
44422 config = obj.config;
44423 frames = obj.frames;
44424 }
44425
44426 var okToPlot = Events.triggerHandler(gd, 'plotly_beforeplot', [data, layout, config]);
44427 if(okToPlot === false) return Promise.reject();
44428
44429 // if there's no data or layout, and this isn't yet a plotly plot
44430 // container, log a warning to help plotly.js users debug
44431 if(!data && !layout && !Lib.isPlotDiv(gd)) {
44432 Lib.warn('Calling Plotly.plot as if redrawing ' +
44433 'but this container doesn\'t yet have a plot.', gd);
44434 }
44435
44436 function addFrames() {
44437 if(frames) {
44438 return exports.addFrames(gd, frames);
44439 }
44440 }
44441
44442 // transfer configuration options to gd until we move over to
44443 // a more OO like model
44444 setPlotContext(gd, config);
44445
44446 if(!layout) layout = {};
44447
44448 // hook class for plots main container (in case of plotly.js
44449 // this won't be #embedded-graph or .js-tab-contents)
44450 d3.select(gd).classed('js-plotly-plot', true);
44451
44452 // off-screen getBoundingClientRect testing space,
44453 // in #js-plotly-tester (and stored as Drawing.tester)
44454 // so we can share cached text across tabs
44455 Drawing.makeTester();
44456
44457 // collect promises for any async actions during plotting
44458 // any part of the plotting code can push to gd._promises, then
44459 // before we move to the next step, we check that they're all
44460 // complete, and empty out the promise list again.
44461 if(!Array.isArray(gd._promises)) gd._promises = [];
44462
44463 var graphWasEmpty = ((gd.data || []).length === 0 && Array.isArray(data));
44464
44465 // if there is already data on the graph, append the new data
44466 // if you only want to redraw, pass a non-array for data
44467 if(Array.isArray(data)) {
44468 helpers.cleanData(data);
44469
44470 if(graphWasEmpty) gd.data = data;
44471 else gd.data.push.apply(gd.data, data);
44472
44473 // for routines outside graph_obj that want a clean tab
44474 // (rather than appending to an existing one) gd.empty
44475 // is used to determine whether to make a new tab
44476 gd.empty = false;
44477 }
44478
44479 if(!gd.layout || graphWasEmpty) {
44480 gd.layout = helpers.cleanLayout(layout);
44481 }
44482
44483 Plots.supplyDefaults(gd);
44484
44485 var fullLayout = gd._fullLayout;
44486 var hasCartesian = fullLayout._has('cartesian');
44487
44488 // Legacy polar plots
44489 if(!fullLayout._has('polar') && data && data[0] && data[0].r) {
44490 Lib.log('Legacy polar charts are deprecated!');
44491 return plotLegacyPolar(gd, data, layout);
44492 }
44493
44494 // so we don't try to re-call Plotly.plot from inside
44495 // legend and colorbar, if margins changed
44496 fullLayout._replotting = true;
44497
44498 // make or remake the framework if we need to
44499 if(graphWasEmpty || fullLayout._shouldCreateBgLayer) {
44500 makePlotFramework(gd);
44501
44502 if(fullLayout._shouldCreateBgLayer) {
44503 delete fullLayout._shouldCreateBgLayer;
44504 }
44505 }
44506
44507 // polar need a different framework
44508 if(gd.framework !== makePlotFramework) {
44509 gd.framework = makePlotFramework;
44510 makePlotFramework(gd);
44511 }
44512
44513 // clear gradient defs on each .plot call, because we know we'll loop through all traces
44514 Drawing.initGradients(gd);
44515
44516 // save initial show spikes once per graph
44517 if(graphWasEmpty) Axes.saveShowSpikeInitial(gd);
44518
44519 // prepare the data and find the autorange
44520
44521 // generate calcdata, if we need to
44522 // to force redoing calcdata, just delete it before calling Plotly.plot
44523 var recalc = !gd.calcdata || gd.calcdata.length !== (gd._fullData || []).length;
44524 if(recalc) Plots.doCalcdata(gd);
44525
44526 // in case it has changed, attach fullData traces to calcdata
44527 for(var i = 0; i < gd.calcdata.length; i++) {
44528 gd.calcdata[i][0].trace = gd._fullData[i];
44529 }
44530
44531 // make the figure responsive
44532 if(gd._context.responsive) {
44533 if(!gd._responsiveChartHandler) {
44534 // Keep a reference to the resize handler to purge it down the road
44535 gd._responsiveChartHandler = function() { if(!Lib.isHidden(gd)) Plots.resize(gd); };
44536
44537 // Listen to window resize
44538 window.addEventListener('resize', gd._responsiveChartHandler);
44539 }
44540 } else {
44541 Lib.clearResponsive(gd);
44542 }
44543
44544 /*
44545 * start async-friendly code - now we're actually drawing things
44546 */
44547
44548 var oldMargins = Lib.extendFlat({}, fullLayout._size);
44549
44550 // draw framework first so that margin-pushing
44551 // components can position themselves correctly
44552 var drawFrameworkCalls = 0;
44553 function drawFramework() {
44554 var basePlotModules = fullLayout._basePlotModules;
44555
44556 for(var i = 0; i < basePlotModules.length; i++) {
44557 if(basePlotModules[i].drawFramework) {
44558 basePlotModules[i].drawFramework(gd);
44559 }
44560 }
44561
44562 if(!fullLayout._glcanvas && fullLayout._has('gl')) {
44563 fullLayout._glcanvas = fullLayout._glcontainer.selectAll('.gl-canvas').data([{
44564 key: 'contextLayer',
44565 context: true,
44566 pick: false
44567 }, {
44568 key: 'focusLayer',
44569 context: false,
44570 pick: false
44571 }, {
44572 key: 'pickLayer',
44573 context: false,
44574 pick: true
44575 }], function(d) { return d.key; });
44576
44577 fullLayout._glcanvas.enter().append('canvas')
44578 .attr('class', function(d) {
44579 return 'gl-canvas gl-canvas-' + d.key.replace('Layer', '');
44580 })
44581 .style({
44582 position: 'absolute',
44583 top: 0,
44584 left: 0,
44585 overflow: 'visible',
44586 'pointer-events': 'none'
44587 });
44588 }
44589
44590 if(fullLayout._glcanvas) {
44591 fullLayout._glcanvas
44592 .attr('width', fullLayout.width)
44593 .attr('height', fullLayout.height);
44594
44595 var regl = fullLayout._glcanvas.data()[0].regl;
44596 if(regl) {
44597 // Unfortunately, this can happen when relayouting to large
44598 // width/height on some browsers.
44599 if(Math.floor(fullLayout.width) !== regl._gl.drawingBufferWidth ||
44600 Math.floor(fullLayout.height) !== regl._gl.drawingBufferHeight
44601 ) {
44602 var msg = 'WebGL context buffer and canvas dimensions do not match due to browser/WebGL bug.';
44603 if(drawFrameworkCalls) {
44604 Lib.error(msg);
44605 } else {
44606 Lib.log(msg + ' Clearing graph and plotting again.');
44607 Plots.cleanPlot([], {}, gd._fullData, fullLayout);
44608 Plots.supplyDefaults(gd);
44609 fullLayout = gd._fullLayout;
44610 Plots.doCalcdata(gd);
44611 drawFrameworkCalls++;
44612 return drawFramework();
44613 }
44614 }
44615 }
44616 }
44617
44618 if(fullLayout.modebar.orientation === 'h') {
44619 fullLayout._modebardiv
44620 .style('height', null)
44621 .style('width', '100%');
44622 } else {
44623 fullLayout._modebardiv
44624 .style('width', null)
44625 .style('height', fullLayout.height + 'px');
44626 }
44627
44628 return Plots.previousPromises(gd);
44629 }
44630
44631 // draw anything that can affect margins.
44632 function marginPushers() {
44633 // First reset the list of things that are allowed to change the margins
44634 // So any deleted traces or components will be wiped out of the
44635 // automargin calculation.
44636 // This means *every* margin pusher must be listed here, even if it
44637 // doesn't actually try to push the margins until later.
44638 Plots.clearAutoMarginIds(gd);
44639
44640 subroutines.drawMarginPushers(gd);
44641 Axes.allowAutoMargin(gd);
44642
44643 // TODO can this be moved elsewhere?
44644 if(fullLayout._has('pie')) {
44645 var fullData = gd._fullData;
44646 for(var i = 0; i < fullData.length; i++) {
44647 var trace = fullData[i];
44648 if(trace.type === 'pie' && trace.automargin) {
44649 Plots.allowAutoMargin(gd, 'pie.' + trace.uid + '.automargin');
44650 }
44651 }
44652 }
44653
44654 Plots.doAutoMargin(gd);
44655 return Plots.previousPromises(gd);
44656 }
44657
44658 // in case the margins changed, draw margin pushers again
44659 function marginPushersAgain() {
44660 if(!Plots.didMarginChange(oldMargins, fullLayout._size)) return;
44661
44662 return Lib.syncOrAsync([
44663 marginPushers,
44664 subroutines.layoutStyles
44665 ], gd);
44666 }
44667
44668 function positionAndAutorange() {
44669 if(!recalc) {
44670 doAutoRangeAndConstraints();
44671 return;
44672 }
44673
44674 // TODO: autosize extra for text markers and images
44675 // see https://github.com/plotly/plotly.js/issues/1111
44676 return Lib.syncOrAsync([
44677 Registry.getComponentMethod('shapes', 'calcAutorange'),
44678 Registry.getComponentMethod('annotations', 'calcAutorange'),
44679 doAutoRangeAndConstraints
44680 ], gd);
44681 }
44682
44683 function doAutoRangeAndConstraints() {
44684 if(gd._transitioning) return;
44685
44686 subroutines.doAutoRangeAndConstraints(gd);
44687
44688 // store initial ranges *after* enforcing constraints, otherwise
44689 // we will never look like we're at the initial ranges
44690 if(graphWasEmpty) Axes.saveRangeInitial(gd);
44691
44692 // this one is different from shapes/annotations calcAutorange
44693 // the others incorporate those components into ax._extremes,
44694 // this one actually sets the ranges in rangesliders.
44695 Registry.getComponentMethod('rangeslider', 'calcAutorange')(gd);
44696 }
44697
44698 // draw ticks, titles, and calculate axis scaling (._b, ._m)
44699 function drawAxes() {
44700 return Axes.draw(gd, graphWasEmpty ? '' : 'redraw');
44701 }
44702
44703 var seq = [
44704 Plots.previousPromises,
44705 addFrames,
44706 drawFramework,
44707 marginPushers,
44708 marginPushersAgain
44709 ];
44710
44711 if(hasCartesian) seq.push(positionAndAutorange);
44712
44713 seq.push(subroutines.layoutStyles);
44714 if(hasCartesian) seq.push(drawAxes);
44715
44716 seq.push(
44717 subroutines.drawData,
44718 subroutines.finalDraw,
44719 initInteractions,
44720 Plots.addLinks,
44721 Plots.rehover,
44722 Plots.redrag,
44723 // TODO: doAutoMargin is only needed here for axis automargin, which
44724 // happens outside of marginPushers where all the other automargins are
44725 // calculated. Would be much better to separate margin calculations from
44726 // component drawing - see https://github.com/plotly/plotly.js/issues/2704
44727 Plots.doAutoMargin,
44728 Plots.previousPromises
44729 );
44730
44731 // even if everything we did was synchronous, return a promise
44732 // so that the caller doesn't care which route we took
44733 var plotDone = Lib.syncOrAsync(seq, gd);
44734 if(!plotDone || !plotDone.then) plotDone = Promise.resolve();
44735
44736 return plotDone.then(function() {
44737 emitAfterPlot(gd);
44738 return gd;
44739 });
44740}
44741
44742function emitAfterPlot(gd) {
44743 var fullLayout = gd._fullLayout;
44744
44745 if(fullLayout._redrawFromAutoMarginCount) {
44746 fullLayout._redrawFromAutoMarginCount--;
44747 } else {
44748 gd.emit('plotly_afterplot');
44749 }
44750}
44751
44752function setPlotConfig(obj) {
44753 return Lib.extendFlat(dfltConfig, obj);
44754}
44755
44756function setBackground(gd, bgColor) {
44757 try {
44758 gd._fullLayout._paper.style('background', bgColor);
44759 } catch(e) {
44760 Lib.error(e);
44761 }
44762}
44763
44764function opaqueSetBackground(gd, bgColor) {
44765 var blend = Color.combine(bgColor, 'white');
44766 setBackground(gd, blend);
44767}
44768
44769function setPlotContext(gd, config) {
44770 if(!gd._context) {
44771 gd._context = Lib.extendDeep({}, dfltConfig);
44772
44773 // stash <base> href, used to make robust clipPath URLs
44774 var base = d3.select('base');
44775 gd._context._baseUrl = base.size() && base.attr('href') ?
44776 window.location.href.split('#')[0] :
44777 '';
44778 }
44779
44780 var context = gd._context;
44781
44782 var i, keys, key;
44783
44784 if(config) {
44785 keys = Object.keys(config);
44786 for(i = 0; i < keys.length; i++) {
44787 key = keys[i];
44788 if(key === 'editable' || key === 'edits') continue;
44789 if(key in context) {
44790 if(key === 'setBackground' && config[key] === 'opaque') {
44791 context[key] = opaqueSetBackground;
44792 } else {
44793 context[key] = config[key];
44794 }
44795 }
44796 }
44797
44798 // map plot3dPixelRatio to plotGlPixelRatio for backward compatibility
44799 if(config.plot3dPixelRatio && !context.plotGlPixelRatio) {
44800 context.plotGlPixelRatio = context.plot3dPixelRatio;
44801 }
44802
44803 // now deal with editable and edits - first editable overrides
44804 // everything, then edits refines
44805 var editable = config.editable;
44806 if(editable !== undefined) {
44807 // we're not going to *use* context.editable, we're only going to
44808 // use context.edits... but keep it for the record
44809 context.editable = editable;
44810
44811 keys = Object.keys(context.edits);
44812 for(i = 0; i < keys.length; i++) {
44813 context.edits[keys[i]] = editable;
44814 }
44815 }
44816 if(config.edits) {
44817 keys = Object.keys(config.edits);
44818 for(i = 0; i < keys.length; i++) {
44819 key = keys[i];
44820 if(key in context.edits) {
44821 context.edits[key] = config.edits[key];
44822 }
44823 }
44824 }
44825
44826 // not part of the user-facing config options
44827 context._exportedPlot = config._exportedPlot;
44828 }
44829
44830 // staticPlot forces a bunch of others:
44831 if(context.staticPlot) {
44832 context.editable = false;
44833 context.edits = {};
44834 context.autosizable = false;
44835 context.scrollZoom = false;
44836 context.doubleClick = false;
44837 context.showTips = false;
44838 context.showLink = false;
44839 context.displayModeBar = false;
44840 }
44841
44842 // make sure hover-only devices have mode bar visible
44843 if(context.displayModeBar === 'hover' && !hasHover) {
44844 context.displayModeBar = true;
44845 }
44846
44847 // default and fallback for setBackground
44848 if(context.setBackground === 'transparent' || typeof context.setBackground !== 'function') {
44849 context.setBackground = setBackground;
44850 }
44851
44852 // Check if gd has a specified widht/height to begin with
44853 context._hasZeroHeight = context._hasZeroHeight || gd.clientHeight === 0;
44854 context._hasZeroWidth = context._hasZeroWidth || gd.clientWidth === 0;
44855
44856 // fill context._scrollZoom helper to help manage scrollZoom flaglist
44857 var szIn = context.scrollZoom;
44858 var szOut = context._scrollZoom = {};
44859 if(szIn === true) {
44860 szOut.cartesian = 1;
44861 szOut.gl3d = 1;
44862 szOut.geo = 1;
44863 szOut.mapbox = 1;
44864 } else if(typeof szIn === 'string') {
44865 var parts = szIn.split('+');
44866 for(i = 0; i < parts.length; i++) {
44867 szOut[parts[i]] = 1;
44868 }
44869 } else if(szIn !== false) {
44870 szOut.gl3d = 1;
44871 szOut.geo = 1;
44872 szOut.mapbox = 1;
44873 }
44874}
44875
44876function plotLegacyPolar(gd, data, layout) {
44877 // build or reuse the container skeleton
44878 var plotContainer = d3.select(gd).selectAll('.plot-container')
44879 .data([0]);
44880 plotContainer.enter()
44881 .insert('div', ':first-child')
44882 .classed('plot-container plotly', true);
44883 var paperDiv = plotContainer.selectAll('.svg-container')
44884 .data([0]);
44885 paperDiv.enter().append('div')
44886 .classed('svg-container', true)
44887 .style('position', 'relative');
44888
44889 // empty it everytime for now
44890 paperDiv.html('');
44891
44892 // fulfill gd requirements
44893 if(data) gd.data = data;
44894 if(layout) gd.layout = layout;
44895 Polar.manager.fillLayout(gd);
44896
44897 // resize canvas
44898 paperDiv.style({
44899 width: gd._fullLayout.width + 'px',
44900 height: gd._fullLayout.height + 'px'
44901 });
44902
44903 // instantiate framework
44904 gd.framework = Polar.manager.framework(gd);
44905
44906 // plot
44907 gd.framework({data: gd.data, layout: gd.layout}, paperDiv.node());
44908
44909 // set undo point
44910 gd.framework.setUndoPoint();
44911
44912 // get the resulting svg for extending it
44913 var polarPlotSVG = gd.framework.svg();
44914
44915 // editable title
44916 var opacity = 1;
44917 var txt = gd._fullLayout.title ? gd._fullLayout.title.text : '';
44918 if(txt === '' || !txt) opacity = 0;
44919
44920 var titleLayout = function() {
44921 this.call(svgTextUtils.convertToTspans, gd);
44922 // TODO: html/mathjax
44923 // TODO: center title
44924 };
44925
44926 var title = polarPlotSVG.select('.title-group text')
44927 .call(titleLayout);
44928
44929 if(gd._context.edits.titleText) {
44930 var placeholderText = Lib._(gd, 'Click to enter Plot title');
44931 if(!txt || txt === placeholderText) {
44932 opacity = 0.2;
44933 // placeholder is not going through convertToTspans
44934 // so needs explicit data-unformatted
44935 title.attr({'data-unformatted': placeholderText})
44936 .text(placeholderText)
44937 .style({opacity: opacity})
44938 .on('mouseover.opacity', function() {
44939 d3.select(this).transition().duration(100)
44940 .style('opacity', 1);
44941 })
44942 .on('mouseout.opacity', function() {
44943 d3.select(this).transition().duration(1000)
44944 .style('opacity', 0);
44945 });
44946 }
44947
44948 var setContenteditable = function() {
44949 this.call(svgTextUtils.makeEditable, {gd: gd})
44950 .on('edit', function(text) {
44951 gd.framework({layout: {title: {text: text}}});
44952 this.text(text)
44953 .call(titleLayout);
44954 this.call(setContenteditable);
44955 })
44956 .on('cancel', function() {
44957 var txt = this.attr('data-unformatted');
44958 this.text(txt).call(titleLayout);
44959 });
44960 };
44961 title.call(setContenteditable);
44962 }
44963
44964 gd._context.setBackground(gd, gd._fullLayout.paper_bgcolor);
44965 Plots.addLinks(gd);
44966
44967 return Promise.resolve();
44968}
44969
44970// convenience function to force a full redraw, mostly for use by plotly.js
44971function redraw(gd) {
44972 gd = Lib.getGraphDiv(gd);
44973
44974 if(!Lib.isPlotDiv(gd)) {
44975 throw new Error('This element is not a Plotly plot: ' + gd);
44976 }
44977
44978 helpers.cleanData(gd.data);
44979 helpers.cleanLayout(gd.layout);
44980
44981 gd.calcdata = undefined;
44982 return exports.plot(gd).then(function() {
44983 gd.emit('plotly_redraw');
44984 return gd;
44985 });
44986}
44987
44988/**
44989 * Convenience function to make idempotent plot option obvious to users.
44990 *
44991 * @param gd
44992 * @param {Object[]} data
44993 * @param {Object} layout
44994 * @param {Object} config
44995 */
44996function newPlot(gd, data, layout, config) {
44997 gd = Lib.getGraphDiv(gd);
44998
44999 // remove gl contexts
45000 Plots.cleanPlot([], {}, gd._fullData || [], gd._fullLayout || {});
45001
45002 Plots.purge(gd);
45003 return exports.plot(gd, data, layout, config);
45004}
45005
45006/**
45007 * Wrap negative indicies to their positive counterparts.
45008 *
45009 * @param {Number[]} indices An array of indices
45010 * @param {Number} maxIndex The maximum index allowable (arr.length - 1)
45011 */
45012function positivifyIndices(indices, maxIndex) {
45013 var parentLength = maxIndex + 1;
45014 var positiveIndices = [];
45015 var i;
45016 var index;
45017
45018 for(i = 0; i < indices.length; i++) {
45019 index = indices[i];
45020 if(index < 0) {
45021 positiveIndices.push(parentLength + index);
45022 } else {
45023 positiveIndices.push(index);
45024 }
45025 }
45026 return positiveIndices;
45027}
45028
45029/**
45030 * Ensures that an index array for manipulating gd.data is valid.
45031 *
45032 * Intended for use with addTraces, deleteTraces, and moveTraces.
45033 *
45034 * @param gd
45035 * @param indices
45036 * @param arrayName
45037 */
45038function assertIndexArray(gd, indices, arrayName) {
45039 var i,
45040 index;
45041
45042 for(i = 0; i < indices.length; i++) {
45043 index = indices[i];
45044
45045 // validate that indices are indeed integers
45046 if(index !== parseInt(index, 10)) {
45047 throw new Error('all values in ' + arrayName + ' must be integers');
45048 }
45049
45050 // check that all indices are in bounds for given gd.data array length
45051 if(index >= gd.data.length || index < -gd.data.length) {
45052 throw new Error(arrayName + ' must be valid indices for gd.data.');
45053 }
45054
45055 // check that indices aren't repeated
45056 if(indices.indexOf(index, i + 1) > -1 ||
45057 index >= 0 && indices.indexOf(-gd.data.length + index) > -1 ||
45058 index < 0 && indices.indexOf(gd.data.length + index) > -1) {
45059 throw new Error('each index in ' + arrayName + ' must be unique.');
45060 }
45061 }
45062}
45063
45064/**
45065 * Private function used by Plotly.moveTraces to check input args
45066 *
45067 * @param gd
45068 * @param currentIndices
45069 * @param newIndices
45070 */
45071function checkMoveTracesArgs(gd, currentIndices, newIndices) {
45072 // check that gd has attribute 'data' and 'data' is array
45073 if(!Array.isArray(gd.data)) {
45074 throw new Error('gd.data must be an array.');
45075 }
45076
45077 // validate currentIndices array
45078 if(typeof currentIndices === 'undefined') {
45079 throw new Error('currentIndices is a required argument.');
45080 } else if(!Array.isArray(currentIndices)) {
45081 currentIndices = [currentIndices];
45082 }
45083 assertIndexArray(gd, currentIndices, 'currentIndices');
45084
45085 // validate newIndices array if it exists
45086 if(typeof newIndices !== 'undefined' && !Array.isArray(newIndices)) {
45087 newIndices = [newIndices];
45088 }
45089 if(typeof newIndices !== 'undefined') {
45090 assertIndexArray(gd, newIndices, 'newIndices');
45091 }
45092
45093 // check currentIndices and newIndices are the same length if newIdices exists
45094 if(typeof newIndices !== 'undefined' && currentIndices.length !== newIndices.length) {
45095 throw new Error('current and new indices must be of equal length.');
45096 }
45097}
45098/**
45099 * A private function to reduce the type checking clutter in addTraces.
45100 *
45101 * @param gd
45102 * @param traces
45103 * @param newIndices
45104 */
45105function checkAddTracesArgs(gd, traces, newIndices) {
45106 var i, value;
45107
45108 // check that gd has attribute 'data' and 'data' is array
45109 if(!Array.isArray(gd.data)) {
45110 throw new Error('gd.data must be an array.');
45111 }
45112
45113 // make sure traces exists
45114 if(typeof traces === 'undefined') {
45115 throw new Error('traces must be defined.');
45116 }
45117
45118 // make sure traces is an array
45119 if(!Array.isArray(traces)) {
45120 traces = [traces];
45121 }
45122
45123 // make sure each value in traces is an object
45124 for(i = 0; i < traces.length; i++) {
45125 value = traces[i];
45126 if(typeof value !== 'object' || (Array.isArray(value) || value === null)) {
45127 throw new Error('all values in traces array must be non-array objects');
45128 }
45129 }
45130
45131 // make sure we have an index for each trace
45132 if(typeof newIndices !== 'undefined' && !Array.isArray(newIndices)) {
45133 newIndices = [newIndices];
45134 }
45135 if(typeof newIndices !== 'undefined' && newIndices.length !== traces.length) {
45136 throw new Error(
45137 'if indices is specified, traces.length must equal indices.length'
45138 );
45139 }
45140}
45141
45142/**
45143 * A private function to reduce the type checking clutter in spliceTraces.
45144 * Get all update Properties from gd.data. Validate inputs and outputs.
45145 * Used by prependTrace and extendTraces
45146 *
45147 * @param gd
45148 * @param update
45149 * @param indices
45150 * @param maxPoints
45151 */
45152function assertExtendTracesArgs(gd, update, indices, maxPoints) {
45153 var maxPointsIsObject = Lib.isPlainObject(maxPoints);
45154
45155 if(!Array.isArray(gd.data)) {
45156 throw new Error('gd.data must be an array');
45157 }
45158 if(!Lib.isPlainObject(update)) {
45159 throw new Error('update must be a key:value object');
45160 }
45161
45162 if(typeof indices === 'undefined') {
45163 throw new Error('indices must be an integer or array of integers');
45164 }
45165
45166 assertIndexArray(gd, indices, 'indices');
45167
45168 for(var key in update) {
45169 /*
45170 * Verify that the attribute to be updated contains as many trace updates
45171 * as indices. Failure must result in throw and no-op
45172 */
45173 if(!Array.isArray(update[key]) || update[key].length !== indices.length) {
45174 throw new Error('attribute ' + key + ' must be an array of length equal to indices array length');
45175 }
45176
45177 /*
45178 * if maxPoints is an object it must match keys and array lengths of 'update' 1:1
45179 */
45180 if(maxPointsIsObject &&
45181 (!(key in maxPoints) || !Array.isArray(maxPoints[key]) ||
45182 maxPoints[key].length !== update[key].length)) {
45183 throw new Error('when maxPoints is set as a key:value object it must contain a 1:1 ' +
45184 'corrispondence with the keys and number of traces in the update object');
45185 }
45186 }
45187}
45188
45189/**
45190 * A private function to reduce the type checking clutter in spliceTraces.
45191 *
45192 * @param {Object|HTMLDivElement} gd
45193 * @param {Object} update
45194 * @param {Number[]} indices
45195 * @param {Number||Object} maxPoints
45196 * @return {Object[]}
45197 */
45198function getExtendProperties(gd, update, indices, maxPoints) {
45199 var maxPointsIsObject = Lib.isPlainObject(maxPoints);
45200 var updateProps = [];
45201 var trace, target, prop, insert, maxp;
45202
45203 // allow scalar index to represent a single trace position
45204 if(!Array.isArray(indices)) indices = [indices];
45205
45206 // negative indices are wrapped around to their positive value. Equivalent to python indexing.
45207 indices = positivifyIndices(indices, gd.data.length - 1);
45208
45209 // loop through all update keys and traces and harvest validated data.
45210 for(var key in update) {
45211 for(var j = 0; j < indices.length; j++) {
45212 /*
45213 * Choose the trace indexed by the indices map argument and get the prop setter-getter
45214 * instance that references the key and value for this particular trace.
45215 */
45216 trace = gd.data[indices[j]];
45217 prop = nestedProperty(trace, key);
45218
45219 /*
45220 * Target is the existing gd.data.trace.dataArray value like "x" or "marker.size"
45221 * Target must exist as an Array to allow the extend operation to be performed.
45222 */
45223 target = prop.get();
45224 insert = update[key][j];
45225
45226 if(!Lib.isArrayOrTypedArray(insert)) {
45227 throw new Error('attribute: ' + key + ' index: ' + j + ' must be an array');
45228 }
45229 if(!Lib.isArrayOrTypedArray(target)) {
45230 throw new Error('cannot extend missing or non-array attribute: ' + key);
45231 }
45232 if(target.constructor !== insert.constructor) {
45233 throw new Error('cannot extend array with an array of a different type: ' + key);
45234 }
45235
45236 /*
45237 * maxPoints may be an object map or a scalar. If object select the key:value, else
45238 * Use the scalar maxPoints for all key and trace combinations.
45239 */
45240 maxp = maxPointsIsObject ? maxPoints[key][j] : maxPoints;
45241
45242 // could have chosen null here, -1 just tells us to not take a window
45243 if(!isNumeric(maxp)) maxp = -1;
45244
45245 /*
45246 * Wrap the nestedProperty in an object containing required data
45247 * for lengthening and windowing this particular trace - key combination.
45248 * Flooring maxp mirrors the behaviour of floats in the Array.slice JSnative function.
45249 */
45250 updateProps.push({
45251 prop: prop,
45252 target: target,
45253 insert: insert,
45254 maxp: Math.floor(maxp)
45255 });
45256 }
45257 }
45258
45259 // all target and insertion data now validated
45260 return updateProps;
45261}
45262
45263/**
45264 * A private function to key Extend and Prepend traces DRY
45265 *
45266 * @param {Object|HTMLDivElement} gd
45267 * @param {Object} update
45268 * @param {Number[]} indices
45269 * @param {Number||Object} maxPoints
45270 * @param {Function} updateArray
45271 * @return {Object}
45272 */
45273function spliceTraces(gd, update, indices, maxPoints, updateArray) {
45274 assertExtendTracesArgs(gd, update, indices, maxPoints);
45275
45276 var updateProps = getExtendProperties(gd, update, indices, maxPoints);
45277 var undoUpdate = {};
45278 var undoPoints = {};
45279
45280 for(var i = 0; i < updateProps.length; i++) {
45281 var prop = updateProps[i].prop;
45282 var maxp = updateProps[i].maxp;
45283
45284 // return new array and remainder
45285 var out = updateArray(updateProps[i].target, updateProps[i].insert, maxp);
45286 prop.set(out[0]);
45287
45288 // build the inverse update object for the undo operation
45289 if(!Array.isArray(undoUpdate[prop.astr])) undoUpdate[prop.astr] = [];
45290 undoUpdate[prop.astr].push(out[1]);
45291
45292 // build the matching maxPoints undo object containing original trace lengths
45293 if(!Array.isArray(undoPoints[prop.astr])) undoPoints[prop.astr] = [];
45294 undoPoints[prop.astr].push(updateProps[i].target.length);
45295 }
45296
45297 return {update: undoUpdate, maxPoints: undoPoints};
45298}
45299
45300function concatTypedArray(arr0, arr1) {
45301 var arr2 = new arr0.constructor(arr0.length + arr1.length);
45302 arr2.set(arr0);
45303 arr2.set(arr1, arr0.length);
45304 return arr2;
45305}
45306
45307/**
45308 * extend && prepend traces at indices with update arrays, window trace lengths to maxPoints
45309 *
45310 * Extend and Prepend have identical APIs. Prepend inserts an array at the head while Extend
45311 * inserts an array off the tail. Prepend truncates the tail of the array - counting maxPoints
45312 * from the head, whereas Extend truncates the head of the array, counting backward maxPoints
45313 * from the tail.
45314 *
45315 * If maxPoints is undefined, nonNumeric, negative or greater than extended trace length no
45316 * truncation / windowing will be performed. If its zero, well the whole trace is truncated.
45317 *
45318 * @param {Object|HTMLDivElement} gd The graph div
45319 * @param {Object} update The key:array map of target attributes to extend
45320 * @param {Number|Number[]} indices The locations of traces to be extended
45321 * @param {Number|Object} [maxPoints] Number of points for trace window after lengthening.
45322 *
45323 */
45324function extendTraces(gd, update, indices, maxPoints) {
45325 gd = Lib.getGraphDiv(gd);
45326
45327 function updateArray(target, insert, maxp) {
45328 var newArray, remainder;
45329
45330 if(Lib.isTypedArray(target)) {
45331 if(maxp < 0) {
45332 var none = new target.constructor(0);
45333 var both = concatTypedArray(target, insert);
45334
45335 if(maxp < 0) {
45336 newArray = both;
45337 remainder = none;
45338 } else {
45339 newArray = none;
45340 remainder = both;
45341 }
45342 } else {
45343 newArray = new target.constructor(maxp);
45344 remainder = new target.constructor(target.length + insert.length - maxp);
45345
45346 if(maxp === insert.length) {
45347 newArray.set(insert);
45348 remainder.set(target);
45349 } else if(maxp < insert.length) {
45350 var numberOfItemsFromInsert = insert.length - maxp;
45351
45352 newArray.set(insert.subarray(numberOfItemsFromInsert));
45353 remainder.set(target);
45354 remainder.set(insert.subarray(0, numberOfItemsFromInsert), target.length);
45355 } else {
45356 var numberOfItemsFromTarget = maxp - insert.length;
45357 var targetBegin = target.length - numberOfItemsFromTarget;
45358
45359 newArray.set(target.subarray(targetBegin));
45360 newArray.set(insert, numberOfItemsFromTarget);
45361 remainder.set(target.subarray(0, targetBegin));
45362 }
45363 }
45364 } else {
45365 newArray = target.concat(insert);
45366 remainder = (maxp >= 0 && maxp < newArray.length) ?
45367 newArray.splice(0, newArray.length - maxp) :
45368 [];
45369 }
45370
45371 return [newArray, remainder];
45372 }
45373
45374 var undo = spliceTraces(gd, update, indices, maxPoints, updateArray);
45375 var promise = exports.redraw(gd);
45376 var undoArgs = [gd, undo.update, indices, undo.maxPoints];
45377 Queue.add(gd, exports.prependTraces, undoArgs, extendTraces, arguments);
45378
45379 return promise;
45380}
45381
45382function prependTraces(gd, update, indices, maxPoints) {
45383 gd = Lib.getGraphDiv(gd);
45384
45385 function updateArray(target, insert, maxp) {
45386 var newArray, remainder;
45387
45388 if(Lib.isTypedArray(target)) {
45389 if(maxp <= 0) {
45390 var none = new target.constructor(0);
45391 var both = concatTypedArray(insert, target);
45392
45393 if(maxp < 0) {
45394 newArray = both;
45395 remainder = none;
45396 } else {
45397 newArray = none;
45398 remainder = both;
45399 }
45400 } else {
45401 newArray = new target.constructor(maxp);
45402 remainder = new target.constructor(target.length + insert.length - maxp);
45403
45404 if(maxp === insert.length) {
45405 newArray.set(insert);
45406 remainder.set(target);
45407 } else if(maxp < insert.length) {
45408 var numberOfItemsFromInsert = insert.length - maxp;
45409
45410 newArray.set(insert.subarray(0, numberOfItemsFromInsert));
45411 remainder.set(insert.subarray(numberOfItemsFromInsert));
45412 remainder.set(target, numberOfItemsFromInsert);
45413 } else {
45414 var numberOfItemsFromTarget = maxp - insert.length;
45415
45416 newArray.set(insert);
45417 newArray.set(target.subarray(0, numberOfItemsFromTarget), insert.length);
45418 remainder.set(target.subarray(numberOfItemsFromTarget));
45419 }
45420 }
45421 } else {
45422 newArray = insert.concat(target);
45423 remainder = (maxp >= 0 && maxp < newArray.length) ?
45424 newArray.splice(maxp, newArray.length) :
45425 [];
45426 }
45427
45428 return [newArray, remainder];
45429 }
45430
45431 var undo = spliceTraces(gd, update, indices, maxPoints, updateArray);
45432 var promise = exports.redraw(gd);
45433 var undoArgs = [gd, undo.update, indices, undo.maxPoints];
45434 Queue.add(gd, exports.extendTraces, undoArgs, prependTraces, arguments);
45435
45436 return promise;
45437}
45438
45439/**
45440 * Add data traces to an existing graph div.
45441 *
45442 * @param {Object|HTMLDivElement} gd The graph div
45443 * @param {Object[]} gd.data The array of traces we're adding to
45444 * @param {Object[]|Object} traces The object or array of objects to add
45445 * @param {Number[]|Number} [newIndices=[gd.data.length]] Locations to add traces
45446 *
45447 */
45448function addTraces(gd, traces, newIndices) {
45449 gd = Lib.getGraphDiv(gd);
45450
45451 var currentIndices = [];
45452 var undoFunc = exports.deleteTraces;
45453 var redoFunc = addTraces;
45454 var undoArgs = [gd, currentIndices];
45455 var redoArgs = [gd, traces]; // no newIndices here
45456 var i;
45457 var promise;
45458
45459 // all validation is done elsewhere to remove clutter here
45460 checkAddTracesArgs(gd, traces, newIndices);
45461
45462 // make sure traces is an array
45463 if(!Array.isArray(traces)) {
45464 traces = [traces];
45465 }
45466
45467 // make sure traces do not repeat existing ones
45468 traces = traces.map(function(trace) {
45469 return Lib.extendFlat({}, trace);
45470 });
45471
45472 helpers.cleanData(traces);
45473
45474 // add the traces to gd.data (no redrawing yet!)
45475 for(i = 0; i < traces.length; i++) {
45476 gd.data.push(traces[i]);
45477 }
45478
45479 // to continue, we need to call moveTraces which requires currentIndices
45480 for(i = 0; i < traces.length; i++) {
45481 currentIndices.push(-traces.length + i);
45482 }
45483
45484 // if the user didn't define newIndices, they just want the traces appended
45485 // i.e., we can simply redraw and be done
45486 if(typeof newIndices === 'undefined') {
45487 promise = exports.redraw(gd);
45488 Queue.add(gd, undoFunc, undoArgs, redoFunc, redoArgs);
45489 return promise;
45490 }
45491
45492 // make sure indices is property defined
45493 if(!Array.isArray(newIndices)) {
45494 newIndices = [newIndices];
45495 }
45496
45497 try {
45498 // this is redundant, but necessary to not catch later possible errors!
45499 checkMoveTracesArgs(gd, currentIndices, newIndices);
45500 } catch(error) {
45501 // something went wrong, reset gd to be safe and rethrow error
45502 gd.data.splice(gd.data.length - traces.length, traces.length);
45503 throw error;
45504 }
45505
45506 // if we're here, the user has defined specific places to place the new traces
45507 // this requires some extra work that moveTraces will do
45508 Queue.startSequence(gd);
45509 Queue.add(gd, undoFunc, undoArgs, redoFunc, redoArgs);
45510 promise = exports.moveTraces(gd, currentIndices, newIndices);
45511 Queue.stopSequence(gd);
45512 return promise;
45513}
45514
45515/**
45516 * Delete traces at `indices` from gd.data array.
45517 *
45518 * @param {Object|HTMLDivElement} gd The graph div
45519 * @param {Object[]} gd.data The array of traces we're removing from
45520 * @param {Number|Number[]} indices The indices
45521 */
45522function deleteTraces(gd, indices) {
45523 gd = Lib.getGraphDiv(gd);
45524
45525 var traces = [];
45526 var undoFunc = exports.addTraces;
45527 var redoFunc = deleteTraces;
45528 var undoArgs = [gd, traces, indices];
45529 var redoArgs = [gd, indices];
45530 var i;
45531 var deletedTrace;
45532
45533 // make sure indices are defined
45534 if(typeof indices === 'undefined') {
45535 throw new Error('indices must be an integer or array of integers.');
45536 } else if(!Array.isArray(indices)) {
45537 indices = [indices];
45538 }
45539 assertIndexArray(gd, indices, 'indices');
45540
45541 // convert negative indices to positive indices
45542 indices = positivifyIndices(indices, gd.data.length - 1);
45543
45544 // we want descending here so that splicing later doesn't affect indexing
45545 indices.sort(Lib.sorterDes);
45546 for(i = 0; i < indices.length; i += 1) {
45547 deletedTrace = gd.data.splice(indices[i], 1)[0];
45548 traces.push(deletedTrace);
45549 }
45550
45551 var promise = exports.redraw(gd);
45552 Queue.add(gd, undoFunc, undoArgs, redoFunc, redoArgs);
45553
45554 return promise;
45555}
45556
45557/**
45558 * Move traces at currentIndices array to locations in newIndices array.
45559 *
45560 * If newIndices is omitted, currentIndices will be moved to the end. E.g.,
45561 * these are equivalent:
45562 *
45563 * Plotly.moveTraces(gd, [1, 2, 3], [-3, -2, -1])
45564 * Plotly.moveTraces(gd, [1, 2, 3])
45565 *
45566 * @param {Object|HTMLDivElement} gd The graph div
45567 * @param {Object[]} gd.data The array of traces we're removing from
45568 * @param {Number|Number[]} currentIndices The locations of traces to be moved
45569 * @param {Number|Number[]} [newIndices] The locations to move traces to
45570 *
45571 * Example calls:
45572 *
45573 * // move trace i to location x
45574 * Plotly.moveTraces(gd, i, x)
45575 *
45576 * // move trace i to end of array
45577 * Plotly.moveTraces(gd, i)
45578 *
45579 * // move traces i, j, k to end of array (i != j != k)
45580 * Plotly.moveTraces(gd, [i, j, k])
45581 *
45582 * // move traces [i, j, k] to [x, y, z] (i != j != k) (x != y != z)
45583 * Plotly.moveTraces(gd, [i, j, k], [x, y, z])
45584 *
45585 * // reorder all traces (assume there are 5--a, b, c, d, e)
45586 * Plotly.moveTraces(gd, [b, d, e, a, c]) // same as 'move to end'
45587 */
45588function moveTraces(gd, currentIndices, newIndices) {
45589 gd = Lib.getGraphDiv(gd);
45590
45591 var newData = [];
45592 var movingTraceMap = [];
45593 var undoFunc = moveTraces;
45594 var redoFunc = moveTraces;
45595 var undoArgs = [gd, newIndices, currentIndices];
45596 var redoArgs = [gd, currentIndices, newIndices];
45597 var i;
45598
45599 // to reduce complexity here, check args elsewhere
45600 // this throws errors where appropriate
45601 checkMoveTracesArgs(gd, currentIndices, newIndices);
45602
45603 // make sure currentIndices is an array
45604 currentIndices = Array.isArray(currentIndices) ? currentIndices : [currentIndices];
45605
45606 // if undefined, define newIndices to point to the end of gd.data array
45607 if(typeof newIndices === 'undefined') {
45608 newIndices = [];
45609 for(i = 0; i < currentIndices.length; i++) {
45610 newIndices.push(-currentIndices.length + i);
45611 }
45612 }
45613
45614 // make sure newIndices is an array if it's user-defined
45615 newIndices = Array.isArray(newIndices) ? newIndices : [newIndices];
45616
45617 // convert negative indices to positive indices (they're the same length)
45618 currentIndices = positivifyIndices(currentIndices, gd.data.length - 1);
45619 newIndices = positivifyIndices(newIndices, gd.data.length - 1);
45620
45621 // at this point, we've coerced the index arrays into predictable forms
45622
45623 // get the traces that aren't being moved around
45624 for(i = 0; i < gd.data.length; i++) {
45625 // if index isn't in currentIndices, include it in ignored!
45626 if(currentIndices.indexOf(i) === -1) {
45627 newData.push(gd.data[i]);
45628 }
45629 }
45630
45631 // get a mapping of indices to moving traces
45632 for(i = 0; i < currentIndices.length; i++) {
45633 movingTraceMap.push({newIndex: newIndices[i], trace: gd.data[currentIndices[i]]});
45634 }
45635
45636 // reorder this mapping by newIndex, ascending
45637 movingTraceMap.sort(function(a, b) {
45638 return a.newIndex - b.newIndex;
45639 });
45640
45641 // now, add the moving traces back in, in order!
45642 for(i = 0; i < movingTraceMap.length; i += 1) {
45643 newData.splice(movingTraceMap[i].newIndex, 0, movingTraceMap[i].trace);
45644 }
45645
45646 gd.data = newData;
45647
45648 var promise = exports.redraw(gd);
45649 Queue.add(gd, undoFunc, undoArgs, redoFunc, redoArgs);
45650
45651 return promise;
45652}
45653
45654/**
45655 * restyle: update trace attributes of an existing plot
45656 *
45657 * Can be called two ways.
45658 *
45659 * Signature 1:
45660 * @param {String | HTMLDivElement} gd
45661 * the id or DOM element of the graph container div
45662 * @param {String} astr
45663 * attribute string (like `'marker.symbol'`) to update
45664 * @param {*} val
45665 * value to give this attribute
45666 * @param {Number[] | Number} [traces]
45667 * integer or array of integers for the traces to alter (all if omitted)
45668 *
45669 * Signature 2:
45670 * @param {String | HTMLDivElement} gd
45671 * (as in signature 1)
45672 * @param {Object} aobj
45673 * attribute object `{astr1: val1, astr2: val2 ...}`
45674 * allows setting multiple attributes simultaneously
45675 * @param {Number[] | Number} [traces]
45676 * (as in signature 1)
45677 *
45678 * `val` (or `val1`, `val2` ... in the object form) can be an array,
45679 * to apply different values to each trace.
45680 *
45681 * If the array is too short, it will wrap around (useful for
45682 * style files that want to specify cyclical default values).
45683 */
45684function restyle(gd, astr, val, _traces) {
45685 gd = Lib.getGraphDiv(gd);
45686 helpers.clearPromiseQueue(gd);
45687
45688 var aobj = {};
45689 if(typeof astr === 'string') aobj[astr] = val;
45690 else if(Lib.isPlainObject(astr)) {
45691 // the 3-arg form
45692 aobj = Lib.extendFlat({}, astr);
45693 if(_traces === undefined) _traces = val;
45694 } else {
45695 Lib.warn('Restyle fail.', astr, val, _traces);
45696 return Promise.reject();
45697 }
45698
45699 if(Object.keys(aobj).length) gd.changed = true;
45700
45701 var traces = helpers.coerceTraceIndices(gd, _traces);
45702
45703 var specs = _restyle(gd, aobj, traces);
45704 var flags = specs.flags;
45705
45706 // clear calcdata and/or axis types if required so they get regenerated
45707 if(flags.calc) gd.calcdata = undefined;
45708 if(flags.clearAxisTypes) helpers.clearAxisTypes(gd, traces, {});
45709
45710 // fill in redraw sequence
45711 var seq = [];
45712
45713 if(flags.fullReplot) {
45714 seq.push(exports.plot);
45715 } else {
45716 seq.push(Plots.previousPromises);
45717
45718 // maybe only call Plots.supplyDataDefaults in the splom case,
45719 // to skip over long and slow axes defaults
45720 Plots.supplyDefaults(gd);
45721
45722 if(flags.markerSize) {
45723 Plots.doCalcdata(gd);
45724 addAxRangeSequence(seq);
45725
45726 // TODO
45727 // if all axes have autorange:false, then
45728 // proceed to subroutines.doTraceStyle(),
45729 // otherwise we must go through addAxRangeSequence,
45730 // which in general must redraws 'all' axes
45731 }
45732
45733 if(flags.style) seq.push(subroutines.doTraceStyle);
45734 if(flags.colorbars) seq.push(subroutines.doColorBars);
45735
45736 seq.push(emitAfterPlot);
45737 }
45738
45739 seq.push(Plots.rehover, Plots.redrag);
45740
45741 Queue.add(gd,
45742 restyle, [gd, specs.undoit, specs.traces],
45743 restyle, [gd, specs.redoit, specs.traces]
45744 );
45745
45746 var plotDone = Lib.syncOrAsync(seq, gd);
45747 if(!plotDone || !plotDone.then) plotDone = Promise.resolve();
45748
45749 return plotDone.then(function() {
45750 gd.emit('plotly_restyle', specs.eventData);
45751 return gd;
45752 });
45753}
45754
45755// for undo: undefined initial vals must be turned into nulls
45756// so that we unset rather than ignore them
45757function undefinedToNull(val) {
45758 if(val === undefined) return null;
45759 return val;
45760}
45761
45762/**
45763 * Factory function to wrap nestedProperty with GUI edits if necessary
45764 * with GUI edits we add an optional prefix to the nestedProperty constructor
45765 * to prepend to the attribute string in the preGUI store.
45766 */
45767function makeNP(preGUI, guiEditFlag) {
45768 if(!guiEditFlag) return nestedProperty;
45769
45770 return function(container, attr, prefix) {
45771 var np = nestedProperty(container, attr);
45772 var npSet = np.set;
45773 np.set = function(val) {
45774 var fullAttr = (prefix || '') + attr;
45775 storeCurrent(fullAttr, np.get(), val, preGUI);
45776 npSet(val);
45777 };
45778 return np;
45779 };
45780}
45781
45782function storeCurrent(attr, val, newVal, preGUI) {
45783 if(Array.isArray(val) || Array.isArray(newVal)) {
45784 var arrayVal = Array.isArray(val) ? val : [];
45785 var arrayNew = Array.isArray(newVal) ? newVal : [];
45786 var maxLen = Math.max(arrayVal.length, arrayNew.length);
45787 for(var i = 0; i < maxLen; i++) {
45788 storeCurrent(attr + '[' + i + ']', arrayVal[i], arrayNew[i], preGUI);
45789 }
45790 } else if(Lib.isPlainObject(val) || Lib.isPlainObject(newVal)) {
45791 var objVal = Lib.isPlainObject(val) ? val : {};
45792 var objNew = Lib.isPlainObject(newVal) ? newVal : {};
45793 var objBoth = Lib.extendFlat({}, objVal, objNew);
45794 for(var key in objBoth) {
45795 storeCurrent(attr + '.' + key, objVal[key], objNew[key], preGUI);
45796 }
45797 } else if(preGUI[attr] === undefined) {
45798 preGUI[attr] = undefinedToNull(val);
45799 }
45800}
45801
45802/**
45803 * storeDirectGUIEdit: for routines that skip restyle/relayout and mock it
45804 * by emitting a plotly_restyle or plotly_relayout event, this routine
45805 * keeps track of the initial state in _preGUI for use by uirevision
45806 * Does *not* apply these changes to data/layout - that's the responsibility
45807 * of the calling routine.
45808 *
45809 * @param {object} container: the input attributes container (eg `layout` or a `trace`)
45810 * @param {object} preGUI: where original values should be stored, either
45811 * `layout._preGUI` or `layout._tracePreGUI[uid]`
45812 * @param {object} edits: the {attr: val} object as normally passed to `relayout` etc
45813 */
45814function _storeDirectGUIEdit(container, preGUI, edits) {
45815 for(var attr in edits) {
45816 var np = nestedProperty(container, attr);
45817 storeCurrent(attr, np.get(), edits[attr], preGUI);
45818 }
45819}
45820
45821function _restyle(gd, aobj, traces) {
45822 var fullLayout = gd._fullLayout;
45823 var fullData = gd._fullData;
45824 var data = gd.data;
45825 var guiEditFlag = fullLayout._guiEditing;
45826 var layoutNP = makeNP(fullLayout._preGUI, guiEditFlag);
45827 var eventData = Lib.extendDeepAll({}, aobj);
45828 var i;
45829
45830 cleanDeprecatedAttributeKeys(aobj);
45831
45832 // initialize flags
45833 var flags = editTypes.traceFlags();
45834
45835 // copies of the change (and previous values of anything affected)
45836 // for the undo / redo queue
45837 var redoit = {};
45838 var undoit = {};
45839 var axlist;
45840
45841 // make a new empty vals array for undoit
45842 function a0() { return traces.map(function() { return undefined; }); }
45843
45844 // for autoranging multiple axes
45845 function addToAxlist(axid) {
45846 var axName = Axes.id2name(axid);
45847 if(axlist.indexOf(axName) === -1) axlist.push(axName);
45848 }
45849
45850 function autorangeAttr(axName) { return 'LAYOUT' + axName + '.autorange'; }
45851
45852 function rangeAttr(axName) { return 'LAYOUT' + axName + '.range'; }
45853
45854 function getFullTrace(traceIndex) {
45855 // usually fullData maps 1:1 onto data, but with groupby transforms
45856 // the fullData index can be greater. Take the *first* matching trace.
45857 for(var j = traceIndex; j < fullData.length; j++) {
45858 if(fullData[j]._input === data[traceIndex]) return fullData[j];
45859 }
45860 // should never get here - and if we *do* it should cause an error
45861 // later on undefined fullTrace is passed to nestedProperty.
45862 }
45863
45864 // for attrs that interact (like scales & autoscales), save the
45865 // old vals before making the change
45866 // val=undefined will not set a value, just record what the value was.
45867 // val=null will delete the attribute
45868 // attr can be an array to set several at once (all to the same val)
45869 function doextra(attr, val, i) {
45870 if(Array.isArray(attr)) {
45871 attr.forEach(function(a) { doextra(a, val, i); });
45872 return;
45873 }
45874 // quit if explicitly setting this elsewhere
45875 if(attr in aobj || helpers.hasParent(aobj, attr)) return;
45876
45877 var extraparam;
45878 if(attr.substr(0, 6) === 'LAYOUT') {
45879 extraparam = layoutNP(gd.layout, attr.replace('LAYOUT', ''));
45880 } else {
45881 var tracei = traces[i];
45882 var preGUI = fullLayout._tracePreGUI[getFullTrace(tracei)._fullInput.uid];
45883 extraparam = makeNP(preGUI, guiEditFlag)(data[tracei], attr);
45884 }
45885
45886 if(!(attr in undoit)) {
45887 undoit[attr] = a0();
45888 }
45889 if(undoit[attr][i] === undefined) {
45890 undoit[attr][i] = undefinedToNull(extraparam.get());
45891 }
45892 if(val !== undefined) {
45893 extraparam.set(val);
45894 }
45895 }
45896
45897 function allBins(binAttr) {
45898 return function(j) {
45899 return fullData[j][binAttr];
45900 };
45901 }
45902
45903 function arrayBins(binAttr) {
45904 return function(vij, j) {
45905 return vij === false ? fullData[traces[j]][binAttr] : null;
45906 };
45907 }
45908
45909 // now make the changes to gd.data (and occasionally gd.layout)
45910 // and figure out what kind of graphics update we need to do
45911 for(var ai in aobj) {
45912 if(helpers.hasParent(aobj, ai)) {
45913 throw new Error('cannot set ' + ai + ' and a parent attribute simultaneously');
45914 }
45915
45916 var vi = aobj[ai];
45917 var cont;
45918 var contFull;
45919 var param;
45920 var oldVal;
45921 var newVal;
45922 var valObject;
45923
45924 // Backward compatibility shim for turning histogram autobin on,
45925 // or freezing previous autobinned values.
45926 // Replace obsolete `autobin(x|y): true` with `(x|y)bins: null`
45927 // and `autobin(x|y): false` with the `(x|y)bins` in `fullData`
45928 if(ai === 'autobinx' || ai === 'autobiny') {
45929 ai = ai.charAt(ai.length - 1) + 'bins';
45930 if(Array.isArray(vi)) vi = vi.map(arrayBins(ai));
45931 else if(vi === false) vi = traces.map(allBins(ai));
45932 else vi = null;
45933 }
45934
45935 redoit[ai] = vi;
45936
45937 if(ai.substr(0, 6) === 'LAYOUT') {
45938 param = layoutNP(gd.layout, ai.replace('LAYOUT', ''));
45939 undoit[ai] = [undefinedToNull(param.get())];
45940 // since we're allowing val to be an array, allow it here too,
45941 // even though that's meaningless
45942 param.set(Array.isArray(vi) ? vi[0] : vi);
45943 // ironically, the layout attrs in restyle only require replot,
45944 // not relayout
45945 flags.calc = true;
45946 continue;
45947 }
45948
45949 // set attribute in gd.data
45950 undoit[ai] = a0();
45951 for(i = 0; i < traces.length; i++) {
45952 cont = data[traces[i]];
45953 contFull = getFullTrace(traces[i]);
45954 var preGUI = fullLayout._tracePreGUI[contFull._fullInput.uid];
45955 param = makeNP(preGUI, guiEditFlag)(cont, ai);
45956 oldVal = param.get();
45957 newVal = Array.isArray(vi) ? vi[i % vi.length] : vi;
45958
45959 if(newVal === undefined) continue;
45960
45961 var finalPart = param.parts[param.parts.length - 1];
45962 var prefix = ai.substr(0, ai.length - finalPart.length - 1);
45963 var prefixDot = prefix ? prefix + '.' : '';
45964 var innerContFull = prefix ?
45965 nestedProperty(contFull, prefix).get() : contFull;
45966
45967 valObject = PlotSchema.getTraceValObject(contFull, param.parts);
45968
45969 if(valObject && valObject.impliedEdits && newVal !== null) {
45970 for(var impliedKey in valObject.impliedEdits) {
45971 doextra(Lib.relativeAttr(ai, impliedKey), valObject.impliedEdits[impliedKey], i);
45972 }
45973 } else if((finalPart === 'thicknessmode' || finalPart === 'lenmode') &&
45974 oldVal !== newVal &&
45975 (newVal === 'fraction' || newVal === 'pixels') &&
45976 innerContFull
45977 ) {
45978 // changing colorbar size modes,
45979 // make the resulting size not change
45980 // note that colorbar fractional sizing is based on the
45981 // original plot size, before anything (like a colorbar)
45982 // increases the margins
45983
45984 var gs = fullLayout._size;
45985 var orient = innerContFull.orient;
45986 var topOrBottom = (orient === 'top') || (orient === 'bottom');
45987 if(finalPart === 'thicknessmode') {
45988 var thicknorm = topOrBottom ? gs.h : gs.w;
45989 doextra(prefixDot + 'thickness', innerContFull.thickness *
45990 (newVal === 'fraction' ? 1 / thicknorm : thicknorm), i);
45991 } else {
45992 var lennorm = topOrBottom ? gs.w : gs.h;
45993 doextra(prefixDot + 'len', innerContFull.len *
45994 (newVal === 'fraction' ? 1 / lennorm : lennorm), i);
45995 }
45996 } else if(ai === 'type' && (
45997 (newVal === 'pie') !== (oldVal === 'pie') ||
45998 (newVal === 'funnelarea') !== (oldVal === 'funnelarea')
45999 )) {
46000 var labelsTo = 'x';
46001 var valuesTo = 'y';
46002 if((newVal === 'bar' || oldVal === 'bar') && cont.orientation === 'h') {
46003 labelsTo = 'y';
46004 valuesTo = 'x';
46005 }
46006 Lib.swapAttrs(cont, ['?', '?src'], 'labels', labelsTo);
46007 Lib.swapAttrs(cont, ['d?', '?0'], 'label', labelsTo);
46008 Lib.swapAttrs(cont, ['?', '?src'], 'values', valuesTo);
46009
46010 if(oldVal === 'pie' || oldVal === 'funnelarea') {
46011 nestedProperty(cont, 'marker.color')
46012 .set(nestedProperty(cont, 'marker.colors').get());
46013
46014 // super kludgy - but if all pies are gone we won't remove them otherwise
46015 fullLayout._pielayer.selectAll('g.trace').remove();
46016 } else if(Registry.traceIs(cont, 'cartesian')) {
46017 nestedProperty(cont, 'marker.colors')
46018 .set(nestedProperty(cont, 'marker.color').get());
46019 }
46020 }
46021
46022 undoit[ai][i] = undefinedToNull(oldVal);
46023 // set the new value - if val is an array, it's one el per trace
46024 // first check for attributes that get more complex alterations
46025 var swapAttrs = [
46026 'swapxy', 'swapxyaxes', 'orientation', 'orientationaxes'
46027 ];
46028 if(swapAttrs.indexOf(ai) !== -1) {
46029 // setting an orientation: make sure it's changing
46030 // before we swap everything else
46031 if(ai === 'orientation') {
46032 param.set(newVal);
46033 // obnoxious that we need this level of coupling... but in order to
46034 // properly handle setting orientation to `null` we need to mimic
46035 // the logic inside Bars.supplyDefaults for default orientation
46036 var defaultOrientation = (cont.x && !cont.y) ? 'h' : 'v';
46037 if((param.get() || defaultOrientation) === contFull.orientation) {
46038 continue;
46039 }
46040 } else if(ai === 'orientationaxes') {
46041 // orientationaxes has no value,
46042 // it flips everything and the axes
46043
46044 cont.orientation =
46045 {v: 'h', h: 'v'}[contFull.orientation];
46046 }
46047 helpers.swapXYData(cont);
46048 flags.calc = flags.clearAxisTypes = true;
46049 } else if(Plots.dataArrayContainers.indexOf(param.parts[0]) !== -1) {
46050 // TODO: use manageArrays.applyContainerArrayChanges here too
46051 helpers.manageArrayContainers(param, newVal, undoit);
46052 flags.calc = true;
46053 } else {
46054 if(valObject) {
46055 // must redo calcdata when restyling array values of arrayOk attributes
46056 // ... but no need to this for regl-based traces
46057 if(valObject.arrayOk &&
46058 !Registry.traceIs(contFull, 'regl') &&
46059 (Lib.isArrayOrTypedArray(newVal) || Lib.isArrayOrTypedArray(oldVal))
46060 ) {
46061 flags.calc = true;
46062 } else editTypes.update(flags, valObject);
46063 } else {
46064 /*
46065 * if we couldn't find valObject, assume a full recalc.
46066 * This can happen if you're changing type and making
46067 * some other edits too, so the modules we're
46068 * looking at don't have these attributes in them.
46069 */
46070 flags.calc = true;
46071 }
46072
46073 // all the other ones, just modify that one attribute
46074 param.set(newVal);
46075 }
46076 }
46077
46078 // swap the data attributes of the relevant x and y axes?
46079 if(['swapxyaxes', 'orientationaxes'].indexOf(ai) !== -1) {
46080 Axes.swap(gd, traces);
46081 }
46082
46083 // swap hovermode if set to "compare x/y data"
46084 if(ai === 'orientationaxes') {
46085 var hovermode = nestedProperty(gd.layout, 'hovermode');
46086 if(hovermode.get() === 'x') {
46087 hovermode.set('y');
46088 } else if(hovermode.get() === 'y') {
46089 hovermode.set('x');
46090 } else if(hovermode.get() === 'x unified') {
46091 hovermode.set('y unified');
46092 } else if(hovermode.get() === 'y unified') {
46093 hovermode.set('x unified');
46094 }
46095 }
46096
46097 // Major enough changes deserve autoscale and
46098 // non-reversed axes so people don't get confused
46099 //
46100 // Note: autobin (or its new analog bin clearing) is not included here
46101 // since we're not pushing bins back to gd.data, so if we have bin
46102 // info it was explicitly provided by the user.
46103 if(['orientation', 'type'].indexOf(ai) !== -1) {
46104 axlist = [];
46105 for(i = 0; i < traces.length; i++) {
46106 var trace = data[traces[i]];
46107
46108 if(Registry.traceIs(trace, 'cartesian')) {
46109 addToAxlist(trace.xaxis || 'x');
46110 addToAxlist(trace.yaxis || 'y');
46111 }
46112 }
46113
46114 doextra(axlist.map(autorangeAttr), true, 0);
46115 doextra(axlist.map(rangeAttr), [0, 1], 0);
46116 }
46117 }
46118
46119 if(flags.calc || flags.plot) {
46120 flags.fullReplot = true;
46121 }
46122
46123 return {
46124 flags: flags,
46125 undoit: undoit,
46126 redoit: redoit,
46127 traces: traces,
46128 eventData: Lib.extendDeepNoArrays([], [eventData, traces])
46129 };
46130}
46131
46132/**
46133 * Converts deprecated attribute keys to
46134 * the current API to ensure backwards compatibility.
46135 *
46136 * This is needed for the update mechanism to determine which
46137 * subroutines to run based on the actual attribute
46138 * definitions (that don't include the deprecated ones).
46139 *
46140 * E.g. Maps {'xaxis.title': 'A chart'} to {'xaxis.title.text': 'A chart'}
46141 * and {titlefont: {...}} to {'title.font': {...}}.
46142 *
46143 * @param aobj
46144 */
46145function cleanDeprecatedAttributeKeys(aobj) {
46146 var oldAxisTitleRegex = Lib.counterRegex('axis', '\.title', false, false);
46147 var colorbarRegex = /colorbar\.title$/;
46148 var keys = Object.keys(aobj);
46149 var i, key, value;
46150
46151 for(i = 0; i < keys.length; i++) {
46152 key = keys[i];
46153 value = aobj[key];
46154
46155 if((key === 'title' || oldAxisTitleRegex.test(key) || colorbarRegex.test(key)) &&
46156 (typeof value === 'string' || typeof value === 'number')) {
46157 replace(key, key.replace('title', 'title.text'));
46158 } else if(key.indexOf('titlefont') > -1) {
46159 replace(key, key.replace('titlefont', 'title.font'));
46160 } else if(key.indexOf('titleposition') > -1) {
46161 replace(key, key.replace('titleposition', 'title.position'));
46162 } else if(key.indexOf('titleside') > -1) {
46163 replace(key, key.replace('titleside', 'title.side'));
46164 } else if(key.indexOf('titleoffset') > -1) {
46165 replace(key, key.replace('titleoffset', 'title.offset'));
46166 }
46167 }
46168
46169 function replace(oldAttrStr, newAttrStr) {
46170 aobj[newAttrStr] = aobj[oldAttrStr];
46171 delete aobj[oldAttrStr];
46172 }
46173}
46174
46175/**
46176 * relayout: update layout attributes of an existing plot
46177 *
46178 * Can be called two ways:
46179 *
46180 * Signature 1:
46181 * @param {String | HTMLDivElement} gd
46182 * the id or dom element of the graph container div
46183 * @param {String} astr
46184 * attribute string (like `'xaxis.range[0]'`) to update
46185 * @param {*} val
46186 * value to give this attribute
46187 *
46188 * Signature 2:
46189 * @param {String | HTMLDivElement} gd
46190 * (as in signature 1)
46191 * @param {Object} aobj
46192 * attribute object `{astr1: val1, astr2: val2 ...}`
46193 * allows setting multiple attributes simultaneously
46194 */
46195function relayout(gd, astr, val) {
46196 gd = Lib.getGraphDiv(gd);
46197 helpers.clearPromiseQueue(gd);
46198
46199 if(gd.framework && gd.framework.isPolar) {
46200 return Promise.resolve(gd);
46201 }
46202
46203 var aobj = {};
46204 if(typeof astr === 'string') {
46205 aobj[astr] = val;
46206 } else if(Lib.isPlainObject(astr)) {
46207 aobj = Lib.extendFlat({}, astr);
46208 } else {
46209 Lib.warn('Relayout fail.', astr, val);
46210 return Promise.reject();
46211 }
46212
46213 if(Object.keys(aobj).length) gd.changed = true;
46214
46215 var specs = _relayout(gd, aobj);
46216 var flags = specs.flags;
46217
46218 // clear calcdata if required
46219 if(flags.calc) gd.calcdata = undefined;
46220
46221 // fill in redraw sequence
46222
46223 // even if we don't have anything left in aobj,
46224 // something may have happened within relayout that we
46225 // need to wait for
46226 var seq = [Plots.previousPromises];
46227
46228 if(flags.layoutReplot) {
46229 seq.push(subroutines.layoutReplot);
46230 } else if(Object.keys(aobj).length) {
46231 axRangeSupplyDefaultsByPass(gd, flags, specs) || Plots.supplyDefaults(gd);
46232
46233 if(flags.legend) seq.push(subroutines.doLegend);
46234 if(flags.layoutstyle) seq.push(subroutines.layoutStyles);
46235 if(flags.axrange) addAxRangeSequence(seq, specs.rangesAltered);
46236 if(flags.ticks) seq.push(subroutines.doTicksRelayout);
46237 if(flags.modebar) seq.push(subroutines.doModeBar);
46238 if(flags.camera) seq.push(subroutines.doCamera);
46239 if(flags.colorbars) seq.push(subroutines.doColorBars);
46240
46241 seq.push(emitAfterPlot);
46242 }
46243
46244 seq.push(Plots.rehover, Plots.redrag);
46245
46246 Queue.add(gd,
46247 relayout, [gd, specs.undoit],
46248 relayout, [gd, specs.redoit]
46249 );
46250
46251 var plotDone = Lib.syncOrAsync(seq, gd);
46252 if(!plotDone || !plotDone.then) plotDone = Promise.resolve(gd);
46253
46254 return plotDone.then(function() {
46255 gd.emit('plotly_relayout', specs.eventData);
46256 return gd;
46257 });
46258}
46259
46260// Optimization mostly for large splom traces where
46261// Plots.supplyDefaults can take > 100ms
46262function axRangeSupplyDefaultsByPass(gd, flags, specs) {
46263 var fullLayout = gd._fullLayout;
46264
46265 if(!flags.axrange) return false;
46266
46267 for(var k in flags) {
46268 if(k !== 'axrange' && flags[k]) return false;
46269 }
46270
46271 for(var axId in specs.rangesAltered) {
46272 var axName = Axes.id2name(axId);
46273 var axIn = gd.layout[axName];
46274 var axOut = fullLayout[axName];
46275 axOut.autorange = axIn.autorange;
46276 axOut.range = axIn.range.slice();
46277 axOut.cleanRange();
46278
46279 if(axOut._matchGroup) {
46280 for(var axId2 in axOut._matchGroup) {
46281 if(axId2 !== axId) {
46282 var ax2 = fullLayout[Axes.id2name(axId2)];
46283 ax2.autorange = axOut.autorange;
46284 ax2.range = axOut.range.slice();
46285 ax2._input.range = axOut.range.slice();
46286 }
46287 }
46288 }
46289 }
46290
46291 return true;
46292}
46293
46294function addAxRangeSequence(seq, rangesAltered) {
46295 // N.B. leave as sequence of subroutines (for now) instead of
46296 // subroutine of its own so that finalDraw always gets
46297 // executed after drawData
46298 var drawAxes = rangesAltered ?
46299 function(gd) {
46300 var axIds = [];
46301 var skipTitle = true;
46302
46303 for(var id in rangesAltered) {
46304 var ax = Axes.getFromId(gd, id);
46305 axIds.push(id);
46306
46307 if(ax._matchGroup) {
46308 for(var id2 in ax._matchGroup) {
46309 if(!rangesAltered[id2]) {
46310 axIds.push(id2);
46311 }
46312 }
46313 }
46314
46315 if(ax.automargin) skipTitle = false;
46316 }
46317
46318 return Axes.draw(gd, axIds, {skipTitle: skipTitle});
46319 } :
46320 function(gd) {
46321 return Axes.draw(gd, 'redraw');
46322 };
46323
46324 seq.push(
46325 clearSelect,
46326 subroutines.doAutoRangeAndConstraints,
46327 drawAxes,
46328 subroutines.drawData,
46329 subroutines.finalDraw
46330 );
46331}
46332
46333var AX_RANGE_RE = /^[xyz]axis[0-9]*\.range(\[[0|1]\])?$/;
46334var AX_AUTORANGE_RE = /^[xyz]axis[0-9]*\.autorange$/;
46335var AX_DOMAIN_RE = /^[xyz]axis[0-9]*\.domain(\[[0|1]\])?$/;
46336
46337function _relayout(gd, aobj) {
46338 var layout = gd.layout;
46339 var fullLayout = gd._fullLayout;
46340 var guiEditFlag = fullLayout._guiEditing;
46341 var layoutNP = makeNP(fullLayout._preGUI, guiEditFlag);
46342 var keys = Object.keys(aobj);
46343 var axes = Axes.list(gd);
46344 var eventData = Lib.extendDeepAll({}, aobj);
46345 var arrayEdits = {};
46346
46347 var arrayStr, i, j;
46348
46349 cleanDeprecatedAttributeKeys(aobj);
46350 keys = Object.keys(aobj);
46351
46352 // look for 'allaxes', split out into all axes
46353 // in case of 3D the axis are nested within a scene which is held in _id
46354 for(i = 0; i < keys.length; i++) {
46355 if(keys[i].indexOf('allaxes') === 0) {
46356 for(j = 0; j < axes.length; j++) {
46357 var scene = axes[j]._id.substr(1);
46358 var axisAttr = (scene.indexOf('scene') !== -1) ? (scene + '.') : '';
46359 var newkey = keys[i].replace('allaxes', axisAttr + axes[j]._name);
46360
46361 if(!aobj[newkey]) aobj[newkey] = aobj[keys[i]];
46362 }
46363
46364 delete aobj[keys[i]];
46365 }
46366 }
46367
46368 // initialize flags
46369 var flags = editTypes.layoutFlags();
46370
46371 // copies of the change (and previous values of anything affected)
46372 // for the undo / redo queue
46373 var redoit = {};
46374 var undoit = {};
46375
46376 // for attrs that interact (like scales & autoscales), save the
46377 // old vals before making the change
46378 // val=undefined will not set a value, just record what the value was.
46379 // attr can be an array to set several at once (all to the same val)
46380 function doextra(attr, val) {
46381 if(Array.isArray(attr)) {
46382 attr.forEach(function(a) { doextra(a, val); });
46383 return;
46384 }
46385
46386 // if we have another value for this attribute (explicitly or
46387 // via a parent) do not override with this auto-generated extra
46388 if(attr in aobj || helpers.hasParent(aobj, attr)) return;
46389
46390 var p = layoutNP(layout, attr);
46391 if(!(attr in undoit)) {
46392 undoit[attr] = undefinedToNull(p.get());
46393 }
46394 if(val !== undefined) p.set(val);
46395 }
46396
46397 // for constraint enforcement: keep track of all axes (as {id: name})
46398 // we're editing the (auto)range of, so we can tell the others constrained
46399 // to scale with them that it's OK for them to shrink
46400 var rangesAltered = {};
46401 var axId;
46402
46403 function recordAlteredAxis(pleafPlus) {
46404 var axId = Axes.name2id(pleafPlus.split('.')[0]);
46405 rangesAltered[axId] = 1;
46406 return axId;
46407 }
46408
46409 // alter gd.layout
46410 for(var ai in aobj) {
46411 if(helpers.hasParent(aobj, ai)) {
46412 throw new Error('cannot set ' + ai + ' and a parent attribute simultaneously');
46413 }
46414
46415 var p = layoutNP(layout, ai);
46416 var vi = aobj[ai];
46417 var plen = p.parts.length;
46418 // p.parts may end with an index integer if the property is an array
46419 var pend = plen - 1;
46420 while(pend > 0 && typeof p.parts[pend] !== 'string') pend--;
46421 // last property in chain (leaf node)
46422 var pleaf = p.parts[pend];
46423 // leaf plus immediate parent
46424 var pleafPlus = p.parts[pend - 1] + '.' + pleaf;
46425 // trunk nodes (everything except the leaf)
46426 var ptrunk = p.parts.slice(0, pend).join('.');
46427 var parentIn = nestedProperty(gd.layout, ptrunk).get();
46428 var parentFull = nestedProperty(fullLayout, ptrunk).get();
46429 var vOld = p.get();
46430
46431 if(vi === undefined) continue;
46432
46433 redoit[ai] = vi;
46434
46435 // axis reverse is special - it is its own inverse
46436 // op and has no flag.
46437 undoit[ai] = (pleaf === 'reverse') ? vi : undefinedToNull(vOld);
46438
46439 var valObject = PlotSchema.getLayoutValObject(fullLayout, p.parts);
46440
46441 if(valObject && valObject.impliedEdits && vi !== null) {
46442 for(var impliedKey in valObject.impliedEdits) {
46443 doextra(Lib.relativeAttr(ai, impliedKey), valObject.impliedEdits[impliedKey]);
46444 }
46445 }
46446
46447 // Setting width or height to null must reset the graph's width / height
46448 // back to its initial value as computed during the first pass in Plots.plotAutoSize.
46449 //
46450 // To do so, we must manually set them back here using the _initialAutoSize cache.
46451 // can't use impliedEdits for this because behavior depends on vi
46452 if(['width', 'height'].indexOf(ai) !== -1) {
46453 if(vi) {
46454 doextra('autosize', null);
46455 // currently we don't support autosize one dim only - so
46456 // explicitly set the other one. Note that doextra will
46457 // ignore this if the same relayout call also provides oppositeAttr
46458 var oppositeAttr = ai === 'height' ? 'width' : 'height';
46459 doextra(oppositeAttr, fullLayout[oppositeAttr]);
46460 } else {
46461 fullLayout[ai] = gd._initialAutoSize[ai];
46462 }
46463 } else if(ai === 'autosize') {
46464 // depends on vi here too, so again can't use impliedEdits
46465 doextra('width', vi ? null : fullLayout.width);
46466 doextra('height', vi ? null : fullLayout.height);
46467 } else if(pleafPlus.match(AX_RANGE_RE)) {
46468 // check autorange vs range
46469
46470 recordAlteredAxis(pleafPlus);
46471 nestedProperty(fullLayout, ptrunk + '._inputRange').set(null);
46472 } else if(pleafPlus.match(AX_AUTORANGE_RE)) {
46473 recordAlteredAxis(pleafPlus);
46474 nestedProperty(fullLayout, ptrunk + '._inputRange').set(null);
46475 var axFull = nestedProperty(fullLayout, ptrunk).get();
46476 if(axFull._inputDomain) {
46477 // if we're autoranging and this axis has a constrained domain,
46478 // reset it so we don't get locked into a shrunken size
46479 axFull._input.domain = axFull._inputDomain.slice();
46480 }
46481 } else if(pleafPlus.match(AX_DOMAIN_RE)) {
46482 nestedProperty(fullLayout, ptrunk + '._inputDomain').set(null);
46483 }
46484
46485 // toggling axis type between log and linear: we need to convert
46486 // positions for components that are still using linearized values,
46487 // not data values like newer components.
46488 // previously we did this for log <-> not-log, but now only do it
46489 // for log <-> linear
46490 if(pleaf === 'type') {
46491 var ax = parentIn;
46492 var toLog = parentFull.type === 'linear' && vi === 'log';
46493 var fromLog = parentFull.type === 'log' && vi === 'linear';
46494
46495 if(toLog || fromLog) {
46496 if(!ax || !ax.range) {
46497 // 2D never gets here, but 3D does
46498 // I don't think this is needed, but left here in case there
46499 // are edge cases I'm not thinking of.
46500 doextra(ptrunk + '.autorange', true);
46501 } else if(!parentFull.autorange) {
46502 // toggling log without autorange: need to also recalculate ranges
46503 // because log axes use linearized values for range endpoints
46504 var r0 = ax.range[0];
46505 var r1 = ax.range[1];
46506 if(toLog) {
46507 // if both limits are negative, autorange
46508 if(r0 <= 0 && r1 <= 0) {
46509 doextra(ptrunk + '.autorange', true);
46510 }
46511 // if one is negative, set it 6 orders below the other.
46512 if(r0 <= 0) r0 = r1 / 1e6;
46513 else if(r1 <= 0) r1 = r0 / 1e6;
46514 // now set the range values as appropriate
46515 doextra(ptrunk + '.range[0]', Math.log(r0) / Math.LN10);
46516 doextra(ptrunk + '.range[1]', Math.log(r1) / Math.LN10);
46517 } else {
46518 doextra(ptrunk + '.range[0]', Math.pow(10, r0));
46519 doextra(ptrunk + '.range[1]', Math.pow(10, r1));
46520 }
46521 } else if(toLog) {
46522 // just make sure the range is positive and in the right
46523 // order, it'll get recalculated later
46524 ax.range = (ax.range[1] > ax.range[0]) ? [1, 2] : [2, 1];
46525 }
46526
46527 // clear polar view initial stash for radial range so that
46528 // value get recomputed in correct units
46529 if(Array.isArray(fullLayout._subplots.polar) &&
46530 fullLayout._subplots.polar.length &&
46531 fullLayout[p.parts[0]] &&
46532 p.parts[1] === 'radialaxis'
46533 ) {
46534 delete fullLayout[p.parts[0]]._subplot.viewInitial['radialaxis.range'];
46535 }
46536
46537 // Annotations and images also need to convert to/from linearized coords
46538 // Shapes do not need this :)
46539 Registry.getComponentMethod('annotations', 'convertCoords')(gd, parentFull, vi, doextra);
46540 Registry.getComponentMethod('images', 'convertCoords')(gd, parentFull, vi, doextra);
46541 } else {
46542 // any other type changes: the range from the previous type
46543 // will not make sense, so autorange it.
46544 doextra(ptrunk + '.autorange', true);
46545 doextra(ptrunk + '.range', null);
46546 }
46547 nestedProperty(fullLayout, ptrunk + '._inputRange').set(null);
46548 } else if(pleaf.match(AX_NAME_PATTERN)) {
46549 var fullProp = nestedProperty(fullLayout, ai).get();
46550 var newType = (vi || {}).type;
46551
46552 // This can potentially cause strange behavior if the autotype is not
46553 // numeric (linear, because we don't auto-log) but the previous type
46554 // was log. That's a very strange edge case though
46555 if(!newType || newType === '-') newType = 'linear';
46556 Registry.getComponentMethod('annotations', 'convertCoords')(gd, fullProp, newType, doextra);
46557 Registry.getComponentMethod('images', 'convertCoords')(gd, fullProp, newType, doextra);
46558 }
46559
46560 // alter gd.layout
46561
46562 // collect array component edits for execution all together
46563 // so we can ensure consistent behavior adding/removing items
46564 // and order-independence for add/remove/edit all together in
46565 // one relayout call
46566 var containerArrayMatch = manageArrays.containerArrayMatch(ai);
46567 if(containerArrayMatch) {
46568 arrayStr = containerArrayMatch.array;
46569 i = containerArrayMatch.index;
46570 var propStr = containerArrayMatch.property;
46571 var updateValObject = valObject || {editType: 'calc'};
46572
46573 if(i !== '' && propStr === '') {
46574 // special handling of undoit if we're adding or removing an element
46575 // ie 'annotations[2]' which can be {...} (add) or null,
46576 // does not work when replacing the entire array
46577 if(manageArrays.isAddVal(vi)) {
46578 undoit[ai] = null;
46579 } else if(manageArrays.isRemoveVal(vi)) {
46580 undoit[ai] = (nestedProperty(layout, arrayStr).get() || [])[i];
46581 } else {
46582 Lib.warn('unrecognized full object value', aobj);
46583 }
46584 }
46585 editTypes.update(flags, updateValObject);
46586
46587 // prepare the edits object we'll send to applyContainerArrayChanges
46588 if(!arrayEdits[arrayStr]) arrayEdits[arrayStr] = {};
46589 var objEdits = arrayEdits[arrayStr][i];
46590 if(!objEdits) objEdits = arrayEdits[arrayStr][i] = {};
46591 objEdits[propStr] = vi;
46592
46593 delete aobj[ai];
46594 } else if(pleaf === 'reverse') {
46595 // handle axis reversal explicitly, as there's no 'reverse' attribute
46596
46597 if(parentIn.range) parentIn.range.reverse();
46598 else {
46599 doextra(ptrunk + '.autorange', true);
46600 parentIn.range = [1, 0];
46601 }
46602
46603 if(parentFull.autorange) flags.calc = true;
46604 else flags.plot = true;
46605 } else {
46606 if((fullLayout._has('scatter-like') && fullLayout._has('regl')) &&
46607 (ai === 'dragmode' &&
46608 (vi === 'lasso' || vi === 'select') &&
46609 !(vOld === 'lasso' || vOld === 'select'))
46610 ) {
46611 flags.plot = true;
46612 } else if(fullLayout._has('gl2d')) {
46613 flags.plot = true;
46614 } else if(valObject) editTypes.update(flags, valObject);
46615 else flags.calc = true;
46616
46617 p.set(vi);
46618 }
46619 }
46620
46621 // now we've collected component edits - execute them all together
46622 for(arrayStr in arrayEdits) {
46623 var finished = manageArrays.applyContainerArrayChanges(gd,
46624 layoutNP(layout, arrayStr), arrayEdits[arrayStr], flags, layoutNP);
46625 if(!finished) flags.plot = true;
46626 }
46627
46628 // figure out if we need to recalculate axis constraints
46629 var constraints = fullLayout._axisConstraintGroups || [];
46630 for(axId in rangesAltered) {
46631 for(i = 0; i < constraints.length; i++) {
46632 var group = constraints[i];
46633 if(group[axId]) {
46634 // Always recalc if we're changing constrained ranges.
46635 // Otherwise it's possible to violate the constraints by
46636 // specifying arbitrary ranges for all axes in the group.
46637 // this way some ranges may expand beyond what's specified,
46638 // as they do at first draw, to satisfy the constraints.
46639 flags.calc = true;
46640 for(var groupAxId in group) {
46641 if(!rangesAltered[groupAxId]) {
46642 Axes.getFromId(gd, groupAxId)._constraintShrinkable = true;
46643 }
46644 }
46645 }
46646 }
46647 }
46648
46649 // If the autosize changed or height or width was explicitly specified,
46650 // this triggers a redraw
46651 // TODO: do we really need special aobj.height/width handling here?
46652 // couldn't editType do this?
46653 if(updateAutosize(gd) || aobj.height || aobj.width) flags.plot = true;
46654
46655 if(flags.plot || flags.calc) {
46656 flags.layoutReplot = true;
46657 }
46658
46659 // now all attribute mods are done, as are
46660 // redo and undo so we can save them
46661
46662 return {
46663 flags: flags,
46664 rangesAltered: rangesAltered,
46665 undoit: undoit,
46666 redoit: redoit,
46667 eventData: eventData
46668 };
46669}
46670
46671/*
46672 * updateAutosize: we made a change, does it change the autosize result?
46673 * puts the new size into fullLayout
46674 * returns true if either height or width changed
46675 */
46676function updateAutosize(gd) {
46677 var fullLayout = gd._fullLayout;
46678 var oldWidth = fullLayout.width;
46679 var oldHeight = fullLayout.height;
46680
46681 // calculate autosizing
46682 if(gd.layout.autosize) Plots.plotAutoSize(gd, gd.layout, fullLayout);
46683
46684 return (fullLayout.width !== oldWidth) || (fullLayout.height !== oldHeight);
46685}
46686
46687/**
46688 * update: update trace and layout attributes of an existing plot
46689 *
46690 * @param {String | HTMLDivElement} gd
46691 * the id or DOM element of the graph container div
46692 * @param {Object} traceUpdate
46693 * attribute object `{astr1: val1, astr2: val2 ...}`
46694 * corresponding to updates in the plot's traces
46695 * @param {Object} layoutUpdate
46696 * attribute object `{astr1: val1, astr2: val2 ...}`
46697 * corresponding to updates in the plot's layout
46698 * @param {Number[] | Number} [traces]
46699 * integer or array of integers for the traces to alter (all if omitted)
46700 *
46701 */
46702function update(gd, traceUpdate, layoutUpdate, _traces) {
46703 gd = Lib.getGraphDiv(gd);
46704 helpers.clearPromiseQueue(gd);
46705
46706 if(gd.framework && gd.framework.isPolar) {
46707 return Promise.resolve(gd);
46708 }
46709
46710 if(!Lib.isPlainObject(traceUpdate)) traceUpdate = {};
46711 if(!Lib.isPlainObject(layoutUpdate)) layoutUpdate = {};
46712
46713 if(Object.keys(traceUpdate).length) gd.changed = true;
46714 if(Object.keys(layoutUpdate).length) gd.changed = true;
46715
46716 var traces = helpers.coerceTraceIndices(gd, _traces);
46717
46718 var restyleSpecs = _restyle(gd, Lib.extendFlat({}, traceUpdate), traces);
46719 var restyleFlags = restyleSpecs.flags;
46720
46721 var relayoutSpecs = _relayout(gd, Lib.extendFlat({}, layoutUpdate));
46722 var relayoutFlags = relayoutSpecs.flags;
46723
46724 // clear calcdata and/or axis types if required
46725 if(restyleFlags.calc || relayoutFlags.calc) gd.calcdata = undefined;
46726 if(restyleFlags.clearAxisTypes) helpers.clearAxisTypes(gd, traces, layoutUpdate);
46727
46728 // fill in redraw sequence
46729 var seq = [];
46730
46731 if(relayoutFlags.layoutReplot) {
46732 // N.B. works fine when both
46733 // relayoutFlags.layoutReplot and restyleFlags.fullReplot are true
46734 seq.push(subroutines.layoutReplot);
46735 } else if(restyleFlags.fullReplot) {
46736 seq.push(exports.plot);
46737 } else {
46738 seq.push(Plots.previousPromises);
46739 axRangeSupplyDefaultsByPass(gd, relayoutFlags, relayoutSpecs) || Plots.supplyDefaults(gd);
46740
46741 if(restyleFlags.style) seq.push(subroutines.doTraceStyle);
46742 if(restyleFlags.colorbars || relayoutFlags.colorbars) seq.push(subroutines.doColorBars);
46743 if(relayoutFlags.legend) seq.push(subroutines.doLegend);
46744 if(relayoutFlags.layoutstyle) seq.push(subroutines.layoutStyles);
46745 if(relayoutFlags.axrange) addAxRangeSequence(seq, relayoutSpecs.rangesAltered);
46746 if(relayoutFlags.ticks) seq.push(subroutines.doTicksRelayout);
46747 if(relayoutFlags.modebar) seq.push(subroutines.doModeBar);
46748 if(relayoutFlags.camera) seq.push(subroutines.doCamera);
46749
46750 seq.push(emitAfterPlot);
46751 }
46752
46753 seq.push(Plots.rehover, Plots.redrag);
46754
46755 Queue.add(gd,
46756 update, [gd, restyleSpecs.undoit, relayoutSpecs.undoit, restyleSpecs.traces],
46757 update, [gd, restyleSpecs.redoit, relayoutSpecs.redoit, restyleSpecs.traces]
46758 );
46759
46760 var plotDone = Lib.syncOrAsync(seq, gd);
46761 if(!plotDone || !plotDone.then) plotDone = Promise.resolve(gd);
46762
46763 return plotDone.then(function() {
46764 gd.emit('plotly_update', {
46765 data: restyleSpecs.eventData,
46766 layout: relayoutSpecs.eventData
46767 });
46768
46769 return gd;
46770 });
46771}
46772
46773/*
46774 * internal-use-only restyle/relayout/update variants that record the initial
46775 * values in (fullLayout|fullTrace)._preGUI so changes can be persisted across
46776 * Plotly.react data updates, dependent on uirevision attributes
46777 */
46778function guiEdit(func) {
46779 return function wrappedEdit(gd) {
46780 gd._fullLayout._guiEditing = true;
46781 var p = func.apply(null, arguments);
46782 gd._fullLayout._guiEditing = false;
46783 return p;
46784 };
46785}
46786
46787// For connecting edited layout attributes to uirevision attrs
46788// If no `attr` we use `match[1] + '.uirevision'`
46789// Ordered by most common edits first, to minimize our search time
46790var layoutUIControlPatterns = [
46791 {pattern: /^hiddenlabels/, attr: 'legend.uirevision'},
46792 {pattern: /^((x|y)axis\d*)\.((auto)?range|title\.text)/},
46793
46794 // showspikes and modes include those nested inside scenes
46795 {pattern: /axis\d*\.showspikes$/, attr: 'modebar.uirevision'},
46796 {pattern: /(hover|drag)mode$/, attr: 'modebar.uirevision'},
46797
46798 {pattern: /^(scene\d*)\.camera/},
46799 {pattern: /^(geo\d*)\.(projection|center|fitbounds)/},
46800 {pattern: /^(ternary\d*\.[abc]axis)\.(min|title\.text)$/},
46801 {pattern: /^(polar\d*\.radialaxis)\.((auto)?range|angle|title\.text)/},
46802 {pattern: /^(polar\d*\.angularaxis)\.rotation/},
46803 {pattern: /^(mapbox\d*)\.(center|zoom|bearing|pitch)/},
46804
46805 {pattern: /^legend\.(x|y)$/, attr: 'editrevision'},
46806 {pattern: /^(shapes|annotations)/, attr: 'editrevision'},
46807 {pattern: /^title\.text$/, attr: 'editrevision'}
46808];
46809
46810// same for trace attributes: if `attr` is given it's in layout,
46811// or with no `attr` we use `trace.uirevision`
46812var traceUIControlPatterns = [
46813 {pattern: /^selectedpoints$/, attr: 'selectionrevision'},
46814 // "visible" includes trace.transforms[i].styles[j].value.visible
46815 {pattern: /(^|value\.)visible$/, attr: 'legend.uirevision'},
46816 {pattern: /^dimensions\[\d+\]\.constraintrange/},
46817 {pattern: /^node\.(x|y|groups)/}, // for Sankey nodes
46818 {pattern: /^level$/}, // for Sunburst & Treemap traces
46819
46820 // below this you must be in editable: true mode
46821 // TODO: I still put name and title with `trace.uirevision`
46822 // reasonable or should these be `editrevision`?
46823 // Also applies to axis titles up in the layout section
46824
46825 // "name" also includes transform.styles
46826 {pattern: /(^|value\.)name$/},
46827 // including nested colorbar attributes (ie marker.colorbar)
46828 {pattern: /colorbar\.title\.text$/},
46829 {pattern: /colorbar\.(x|y)$/, attr: 'editrevision'}
46830];
46831
46832function findUIPattern(key, patternSpecs) {
46833 for(var i = 0; i < patternSpecs.length; i++) {
46834 var spec = patternSpecs[i];
46835 var match = key.match(spec.pattern);
46836 if(match) {
46837 return {head: match[1], attr: spec.attr};
46838 }
46839 }
46840}
46841
46842// We're finding the new uirevision before supplyDefaults, so do the
46843// inheritance manually. Note that only `undefined` inherits - other
46844// falsy values are returned.
46845function getNewRev(revAttr, container) {
46846 var newRev = nestedProperty(container, revAttr).get();
46847 if(newRev !== undefined) return newRev;
46848
46849 var parts = revAttr.split('.');
46850 parts.pop();
46851 while(parts.length > 1) {
46852 parts.pop();
46853 newRev = nestedProperty(container, parts.join('.') + '.uirevision').get();
46854 if(newRev !== undefined) return newRev;
46855 }
46856
46857 return container.uirevision;
46858}
46859
46860function getFullTraceIndexFromUid(uid, fullData) {
46861 for(var i = 0; i < fullData.length; i++) {
46862 if(fullData[i]._fullInput.uid === uid) return i;
46863 }
46864 return -1;
46865}
46866
46867function getTraceIndexFromUid(uid, data, tracei) {
46868 for(var i = 0; i < data.length; i++) {
46869 if(data[i].uid === uid) return i;
46870 }
46871 // fall back on trace order, but only if user didn't provide a uid for that trace
46872 return (!data[tracei] || data[tracei].uid) ? -1 : tracei;
46873}
46874
46875function valsMatch(v1, v2) {
46876 var v1IsObj = Lib.isPlainObject(v1);
46877 var v1IsArray = Array.isArray(v1);
46878 if(v1IsObj || v1IsArray) {
46879 return (
46880 (v1IsObj && Lib.isPlainObject(v2)) ||
46881 (v1IsArray && Array.isArray(v2))
46882 ) && JSON.stringify(v1) === JSON.stringify(v2);
46883 }
46884 return v1 === v2;
46885}
46886
46887function applyUIRevisions(data, layout, oldFullData, oldFullLayout) {
46888 var layoutPreGUI = oldFullLayout._preGUI;
46889 var key, revAttr, oldRev, newRev, match, preGUIVal, newNP, newVal;
46890 var bothInheritAutorange = [];
46891 var newRangeAccepted = {};
46892 for(key in layoutPreGUI) {
46893 match = findUIPattern(key, layoutUIControlPatterns);
46894 if(match) {
46895 revAttr = match.attr || (match.head + '.uirevision');
46896 oldRev = nestedProperty(oldFullLayout, revAttr).get();
46897 newRev = oldRev && getNewRev(revAttr, layout);
46898 if(newRev && (newRev === oldRev)) {
46899 preGUIVal = layoutPreGUI[key];
46900 if(preGUIVal === null) preGUIVal = undefined;
46901 newNP = nestedProperty(layout, key);
46902 newVal = newNP.get();
46903 if(valsMatch(newVal, preGUIVal)) {
46904 if(newVal === undefined && key.substr(key.length - 9) === 'autorange') {
46905 bothInheritAutorange.push(key.substr(0, key.length - 10));
46906 }
46907 newNP.set(undefinedToNull(nestedProperty(oldFullLayout, key).get()));
46908 continue;
46909 }
46910 }
46911 } else {
46912 Lib.warn('unrecognized GUI edit: ' + key);
46913 }
46914 // if we got this far, the new value was accepted as the new starting
46915 // point (either because it changed or revision changed)
46916 // so remove it from _preGUI for next time.
46917 delete layoutPreGUI[key];
46918
46919 if(key.substr(key.length - 8, 6) === 'range[') {
46920 newRangeAccepted[key.substr(0, key.length - 9)] = 1;
46921 }
46922 }
46923
46924 // Special logic for `autorange`, since it interacts with `range`:
46925 // If the new figure's matching `range` was kept, and `autorange`
46926 // wasn't supplied explicitly in either the original or the new figure,
46927 // we shouldn't alter that - but we may just have done that, so fix it.
46928 for(var i = 0; i < bothInheritAutorange.length; i++) {
46929 var axAttr = bothInheritAutorange[i];
46930 if(newRangeAccepted[axAttr]) {
46931 var newAx = nestedProperty(layout, axAttr).get();
46932 if(newAx) delete newAx.autorange;
46933 }
46934 }
46935
46936 // Now traces - try to match them up by uid (in case we added/deleted in
46937 // the middle), then fall back on index.
46938 var allTracePreGUI = oldFullLayout._tracePreGUI;
46939 for(var uid in allTracePreGUI) {
46940 var tracePreGUI = allTracePreGUI[uid];
46941 var newTrace = null;
46942 var fullInput;
46943 for(key in tracePreGUI) {
46944 // wait until we know we have preGUI values to look for traces
46945 // but if we don't find both, stop looking at this uid
46946 if(!newTrace) {
46947 var fulli = getFullTraceIndexFromUid(uid, oldFullData);
46948 if(fulli < 0) {
46949 // Somehow we didn't even have this trace in oldFullData...
46950 // I guess this could happen with `deleteTraces` or something
46951 delete allTracePreGUI[uid];
46952 break;
46953 }
46954 var fullTrace = oldFullData[fulli];
46955 fullInput = fullTrace._fullInput;
46956
46957 var newTracei = getTraceIndexFromUid(uid, data, fullInput.index);
46958 if(newTracei < 0) {
46959 // No match in new data
46960 delete allTracePreGUI[uid];
46961 break;
46962 }
46963 newTrace = data[newTracei];
46964 }
46965
46966 match = findUIPattern(key, traceUIControlPatterns);
46967 if(match) {
46968 if(match.attr) {
46969 oldRev = nestedProperty(oldFullLayout, match.attr).get();
46970 newRev = oldRev && getNewRev(match.attr, layout);
46971 } else {
46972 oldRev = fullInput.uirevision;
46973 // inheritance for trace.uirevision is simple, just layout.uirevision
46974 newRev = newTrace.uirevision;
46975 if(newRev === undefined) newRev = layout.uirevision;
46976 }
46977
46978 if(newRev && newRev === oldRev) {
46979 preGUIVal = tracePreGUI[key];
46980 if(preGUIVal === null) preGUIVal = undefined;
46981 newNP = nestedProperty(newTrace, key);
46982 newVal = newNP.get();
46983 if(valsMatch(newVal, preGUIVal)) {
46984 newNP.set(undefinedToNull(nestedProperty(fullInput, key).get()));
46985 continue;
46986 }
46987 }
46988 } else {
46989 Lib.warn('unrecognized GUI edit: ' + key + ' in trace uid ' + uid);
46990 }
46991 delete tracePreGUI[key];
46992 }
46993 }
46994}
46995
46996/**
46997 * Plotly.react:
46998 * A plot/update method that takes the full plot state (same API as plot/newPlot)
46999 * and diffs to determine the minimal update pathway
47000 *
47001 * @param {string id or DOM element} gd
47002 * the id or DOM element of the graph container div
47003 * @param {array of objects} data
47004 * array of traces, containing the data and display information for each trace
47005 * @param {object} layout
47006 * object describing the overall display of the plot,
47007 * all the stuff that doesn't pertain to any individual trace
47008 * @param {object} config
47009 * configuration options (see ./plot_config.js for more info)
47010 *
47011 * OR
47012 *
47013 * @param {string id or DOM element} gd
47014 * the id or DOM element of the graph container div
47015 * @param {object} figure
47016 * object containing `data`, `layout`, `config`, and `frames` members
47017 *
47018 */
47019function react(gd, data, layout, config) {
47020 var frames, plotDone;
47021
47022 function addFrames() { return exports.addFrames(gd, frames); }
47023
47024 gd = Lib.getGraphDiv(gd);
47025 helpers.clearPromiseQueue(gd);
47026
47027 var oldFullData = gd._fullData;
47028 var oldFullLayout = gd._fullLayout;
47029
47030 // you can use this as the initial draw as well as to update
47031 if(!Lib.isPlotDiv(gd) || !oldFullData || !oldFullLayout) {
47032 plotDone = exports.newPlot(gd, data, layout, config);
47033 } else {
47034 if(Lib.isPlainObject(data)) {
47035 var obj = data;
47036 data = obj.data;
47037 layout = obj.layout;
47038 config = obj.config;
47039 frames = obj.frames;
47040 }
47041
47042 var configChanged = false;
47043 // assume that if there's a config at all, we're reacting to it too,
47044 // and completely replace the previous config
47045 if(config) {
47046 var oldConfig = Lib.extendDeep({}, gd._context);
47047 gd._context = undefined;
47048 setPlotContext(gd, config);
47049 configChanged = diffConfig(oldConfig, gd._context);
47050 }
47051
47052 gd.data = data || [];
47053 helpers.cleanData(gd.data);
47054 gd.layout = layout || {};
47055 helpers.cleanLayout(gd.layout);
47056
47057 applyUIRevisions(gd.data, gd.layout, oldFullData, oldFullLayout);
47058
47059 // "true" skips updating calcdata and remapping arrays from calcTransforms,
47060 // which supplyDefaults usually does at the end, but we may need to NOT do
47061 // if the diff (which we haven't determined yet) says we'll recalc
47062 Plots.supplyDefaults(gd, {skipUpdateCalc: true});
47063
47064 var newFullData = gd._fullData;
47065 var newFullLayout = gd._fullLayout;
47066 var immutable = newFullLayout.datarevision === undefined;
47067 var transition = newFullLayout.transition;
47068
47069 var relayoutFlags = diffLayout(gd, oldFullLayout, newFullLayout, immutable, transition);
47070 var newDataRevision = relayoutFlags.newDataRevision;
47071 var restyleFlags = diffData(gd, oldFullData, newFullData, immutable, transition, newDataRevision);
47072
47073 // TODO: how to translate this part of relayout to Plotly.react?
47074 // // Setting width or height to null must reset the graph's width / height
47075 // // back to its initial value as computed during the first pass in Plots.plotAutoSize.
47076 // //
47077 // // To do so, we must manually set them back here using the _initialAutoSize cache.
47078 // if(['width', 'height'].indexOf(ai) !== -1 && vi === null) {
47079 // fullLayout[ai] = gd._initialAutoSize[ai];
47080 // }
47081
47082 if(updateAutosize(gd)) relayoutFlags.layoutReplot = true;
47083
47084 // clear calcdata if required
47085 if(restyleFlags.calc || relayoutFlags.calc) gd.calcdata = undefined;
47086 // otherwise do the calcdata updates and calcTransform array remaps that we skipped earlier
47087 else Plots.supplyDefaultsUpdateCalc(gd.calcdata, newFullData);
47088
47089 // Note: what restyle/relayout use impliedEdits and clearAxisTypes for
47090 // must be handled by the user when using Plotly.react.
47091
47092 // fill in redraw sequence
47093 var seq = [];
47094
47095 if(frames) {
47096 gd._transitionData = {};
47097 Plots.createTransitionData(gd);
47098 seq.push(addFrames);
47099 }
47100
47101 // Transition pathway,
47102 // only used when 'transition' is set by user and
47103 // when at least one animatable attribute has changed,
47104 // N.B. config changed aren't animatable
47105 if(newFullLayout.transition && !configChanged && (restyleFlags.anim || relayoutFlags.anim)) {
47106 Plots.doCalcdata(gd);
47107 subroutines.doAutoRangeAndConstraints(gd);
47108
47109 seq.push(function() {
47110 return Plots.transitionFromReact(gd, restyleFlags, relayoutFlags, oldFullLayout);
47111 });
47112 } else if(restyleFlags.fullReplot || relayoutFlags.layoutReplot || configChanged) {
47113 gd._fullLayout._skipDefaults = true;
47114 seq.push(exports.plot);
47115 } else {
47116 for(var componentType in relayoutFlags.arrays) {
47117 var indices = relayoutFlags.arrays[componentType];
47118 if(indices.length) {
47119 var drawOne = Registry.getComponentMethod(componentType, 'drawOne');
47120 if(drawOne !== Lib.noop) {
47121 for(var i = 0; i < indices.length; i++) {
47122 drawOne(gd, indices[i]);
47123 }
47124 } else {
47125 var draw = Registry.getComponentMethod(componentType, 'draw');
47126 if(draw === Lib.noop) {
47127 throw new Error('cannot draw components: ' + componentType);
47128 }
47129 draw(gd);
47130 }
47131 }
47132 }
47133
47134 seq.push(Plots.previousPromises);
47135 if(restyleFlags.style) seq.push(subroutines.doTraceStyle);
47136 if(restyleFlags.colorbars || relayoutFlags.colorbars) seq.push(subroutines.doColorBars);
47137 if(relayoutFlags.legend) seq.push(subroutines.doLegend);
47138 if(relayoutFlags.layoutstyle) seq.push(subroutines.layoutStyles);
47139 if(relayoutFlags.axrange) addAxRangeSequence(seq);
47140 if(relayoutFlags.ticks) seq.push(subroutines.doTicksRelayout);
47141 if(relayoutFlags.modebar) seq.push(subroutines.doModeBar);
47142 if(relayoutFlags.camera) seq.push(subroutines.doCamera);
47143 seq.push(emitAfterPlot);
47144 }
47145
47146 seq.push(Plots.rehover, Plots.redrag);
47147
47148 plotDone = Lib.syncOrAsync(seq, gd);
47149 if(!plotDone || !plotDone.then) plotDone = Promise.resolve(gd);
47150 }
47151
47152 return plotDone.then(function() {
47153 gd.emit('plotly_react', {
47154 data: data,
47155 layout: layout
47156 });
47157
47158 return gd;
47159 });
47160}
47161
47162function diffData(gd, oldFullData, newFullData, immutable, transition, newDataRevision) {
47163 var sameTraceLength = oldFullData.length === newFullData.length;
47164
47165 if(!transition && !sameTraceLength) {
47166 return {
47167 fullReplot: true,
47168 calc: true
47169 };
47170 }
47171
47172 var flags = editTypes.traceFlags();
47173 flags.arrays = {};
47174 flags.nChanges = 0;
47175 flags.nChangesAnim = 0;
47176
47177 var i, trace;
47178
47179 function getTraceValObject(parts) {
47180 var out = PlotSchema.getTraceValObject(trace, parts);
47181 if(!trace._module.animatable && out.anim) {
47182 out.anim = false;
47183 }
47184 return out;
47185 }
47186
47187 var diffOpts = {
47188 getValObject: getTraceValObject,
47189 flags: flags,
47190 immutable: immutable,
47191 transition: transition,
47192 newDataRevision: newDataRevision,
47193 gd: gd
47194 };
47195
47196 var seenUIDs = {};
47197
47198 for(i = 0; i < oldFullData.length; i++) {
47199 if(newFullData[i]) {
47200 trace = newFullData[i]._fullInput;
47201 if(Plots.hasMakesDataTransform(trace)) trace = newFullData[i];
47202 if(seenUIDs[trace.uid]) continue;
47203 seenUIDs[trace.uid] = 1;
47204
47205 getDiffFlags(oldFullData[i]._fullInput, trace, [], diffOpts);
47206 }
47207 }
47208
47209 if(flags.calc || flags.plot) {
47210 flags.fullReplot = true;
47211 }
47212
47213 if(transition && flags.nChanges && flags.nChangesAnim) {
47214 flags.anim = (flags.nChanges === flags.nChangesAnim) && sameTraceLength ? 'all' : 'some';
47215 }
47216
47217 return flags;
47218}
47219
47220function diffLayout(gd, oldFullLayout, newFullLayout, immutable, transition) {
47221 var flags = editTypes.layoutFlags();
47222 flags.arrays = {};
47223 flags.rangesAltered = {};
47224 flags.nChanges = 0;
47225 flags.nChangesAnim = 0;
47226
47227 function getLayoutValObject(parts) {
47228 return PlotSchema.getLayoutValObject(newFullLayout, parts);
47229 }
47230
47231 var diffOpts = {
47232 getValObject: getLayoutValObject,
47233 flags: flags,
47234 immutable: immutable,
47235 transition: transition,
47236 gd: gd
47237 };
47238
47239 getDiffFlags(oldFullLayout, newFullLayout, [], diffOpts);
47240
47241 if(flags.plot || flags.calc) {
47242 flags.layoutReplot = true;
47243 }
47244
47245 if(transition && flags.nChanges && flags.nChangesAnim) {
47246 flags.anim = flags.nChanges === flags.nChangesAnim ? 'all' : 'some';
47247 }
47248
47249 return flags;
47250}
47251
47252function getDiffFlags(oldContainer, newContainer, outerparts, opts) {
47253 var valObject, key, astr;
47254
47255 var getValObject = opts.getValObject;
47256 var flags = opts.flags;
47257 var immutable = opts.immutable;
47258 var inArray = opts.inArray;
47259 var arrayIndex = opts.arrayIndex;
47260
47261 function changed() {
47262 var editType = valObject.editType;
47263 if(inArray && editType.indexOf('arraydraw') !== -1) {
47264 Lib.pushUnique(flags.arrays[inArray], arrayIndex);
47265 return;
47266 }
47267 editTypes.update(flags, valObject);
47268
47269 if(editType !== 'none') {
47270 flags.nChanges++;
47271 }
47272
47273 // track animatable changes
47274 if(opts.transition && valObject.anim) {
47275 flags.nChangesAnim++;
47276 }
47277
47278 // track cartesian axes with altered ranges
47279 if(AX_RANGE_RE.test(astr) || AX_AUTORANGE_RE.test(astr)) {
47280 flags.rangesAltered[outerparts[0]] = 1;
47281 }
47282
47283 // clear _inputDomain on cartesian axes with altered domains
47284 if(AX_DOMAIN_RE.test(astr)) {
47285 nestedProperty(newContainer, '_inputDomain').set(null);
47286 }
47287
47288 // track datarevision changes
47289 if(key === 'datarevision') {
47290 flags.newDataRevision = 1;
47291 }
47292 }
47293
47294 function valObjectCanBeDataArray(valObject) {
47295 return valObject.valType === 'data_array' || valObject.arrayOk;
47296 }
47297
47298 for(key in oldContainer) {
47299 // short-circuit based on previous calls or previous keys that already maximized the pathway
47300 if(flags.calc && !opts.transition) return;
47301
47302 var oldVal = oldContainer[key];
47303 var newVal = newContainer[key];
47304 var parts = outerparts.concat(key);
47305 astr = parts.join('.');
47306
47307 if(key.charAt(0) === '_' || typeof oldVal === 'function' || oldVal === newVal) continue;
47308
47309 // FIXME: ax.tick0 and dtick get filled in during plotting (except for geo subplots),
47310 // and unlike other auto values they don't make it back into the input,
47311 // so newContainer won't have them.
47312 if((key === 'tick0' || key === 'dtick') && outerparts[0] !== 'geo') {
47313 var tickMode = newContainer.tickmode;
47314 if(tickMode === 'auto' || tickMode === 'array' || !tickMode) continue;
47315 }
47316 // FIXME: Similarly for axis ranges for 3D
47317 // contourcarpet doesn't HAVE zmin/zmax, they're just auto-added. It needs them.
47318 if(key === 'range' && newContainer.autorange) continue;
47319 if((key === 'zmin' || key === 'zmax') && newContainer.type === 'contourcarpet') continue;
47320
47321 valObject = getValObject(parts);
47322
47323 // in case type changed, we may not even *have* a valObject.
47324 if(!valObject) continue;
47325
47326 if(valObject._compareAsJSON && JSON.stringify(oldVal) === JSON.stringify(newVal)) continue;
47327
47328 var valType = valObject.valType;
47329 var i;
47330
47331 var canBeDataArray = valObjectCanBeDataArray(valObject);
47332 var wasArray = Array.isArray(oldVal);
47333 var nowArray = Array.isArray(newVal);
47334
47335 // hack for traces that modify the data in supplyDefaults, like
47336 // converting 1D to 2D arrays, which will always create new objects
47337 if(wasArray && nowArray) {
47338 var inputKey = '_input_' + key;
47339 var oldValIn = oldContainer[inputKey];
47340 var newValIn = newContainer[inputKey];
47341 if(Array.isArray(oldValIn) && oldValIn === newValIn) continue;
47342 }
47343
47344 if(newVal === undefined) {
47345 if(canBeDataArray && wasArray) flags.calc = true;
47346 else changed();
47347 } else if(valObject._isLinkedToArray) {
47348 var arrayEditIndices = [];
47349 var extraIndices = false;
47350 if(!inArray) flags.arrays[key] = arrayEditIndices;
47351
47352 var minLen = Math.min(oldVal.length, newVal.length);
47353 var maxLen = Math.max(oldVal.length, newVal.length);
47354 if(minLen !== maxLen) {
47355 if(valObject.editType === 'arraydraw') {
47356 extraIndices = true;
47357 } else {
47358 changed();
47359 continue;
47360 }
47361 }
47362
47363 for(i = 0; i < minLen; i++) {
47364 getDiffFlags(oldVal[i], newVal[i], parts.concat(i),
47365 // add array indices, but not if we're already in an array
47366 Lib.extendFlat({inArray: key, arrayIndex: i}, opts));
47367 }
47368
47369 // put this at the end so that we know our collected array indices are sorted
47370 // but the check for length changes happens up front so we can short-circuit
47371 // diffing if appropriate
47372 if(extraIndices) {
47373 for(i = minLen; i < maxLen; i++) {
47374 arrayEditIndices.push(i);
47375 }
47376 }
47377 } else if(!valType && Lib.isPlainObject(oldVal)) {
47378 getDiffFlags(oldVal, newVal, parts, opts);
47379 } else if(canBeDataArray) {
47380 if(wasArray && nowArray) {
47381 // don't try to diff two data arrays. If immutable we know the data changed,
47382 // if not, assume it didn't and let `layout.datarevision` tell us if it did
47383 if(immutable) {
47384 flags.calc = true;
47385 }
47386
47387 // look for animatable attributes when the data changed
47388 if(immutable || opts.newDataRevision) {
47389 changed();
47390 }
47391 } else if(wasArray !== nowArray) {
47392 flags.calc = true;
47393 } else changed();
47394 } else if(wasArray && nowArray) {
47395 // info array, colorscale, 'any' - these are short, just stringify.
47396 // I don't *think* that covers up any real differences post-validation, does it?
47397 // otherwise we need to dive in 1 (info_array) or 2 (colorscale) levels and compare
47398 // all elements.
47399 if(oldVal.length !== newVal.length || String(oldVal) !== String(newVal)) {
47400 changed();
47401 }
47402 } else {
47403 changed();
47404 }
47405 }
47406
47407 for(key in newContainer) {
47408 if(!(key in oldContainer || key.charAt(0) === '_' || typeof newContainer[key] === 'function')) {
47409 valObject = getValObject(outerparts.concat(key));
47410
47411 if(valObjectCanBeDataArray(valObject) && Array.isArray(newContainer[key])) {
47412 flags.calc = true;
47413 return;
47414 } else changed();
47415 }
47416 }
47417}
47418
47419/*
47420 * simple diff for config - for now, just treat all changes as equivalent
47421 */
47422function diffConfig(oldConfig, newConfig) {
47423 var key;
47424
47425 for(key in oldConfig) {
47426 if(key.charAt(0) === '_') continue;
47427 var oldVal = oldConfig[key];
47428 var newVal = newConfig[key];
47429 if(oldVal !== newVal) {
47430 if(Lib.isPlainObject(oldVal) && Lib.isPlainObject(newVal)) {
47431 if(diffConfig(oldVal, newVal)) {
47432 return true;
47433 }
47434 } else if(Array.isArray(oldVal) && Array.isArray(newVal)) {
47435 if(oldVal.length !== newVal.length) {
47436 return true;
47437 }
47438 for(var i = 0; i < oldVal.length; i++) {
47439 if(oldVal[i] !== newVal[i]) {
47440 if(Lib.isPlainObject(oldVal[i]) && Lib.isPlainObject(newVal[i])) {
47441 if(diffConfig(oldVal[i], newVal[i])) {
47442 return true;
47443 }
47444 } else {
47445 return true;
47446 }
47447 }
47448 }
47449 } else {
47450 return true;
47451 }
47452 }
47453 }
47454}
47455
47456/**
47457 * Animate to a frame, sequence of frame, frame group, or frame definition
47458 *
47459 * @param {string id or DOM element} gd
47460 * the id or DOM element of the graph container div
47461 *
47462 * @param {string or object or array of strings or array of objects} frameOrGroupNameOrFrameList
47463 * a single frame, array of frames, or group to which to animate. The intent is
47464 * inferred by the type of the input. Valid inputs are:
47465 *
47466 * - string, e.g. 'groupname': animate all frames of a given `group` in the order
47467 * in which they are defined via `Plotly.addFrames`.
47468 *
47469 * - array of strings, e.g. ['frame1', frame2']: a list of frames by name to which
47470 * to animate in sequence
47471 *
47472 * - object: {data: ...}: a frame definition to which to animate. The frame is not
47473 * and does not need to be added via `Plotly.addFrames`. It may contain any of
47474 * the properties of a frame, including `data`, `layout`, and `traces`. The
47475 * frame is used as provided and does not use the `baseframe` property.
47476 *
47477 * - array of objects, e.g. [{data: ...}, {data: ...}]: a list of frame objects,
47478 * each following the same rules as a single `object`.
47479 *
47480 * @param {object} animationOpts
47481 * configuration for the animation
47482 */
47483function animate(gd, frameOrGroupNameOrFrameList, animationOpts) {
47484 gd = Lib.getGraphDiv(gd);
47485
47486 if(!Lib.isPlotDiv(gd)) {
47487 throw new Error(
47488 'This element is not a Plotly plot: ' + gd + '. It\'s likely that you\'ve failed ' +
47489 'to create a plot before animating it. For more details, see ' +
47490 'https://plotly.com/javascript/animations/'
47491 );
47492 }
47493
47494 var trans = gd._transitionData;
47495
47496 // This is the queue of frames that will be animated as soon as possible. They
47497 // are popped immediately upon the *start* of a transition:
47498 if(!trans._frameQueue) {
47499 trans._frameQueue = [];
47500 }
47501
47502 animationOpts = Plots.supplyAnimationDefaults(animationOpts);
47503 var transitionOpts = animationOpts.transition;
47504 var frameOpts = animationOpts.frame;
47505
47506 // Since frames are popped immediately, an empty queue only means all frames have
47507 // *started* to transition, not that the animation is complete. To solve that,
47508 // track a separate counter that increments at the same time as frames are added
47509 // to the queue, but decrements only when the transition is complete.
47510 if(trans._frameWaitingCnt === undefined) {
47511 trans._frameWaitingCnt = 0;
47512 }
47513
47514 function getTransitionOpts(i) {
47515 if(Array.isArray(transitionOpts)) {
47516 if(i >= transitionOpts.length) {
47517 return transitionOpts[0];
47518 } else {
47519 return transitionOpts[i];
47520 }
47521 } else {
47522 return transitionOpts;
47523 }
47524 }
47525
47526 function getFrameOpts(i) {
47527 if(Array.isArray(frameOpts)) {
47528 if(i >= frameOpts.length) {
47529 return frameOpts[0];
47530 } else {
47531 return frameOpts[i];
47532 }
47533 } else {
47534 return frameOpts;
47535 }
47536 }
47537
47538 // Execute a callback after the wrapper function has been called n times.
47539 // This is used to defer the resolution until a transition has resovled *and*
47540 // the frame has completed. If it's not done this way, then we get a race
47541 // condition in which the animation might resolve before a transition is complete
47542 // or vice versa.
47543 function callbackOnNthTime(cb, n) {
47544 var cnt = 0;
47545 return function() {
47546 if(cb && ++cnt === n) {
47547 return cb();
47548 }
47549 };
47550 }
47551
47552 return new Promise(function(resolve, reject) {
47553 function discardExistingFrames() {
47554 if(trans._frameQueue.length === 0) {
47555 return;
47556 }
47557
47558 while(trans._frameQueue.length) {
47559 var next = trans._frameQueue.pop();
47560 if(next.onInterrupt) {
47561 next.onInterrupt();
47562 }
47563 }
47564
47565 gd.emit('plotly_animationinterrupted', []);
47566 }
47567
47568 function queueFrames(frameList) {
47569 if(frameList.length === 0) return;
47570
47571 for(var i = 0; i < frameList.length; i++) {
47572 var computedFrame;
47573
47574 if(frameList[i].type === 'byname') {
47575 // If it's a named frame, compute it:
47576 computedFrame = Plots.computeFrame(gd, frameList[i].name);
47577 } else {
47578 // Otherwise we must have been given a simple object, so treat
47579 // the input itself as the computed frame.
47580 computedFrame = frameList[i].data;
47581 }
47582
47583 var frameOpts = getFrameOpts(i);
47584 var transitionOpts = getTransitionOpts(i);
47585
47586 // It doesn't make much sense for the transition duration to be greater than
47587 // the frame duration, so limit it:
47588 transitionOpts.duration = Math.min(transitionOpts.duration, frameOpts.duration);
47589
47590 var nextFrame = {
47591 frame: computedFrame,
47592 name: frameList[i].name,
47593 frameOpts: frameOpts,
47594 transitionOpts: transitionOpts,
47595 };
47596 if(i === frameList.length - 1) {
47597 // The last frame in this .animate call stores the promise resolve
47598 // and reject callbacks. This is how we ensure that the animation
47599 // loop (which may exist as a result of a *different* .animate call)
47600 // still resolves or rejecdts this .animate call's promise. once it's
47601 // complete.
47602 nextFrame.onComplete = callbackOnNthTime(resolve, 2);
47603 nextFrame.onInterrupt = reject;
47604 }
47605
47606 trans._frameQueue.push(nextFrame);
47607 }
47608
47609 // Set it as never having transitioned to a frame. This will cause the animation
47610 // loop to immediately transition to the next frame (which, for immediate mode,
47611 // is the first frame in the list since all others would have been discarded
47612 // below)
47613 if(animationOpts.mode === 'immediate') {
47614 trans._lastFrameAt = -Infinity;
47615 }
47616
47617 // Only it's not already running, start a RAF loop. This could be avoided in the
47618 // case that there's only one frame, but it significantly complicated the logic
47619 // and only sped things up by about 5% or so for a lorenz attractor simulation.
47620 // It would be a fine thing to implement, but the benefit of that optimization
47621 // doesn't seem worth the extra complexity.
47622 if(!trans._animationRaf) {
47623 beginAnimationLoop();
47624 }
47625 }
47626
47627 function stopAnimationLoop() {
47628 gd.emit('plotly_animated');
47629
47630 // Be sure to unset also since it's how we know whether a loop is already running:
47631 window.cancelAnimationFrame(trans._animationRaf);
47632 trans._animationRaf = null;
47633 }
47634
47635 function nextFrame() {
47636 if(trans._currentFrame && trans._currentFrame.onComplete) {
47637 // Execute the callback and unset it to ensure it doesn't
47638 // accidentally get called twice
47639 trans._currentFrame.onComplete();
47640 }
47641
47642 var newFrame = trans._currentFrame = trans._frameQueue.shift();
47643
47644 if(newFrame) {
47645 // Since it's sometimes necessary to do deep digging into frame data,
47646 // we'll consider it not 100% impossible for nulls or numbers to sneak through,
47647 // so check when casting the name, just to be absolutely certain:
47648 var stringName = newFrame.name ? newFrame.name.toString() : null;
47649 gd._fullLayout._currentFrame = stringName;
47650
47651 trans._lastFrameAt = Date.now();
47652 trans._timeToNext = newFrame.frameOpts.duration;
47653
47654 // This is simply called and it's left to .transition to decide how to manage
47655 // interrupting current transitions. That means we don't need to worry about
47656 // how it resolves or what happens after this:
47657 Plots.transition(gd,
47658 newFrame.frame.data,
47659 newFrame.frame.layout,
47660 helpers.coerceTraceIndices(gd, newFrame.frame.traces),
47661 newFrame.frameOpts,
47662 newFrame.transitionOpts
47663 ).then(function() {
47664 if(newFrame.onComplete) {
47665 newFrame.onComplete();
47666 }
47667 });
47668
47669 gd.emit('plotly_animatingframe', {
47670 name: stringName,
47671 frame: newFrame.frame,
47672 animation: {
47673 frame: newFrame.frameOpts,
47674 transition: newFrame.transitionOpts,
47675 }
47676 });
47677 } else {
47678 // If there are no more frames, then stop the RAF loop:
47679 stopAnimationLoop();
47680 }
47681 }
47682
47683 function beginAnimationLoop() {
47684 gd.emit('plotly_animating');
47685
47686 // If no timer is running, then set last frame = long ago so that the next
47687 // frame is immediately transitioned:
47688 trans._lastFrameAt = -Infinity;
47689 trans._timeToNext = 0;
47690 trans._runningTransitions = 0;
47691 trans._currentFrame = null;
47692
47693 var doFrame = function() {
47694 // This *must* be requested before nextFrame since nextFrame may decide
47695 // to cancel it if there's nothing more to animated:
47696 trans._animationRaf = window.requestAnimationFrame(doFrame);
47697
47698 // Check if we're ready for a new frame:
47699 if(Date.now() - trans._lastFrameAt > trans._timeToNext) {
47700 nextFrame();
47701 }
47702 };
47703
47704 doFrame();
47705 }
47706
47707 // This is an animate-local counter that helps match up option input list
47708 // items with the particular frame.
47709 var configCounter = 0;
47710 function setTransitionConfig(frame) {
47711 if(Array.isArray(transitionOpts)) {
47712 if(configCounter >= transitionOpts.length) {
47713 frame.transitionOpts = transitionOpts[configCounter];
47714 } else {
47715 frame.transitionOpts = transitionOpts[0];
47716 }
47717 } else {
47718 frame.transitionOpts = transitionOpts;
47719 }
47720 configCounter++;
47721 return frame;
47722 }
47723
47724 // Disambiguate what's sort of frames have been received
47725 var i, frame;
47726 var frameList = [];
47727 var allFrames = frameOrGroupNameOrFrameList === undefined || frameOrGroupNameOrFrameList === null;
47728 var isFrameArray = Array.isArray(frameOrGroupNameOrFrameList);
47729 var isSingleFrame = !allFrames && !isFrameArray && Lib.isPlainObject(frameOrGroupNameOrFrameList);
47730
47731 if(isSingleFrame) {
47732 // In this case, a simple object has been passed to animate.
47733 frameList.push({
47734 type: 'object',
47735 data: setTransitionConfig(Lib.extendFlat({}, frameOrGroupNameOrFrameList))
47736 });
47737 } else if(allFrames || ['string', 'number'].indexOf(typeof frameOrGroupNameOrFrameList) !== -1) {
47738 // In this case, null or undefined has been passed so that we want to
47739 // animate *all* currently defined frames
47740 for(i = 0; i < trans._frames.length; i++) {
47741 frame = trans._frames[i];
47742
47743 if(!frame) continue;
47744
47745 if(allFrames || String(frame.group) === String(frameOrGroupNameOrFrameList)) {
47746 frameList.push({
47747 type: 'byname',
47748 name: String(frame.name),
47749 data: setTransitionConfig({name: frame.name})
47750 });
47751 }
47752 }
47753 } else if(isFrameArray) {
47754 for(i = 0; i < frameOrGroupNameOrFrameList.length; i++) {
47755 var frameOrName = frameOrGroupNameOrFrameList[i];
47756 if(['number', 'string'].indexOf(typeof frameOrName) !== -1) {
47757 frameOrName = String(frameOrName);
47758 // In this case, there's an array and this frame is a string name:
47759 frameList.push({
47760 type: 'byname',
47761 name: frameOrName,
47762 data: setTransitionConfig({name: frameOrName})
47763 });
47764 } else if(Lib.isPlainObject(frameOrName)) {
47765 frameList.push({
47766 type: 'object',
47767 data: setTransitionConfig(Lib.extendFlat({}, frameOrName))
47768 });
47769 }
47770 }
47771 }
47772
47773 // Verify that all of these frames actually exist; return and reject if not:
47774 for(i = 0; i < frameList.length; i++) {
47775 frame = frameList[i];
47776 if(frame.type === 'byname' && !trans._frameHash[frame.data.name]) {
47777 Lib.warn('animate failure: frame not found: "' + frame.data.name + '"');
47778 reject();
47779 return;
47780 }
47781 }
47782
47783 // If the mode is either next or immediate, then all currently queued frames must
47784 // be dumped and the corresponding .animate promises rejected.
47785 if(['next', 'immediate'].indexOf(animationOpts.mode) !== -1) {
47786 discardExistingFrames();
47787 }
47788
47789 if(animationOpts.direction === 'reverse') {
47790 frameList.reverse();
47791 }
47792
47793 var currentFrame = gd._fullLayout._currentFrame;
47794 if(currentFrame && animationOpts.fromcurrent) {
47795 var idx = -1;
47796 for(i = 0; i < frameList.length; i++) {
47797 frame = frameList[i];
47798 if(frame.type === 'byname' && frame.name === currentFrame) {
47799 idx = i;
47800 break;
47801 }
47802 }
47803
47804 if(idx > 0 && idx < frameList.length - 1) {
47805 var filteredFrameList = [];
47806 for(i = 0; i < frameList.length; i++) {
47807 frame = frameList[i];
47808 if(frameList[i].type !== 'byname' || i > idx) {
47809 filteredFrameList.push(frame);
47810 }
47811 }
47812 frameList = filteredFrameList;
47813 }
47814 }
47815
47816 if(frameList.length > 0) {
47817 queueFrames(frameList);
47818 } else {
47819 // This is the case where there were simply no frames. It's a little strange
47820 // since there's not much to do:
47821 gd.emit('plotly_animated');
47822 resolve();
47823 }
47824 });
47825}
47826
47827/**
47828 * Register new frames
47829 *
47830 * @param {string id or DOM element} gd
47831 * the id or DOM element of the graph container div
47832 *
47833 * @param {array of objects} frameList
47834 * list of frame definitions, in which each object includes any of:
47835 * - name: {string} name of frame to add
47836 * - data: {array of objects} trace data
47837 * - layout {object} layout definition
47838 * - traces {array} trace indices
47839 * - baseframe {string} name of frame from which this frame gets defaults
47840 *
47841 * @param {array of integers} indices
47842 * an array of integer indices matching the respective frames in `frameList`. If not
47843 * provided, an index will be provided in serial order. If already used, the frame
47844 * will be overwritten.
47845 */
47846function addFrames(gd, frameList, indices) {
47847 gd = Lib.getGraphDiv(gd);
47848
47849 if(frameList === null || frameList === undefined) {
47850 return Promise.resolve();
47851 }
47852
47853 if(!Lib.isPlotDiv(gd)) {
47854 throw new Error(
47855 'This element is not a Plotly plot: ' + gd + '. It\'s likely that you\'ve failed ' +
47856 'to create a plot before adding frames. For more details, see ' +
47857 'https://plotly.com/javascript/animations/'
47858 );
47859 }
47860
47861 var i, frame, j, idx;
47862 var _frames = gd._transitionData._frames;
47863 var _frameHash = gd._transitionData._frameHash;
47864
47865
47866 if(!Array.isArray(frameList)) {
47867 throw new Error('addFrames failure: frameList must be an Array of frame definitions' + frameList);
47868 }
47869
47870 // Create a sorted list of insertions since we run into lots of problems if these
47871 // aren't in ascending order of index:
47872 //
47873 // Strictly for sorting. Make sure this is guaranteed to never collide with any
47874 // already-exisisting indices:
47875 var bigIndex = _frames.length + frameList.length * 2;
47876
47877 var insertions = [];
47878 var _frameHashLocal = {};
47879 for(i = frameList.length - 1; i >= 0; i--) {
47880 if(!Lib.isPlainObject(frameList[i])) continue;
47881
47882 // The entire logic for checking for this type of name collision can be removed once we migrate to ES6 and
47883 // use a Map instead of an Object instance, as Map keys aren't converted to strings.
47884 var lookupName = frameList[i].name;
47885 var name = (_frameHash[lookupName] || _frameHashLocal[lookupName] || {}).name;
47886 var newName = frameList[i].name;
47887 var collisionPresent = _frameHash[name] || _frameHashLocal[name];
47888
47889 if(name && newName && typeof newName === 'number' && collisionPresent && numericNameWarningCount < numericNameWarningCountLimit) {
47890 numericNameWarningCount++;
47891
47892 Lib.warn('addFrames: overwriting frame "' + (_frameHash[name] || _frameHashLocal[name]).name +
47893 '" with a frame whose name of type "number" also equates to "' +
47894 name + '". This is valid but may potentially lead to unexpected ' +
47895 'behavior since all plotly.js frame names are stored internally ' +
47896 'as strings.');
47897
47898 if(numericNameWarningCount === numericNameWarningCountLimit) {
47899 Lib.warn('addFrames: This API call has yielded too many of these warnings. ' +
47900 'For the rest of this call, further warnings about numeric frame ' +
47901 'names will be suppressed.');
47902 }
47903 }
47904
47905 _frameHashLocal[lookupName] = {name: lookupName};
47906
47907 insertions.push({
47908 frame: Plots.supplyFrameDefaults(frameList[i]),
47909 index: (indices && indices[i] !== undefined && indices[i] !== null) ? indices[i] : bigIndex + i
47910 });
47911 }
47912
47913 // Sort this, taking note that undefined insertions end up at the end:
47914 insertions.sort(function(a, b) {
47915 if(a.index > b.index) return -1;
47916 if(a.index < b.index) return 1;
47917 return 0;
47918 });
47919
47920 var ops = [];
47921 var revops = [];
47922 var frameCount = _frames.length;
47923
47924 for(i = insertions.length - 1; i >= 0; i--) {
47925 frame = insertions[i].frame;
47926
47927 if(typeof frame.name === 'number') {
47928 Lib.warn('Warning: addFrames accepts frames with numeric names, but the numbers are' +
47929 'implicitly cast to strings');
47930 }
47931
47932 if(!frame.name) {
47933 // Repeatedly assign a default name, incrementing the counter each time until
47934 // we get a name that's not in the hashed lookup table:
47935 while(_frameHash[(frame.name = 'frame ' + gd._transitionData._counter++)]);
47936 }
47937
47938 if(_frameHash[frame.name]) {
47939 // If frame is present, overwrite its definition:
47940 for(j = 0; j < _frames.length; j++) {
47941 if((_frames[j] || {}).name === frame.name) break;
47942 }
47943 ops.push({type: 'replace', index: j, value: frame});
47944 revops.unshift({type: 'replace', index: j, value: _frames[j]});
47945 } else {
47946 // Otherwise insert it at the end of the list:
47947 idx = Math.max(0, Math.min(insertions[i].index, frameCount));
47948
47949 ops.push({type: 'insert', index: idx, value: frame});
47950 revops.unshift({type: 'delete', index: idx});
47951 frameCount++;
47952 }
47953 }
47954
47955 var undoFunc = Plots.modifyFrames;
47956 var redoFunc = Plots.modifyFrames;
47957 var undoArgs = [gd, revops];
47958 var redoArgs = [gd, ops];
47959
47960 if(Queue) Queue.add(gd, undoFunc, undoArgs, redoFunc, redoArgs);
47961
47962 return Plots.modifyFrames(gd, ops);
47963}
47964
47965/**
47966 * Delete frame
47967 *
47968 * @param {string id or DOM element} gd
47969 * the id or DOM element of the graph container div
47970 *
47971 * @param {array of integers} frameList
47972 * list of integer indices of frames to be deleted
47973 */
47974function deleteFrames(gd, frameList) {
47975 gd = Lib.getGraphDiv(gd);
47976
47977 if(!Lib.isPlotDiv(gd)) {
47978 throw new Error('This element is not a Plotly plot: ' + gd);
47979 }
47980
47981 var i, idx;
47982 var _frames = gd._transitionData._frames;
47983 var ops = [];
47984 var revops = [];
47985
47986 if(!frameList) {
47987 frameList = [];
47988 for(i = 0; i < _frames.length; i++) {
47989 frameList.push(i);
47990 }
47991 }
47992
47993 frameList = frameList.slice();
47994 frameList.sort();
47995
47996 for(i = frameList.length - 1; i >= 0; i--) {
47997 idx = frameList[i];
47998 ops.push({type: 'delete', index: idx});
47999 revops.unshift({type: 'insert', index: idx, value: _frames[idx]});
48000 }
48001
48002 var undoFunc = Plots.modifyFrames;
48003 var redoFunc = Plots.modifyFrames;
48004 var undoArgs = [gd, revops];
48005 var redoArgs = [gd, ops];
48006
48007 if(Queue) Queue.add(gd, undoFunc, undoArgs, redoFunc, redoArgs);
48008
48009 return Plots.modifyFrames(gd, ops);
48010}
48011
48012/**
48013 * Purge a graph container div back to its initial pre-Plotly.plot state
48014 *
48015 * @param {string id or DOM element} gd
48016 * the id or DOM element of the graph container div
48017 */
48018function purge(gd) {
48019 gd = Lib.getGraphDiv(gd);
48020
48021 var fullLayout = gd._fullLayout || {};
48022 var fullData = gd._fullData || [];
48023
48024 // remove gl contexts
48025 Plots.cleanPlot([], {}, fullData, fullLayout);
48026
48027 // purge properties
48028 Plots.purge(gd);
48029
48030 // purge event emitter methods
48031 Events.purge(gd);
48032
48033 // remove plot container
48034 if(fullLayout._container) fullLayout._container.remove();
48035
48036 // in contrast to Plotly.Plots.purge which does NOT clear _context!
48037 delete gd._context;
48038
48039 return gd;
48040}
48041
48042// -------------------------------------------------------
48043// makePlotFramework: Create the plot container and axes
48044// -------------------------------------------------------
48045function makePlotFramework(gd) {
48046 var gd3 = d3.select(gd);
48047 var fullLayout = gd._fullLayout;
48048
48049 // Plot container
48050 fullLayout._container = gd3.selectAll('.plot-container').data([0]);
48051 fullLayout._container.enter().insert('div', ':first-child')
48052 .classed('plot-container', true)
48053 .classed('plotly', true);
48054
48055 // Make the svg container
48056 fullLayout._paperdiv = fullLayout._container.selectAll('.svg-container').data([0]);
48057 fullLayout._paperdiv.enter().append('div')
48058 .classed('svg-container', true)
48059 .style('position', 'relative');
48060
48061 // Make the graph containers
48062 // start fresh each time we get here, so we know the order comes out
48063 // right, rather than enter/exit which can muck up the order
48064 // TODO: sort out all the ordering so we don't have to
48065 // explicitly delete anything
48066 // FIXME: parcoords reuses this object, not the best pattern
48067 fullLayout._glcontainer = fullLayout._paperdiv.selectAll('.gl-container')
48068 .data([{}]);
48069
48070 fullLayout._glcontainer.enter().append('div')
48071 .classed('gl-container', true);
48072
48073 fullLayout._paperdiv.selectAll('.main-svg').remove();
48074 fullLayout._paperdiv.select('.modebar-container').remove();
48075
48076 fullLayout._paper = fullLayout._paperdiv.insert('svg', ':first-child')
48077 .classed('main-svg', true);
48078
48079 fullLayout._toppaper = fullLayout._paperdiv.append('svg')
48080 .classed('main-svg', true);
48081
48082 fullLayout._modebardiv = fullLayout._paperdiv.append('div');
48083
48084 fullLayout._hoverpaper = fullLayout._paperdiv.append('svg')
48085 .classed('main-svg', true);
48086
48087 if(!fullLayout._uid) {
48088 var otherUids = {};
48089 d3.selectAll('defs').each(function() {
48090 if(this.id) otherUids[this.id.split('-')[1]] = 1;
48091 });
48092 fullLayout._uid = Lib.randstr(otherUids);
48093 }
48094
48095 fullLayout._paperdiv.selectAll('.main-svg')
48096 .attr(xmlnsNamespaces.svgAttrs);
48097
48098 fullLayout._defs = fullLayout._paper.append('defs')
48099 .attr('id', 'defs-' + fullLayout._uid);
48100
48101 fullLayout._clips = fullLayout._defs.append('g')
48102 .classed('clips', true);
48103
48104 fullLayout._topdefs = fullLayout._toppaper.append('defs')
48105 .attr('id', 'topdefs-' + fullLayout._uid);
48106
48107 fullLayout._topclips = fullLayout._topdefs.append('g')
48108 .classed('clips', true);
48109
48110 fullLayout._bgLayer = fullLayout._paper.append('g')
48111 .classed('bglayer', true);
48112
48113 fullLayout._draggers = fullLayout._paper.append('g')
48114 .classed('draglayer', true);
48115
48116 // lower shape/image layer - note that this is behind
48117 // all subplots data/grids but above the backgrounds
48118 // except inset subplots, whose backgrounds are drawn
48119 // inside their own group so that they appear above
48120 // the data for the main subplot
48121 // lower shapes and images which are fully referenced to
48122 // a subplot still get drawn within the subplot's group
48123 // so they will work correctly on insets
48124 var layerBelow = fullLayout._paper.append('g')
48125 .classed('layer-below', true);
48126 fullLayout._imageLowerLayer = layerBelow.append('g')
48127 .classed('imagelayer', true);
48128 fullLayout._shapeLowerLayer = layerBelow.append('g')
48129 .classed('shapelayer', true);
48130
48131 // single cartesian layer for the whole plot
48132 fullLayout._cartesianlayer = fullLayout._paper.append('g').classed('cartesianlayer', true);
48133
48134 // single polar layer for the whole plot
48135 fullLayout._polarlayer = fullLayout._paper.append('g').classed('polarlayer', true);
48136
48137 // single ternary layer for the whole plot
48138 fullLayout._ternarylayer = fullLayout._paper.append('g').classed('ternarylayer', true);
48139
48140 // single geo layer for the whole plot
48141 fullLayout._geolayer = fullLayout._paper.append('g').classed('geolayer', true);
48142
48143 // single funnelarea layer for the whole plot
48144 fullLayout._funnelarealayer = fullLayout._paper.append('g').classed('funnelarealayer', true);
48145
48146 // single pie layer for the whole plot
48147 fullLayout._pielayer = fullLayout._paper.append('g').classed('pielayer', true);
48148
48149 // single treemap layer for the whole plot
48150 fullLayout._treemaplayer = fullLayout._paper.append('g').classed('treemaplayer', true);
48151
48152 // single sunburst layer for the whole plot
48153 fullLayout._sunburstlayer = fullLayout._paper.append('g').classed('sunburstlayer', true);
48154
48155 // single indicator layer for the whole plot
48156 fullLayout._indicatorlayer = fullLayout._toppaper.append('g').classed('indicatorlayer', true);
48157
48158 // fill in image server scrape-svg
48159 fullLayout._glimages = fullLayout._paper.append('g').classed('glimages', true);
48160
48161 // lastly upper shapes, info (legend, annotations) and hover layers go on top
48162 // these are in a different svg element normally, but get collapsed into a single
48163 // svg when exporting (after inserting 3D)
48164 // upper shapes/images are only those drawn above the whole plot, including subplots
48165 var layerAbove = fullLayout._toppaper.append('g')
48166 .classed('layer-above', true);
48167 fullLayout._imageUpperLayer = layerAbove.append('g')
48168 .classed('imagelayer', true);
48169 fullLayout._shapeUpperLayer = layerAbove.append('g')
48170 .classed('shapelayer', true);
48171
48172 fullLayout._infolayer = fullLayout._toppaper.append('g').classed('infolayer', true);
48173 fullLayout._menulayer = fullLayout._toppaper.append('g').classed('menulayer', true);
48174 fullLayout._zoomlayer = fullLayout._toppaper.append('g').classed('zoomlayer', true);
48175 fullLayout._hoverlayer = fullLayout._hoverpaper.append('g').classed('hoverlayer', true);
48176
48177 // Make the modebar container
48178 fullLayout._modebardiv
48179 .classed('modebar-container', true)
48180 .style('position', 'absolute')
48181 .style('top', '0px')
48182 .style('right', '0px');
48183
48184 gd.emit('plotly_framework');
48185}
48186
48187exports.animate = animate;
48188exports.addFrames = addFrames;
48189exports.deleteFrames = deleteFrames;
48190
48191exports.addTraces = addTraces;
48192exports.deleteTraces = deleteTraces;
48193exports.extendTraces = extendTraces;
48194exports.moveTraces = moveTraces;
48195exports.prependTraces = prependTraces;
48196
48197exports.newPlot = newPlot;
48198exports.plot = plot;
48199exports.purge = purge;
48200
48201exports.react = react;
48202exports.redraw = redraw;
48203exports.relayout = relayout;
48204exports.restyle = restyle;
48205
48206exports.setPlotConfig = setPlotConfig;
48207
48208exports.update = update;
48209
48210exports._guiRelayout = guiEdit(relayout);
48211exports._guiRestyle = guiEdit(restyle);
48212exports._guiUpdate = guiEdit(update);
48213
48214exports._storeDirectGUIEdit = _storeDirectGUIEdit;
48215
48216},{"../components/color":52,"../components/drawing":74,"../constants/xmlns_namespaces":159,"../lib":178,"../lib/events":172,"../lib/queue":192,"../lib/svg_text_utils":199,"../plots/cartesian/axes":222,"../plots/cartesian/constants":228,"../plots/cartesian/graph_interact":231,"../plots/cartesian/select":241,"../plots/plots":256,"../plots/polar/legacy":259,"../registry":269,"./edit_types":205,"./helpers":206,"./manage_arrays":208,"./plot_config":210,"./plot_schema":211,"./subroutines":213,"d3":16,"fast-isnumeric":18,"has-hover":20}],210:[function(_dereq_,module,exports){
48217/**
48218* Copyright 2012-2020, Plotly, Inc.
48219* All rights reserved.
48220*
48221* This source code is licensed under the MIT license found in the
48222* LICENSE file in the root directory of this source tree.
48223*/
48224
48225'use strict';
48226
48227/**
48228 * This will be transferred over to gd and overridden by
48229 * config args to Plotly.plot.
48230 *
48231 * The defaults are the appropriate settings for plotly.js,
48232 * so we get the right experience without any config argument.
48233 *
48234 * N.B. the config options are not coerced using Lib.coerce so keys
48235 * like `valType` and `values` are only set for documentation purposes
48236 * at the moment.
48237 */
48238
48239var configAttributes = {
48240 staticPlot: {
48241 valType: 'boolean',
48242 dflt: false,
48243
48244 },
48245
48246 plotlyServerURL: {
48247 valType: 'string',
48248 dflt: '',
48249
48250 },
48251
48252 editable: {
48253 valType: 'boolean',
48254 dflt: false,
48255
48256 },
48257 edits: {
48258 annotationPosition: {
48259 valType: 'boolean',
48260 dflt: false,
48261
48262 },
48263 annotationTail: {
48264 valType: 'boolean',
48265 dflt: false,
48266
48267 },
48268 annotationText: {
48269 valType: 'boolean',
48270 dflt: false,
48271
48272 },
48273 axisTitleText: {
48274 valType: 'boolean',
48275 dflt: false,
48276
48277 },
48278 colorbarPosition: {
48279 valType: 'boolean',
48280 dflt: false,
48281
48282 },
48283 colorbarTitleText: {
48284 valType: 'boolean',
48285 dflt: false,
48286
48287 },
48288 legendPosition: {
48289 valType: 'boolean',
48290 dflt: false,
48291
48292 },
48293 legendText: {
48294 valType: 'boolean',
48295 dflt: false,
48296
48297 },
48298 shapePosition: {
48299 valType: 'boolean',
48300 dflt: false,
48301
48302 },
48303 titleText: {
48304 valType: 'boolean',
48305 dflt: false,
48306
48307 }
48308 },
48309
48310 autosizable: {
48311 valType: 'boolean',
48312 dflt: false,
48313
48314 },
48315 responsive: {
48316 valType: 'boolean',
48317 dflt: false,
48318
48319 },
48320 fillFrame: {
48321 valType: 'boolean',
48322 dflt: false,
48323
48324 },
48325 frameMargins: {
48326 valType: 'number',
48327 dflt: 0,
48328 min: 0,
48329 max: 0.5,
48330
48331 },
48332
48333 scrollZoom: {
48334 valType: 'flaglist',
48335 flags: ['cartesian', 'gl3d', 'geo', 'mapbox'],
48336 extras: [true, false],
48337 dflt: 'gl3d+geo+mapbox',
48338
48339 },
48340 doubleClick: {
48341 valType: 'enumerated',
48342 values: [false, 'reset', 'autosize', 'reset+autosize'],
48343 dflt: 'reset+autosize',
48344
48345 },
48346 doubleClickDelay: {
48347 valType: 'number',
48348 dflt: 300,
48349 min: 0,
48350
48351 },
48352
48353 showAxisDragHandles: {
48354 valType: 'boolean',
48355 dflt: true,
48356
48357 },
48358 showAxisRangeEntryBoxes: {
48359 valType: 'boolean',
48360 dflt: true,
48361
48362 },
48363
48364 showTips: {
48365 valType: 'boolean',
48366 dflt: true,
48367
48368 },
48369
48370 showLink: {
48371 valType: 'boolean',
48372 dflt: false,
48373
48374 },
48375 linkText: {
48376 valType: 'string',
48377 dflt: 'Edit chart',
48378 noBlank: true,
48379
48380 },
48381 sendData: {
48382 valType: 'boolean',
48383 dflt: true,
48384
48385 },
48386 showSources: {
48387 valType: 'any',
48388 dflt: false,
48389
48390 },
48391
48392 displayModeBar: {
48393 valType: 'enumerated',
48394 values: ['hover', true, false],
48395 dflt: 'hover',
48396
48397 },
48398 showSendToCloud: {
48399 valType: 'boolean',
48400 dflt: false,
48401
48402 },
48403 showEditInChartStudio: {
48404 valType: 'boolean',
48405 dflt: false,
48406
48407 },
48408 modeBarButtonsToRemove: {
48409 valType: 'any',
48410 dflt: [],
48411
48412 },
48413 modeBarButtonsToAdd: {
48414 valType: 'any',
48415 dflt: [],
48416
48417 },
48418 modeBarButtons: {
48419 valType: 'any',
48420 dflt: false,
48421
48422 },
48423 toImageButtonOptions: {
48424 valType: 'any',
48425 dflt: {},
48426
48427 },
48428 displaylogo: {
48429 valType: 'boolean',
48430 dflt: true,
48431
48432 },
48433 watermark: {
48434 valType: 'boolean',
48435 dflt: false,
48436
48437 },
48438
48439 plotGlPixelRatio: {
48440 valType: 'number',
48441 dflt: 2,
48442 min: 1,
48443 max: 4,
48444
48445 },
48446
48447 setBackground: {
48448 valType: 'any',
48449 dflt: 'transparent',
48450
48451 },
48452
48453 topojsonURL: {
48454 valType: 'string',
48455 noBlank: true,
48456 dflt: 'https://cdn.plot.ly/',
48457
48458 },
48459
48460 mapboxAccessToken: {
48461 valType: 'string',
48462 dflt: null,
48463
48464 },
48465
48466 logging: {
48467 valType: 'integer',
48468 min: 0,
48469 max: 2,
48470 dflt: 1,
48471
48472 },
48473
48474 notifyOnLogging: {
48475 valType: 'integer',
48476 min: 0,
48477 max: 2,
48478 dflt: 0,
48479
48480 },
48481
48482 queueLength: {
48483 valType: 'integer',
48484 min: 0,
48485 dflt: 0,
48486
48487 },
48488
48489 globalTransforms: {
48490 valType: 'any',
48491 dflt: [],
48492
48493 },
48494
48495 locale: {
48496 valType: 'string',
48497 dflt: 'en-US',
48498
48499 },
48500
48501 locales: {
48502 valType: 'any',
48503 dflt: {},
48504
48505 }
48506};
48507
48508var dfltConfig = {};
48509
48510function crawl(src, target) {
48511 for(var k in src) {
48512 var obj = src[k];
48513 if(obj.valType) {
48514 target[k] = obj.dflt;
48515 } else {
48516 if(!target[k]) {
48517 target[k] = {};
48518 }
48519 crawl(obj, target[k]);
48520 }
48521 }
48522}
48523
48524crawl(configAttributes, dfltConfig);
48525
48526module.exports = {
48527 configAttributes: configAttributes,
48528 dfltConfig: dfltConfig
48529};
48530
48531},{}],211:[function(_dereq_,module,exports){
48532/**
48533* Copyright 2012-2020, Plotly, Inc.
48534* All rights reserved.
48535*
48536* This source code is licensed under the MIT license found in the
48537* LICENSE file in the root directory of this source tree.
48538*/
48539
48540'use strict';
48541
48542var Registry = _dereq_('../registry');
48543var Lib = _dereq_('../lib');
48544
48545var baseAttributes = _dereq_('../plots/attributes');
48546var baseLayoutAttributes = _dereq_('../plots/layout_attributes');
48547var frameAttributes = _dereq_('../plots/frame_attributes');
48548var animationAttributes = _dereq_('../plots/animation_attributes');
48549var configAttributes = _dereq_('./plot_config').configAttributes;
48550
48551// polar attributes are not part of the Registry yet
48552var polarAreaAttrs = _dereq_('../plots/polar/legacy/area_attributes');
48553var polarAxisAttrs = _dereq_('../plots/polar/legacy/axis_attributes');
48554
48555var editTypes = _dereq_('./edit_types');
48556
48557var extendFlat = Lib.extendFlat;
48558var extendDeepAll = Lib.extendDeepAll;
48559var isPlainObject = Lib.isPlainObject;
48560var isArrayOrTypedArray = Lib.isArrayOrTypedArray;
48561var nestedProperty = Lib.nestedProperty;
48562var valObjectMeta = Lib.valObjectMeta;
48563
48564var IS_SUBPLOT_OBJ = '_isSubplotObj';
48565var IS_LINKED_TO_ARRAY = '_isLinkedToArray';
48566var ARRAY_ATTR_REGEXPS = '_arrayAttrRegexps';
48567var DEPRECATED = '_deprecated';
48568var UNDERSCORE_ATTRS = [IS_SUBPLOT_OBJ, IS_LINKED_TO_ARRAY, ARRAY_ATTR_REGEXPS, DEPRECATED];
48569
48570exports.IS_SUBPLOT_OBJ = IS_SUBPLOT_OBJ;
48571exports.IS_LINKED_TO_ARRAY = IS_LINKED_TO_ARRAY;
48572exports.DEPRECATED = DEPRECATED;
48573exports.UNDERSCORE_ATTRS = UNDERSCORE_ATTRS;
48574
48575/** Outputs the full plotly.js plot schema
48576 *
48577 * @return {object}
48578 * - defs
48579 * - traces
48580 * - layout
48581 * - transforms
48582 * - frames
48583 * - animations
48584 * - config
48585 */
48586exports.get = function() {
48587 var traces = {};
48588
48589 Registry.allTypes.concat('area').forEach(function(type) {
48590 traces[type] = getTraceAttributes(type);
48591 });
48592
48593 var transforms = {};
48594
48595 Object.keys(Registry.transformsRegistry).forEach(function(type) {
48596 transforms[type] = getTransformAttributes(type);
48597 });
48598
48599 return {
48600 defs: {
48601 valObjects: valObjectMeta,
48602 metaKeys: UNDERSCORE_ATTRS.concat(['description', 'role', 'editType', 'impliedEdits']),
48603 editType: {
48604 traces: editTypes.traces,
48605 layout: editTypes.layout
48606 },
48607 impliedEdits: {
48608
48609 }
48610 },
48611
48612 traces: traces,
48613 layout: getLayoutAttributes(),
48614
48615 transforms: transforms,
48616
48617 frames: getFramesAttributes(),
48618 animation: formatAttributes(animationAttributes),
48619
48620 config: formatAttributes(configAttributes)
48621 };
48622};
48623
48624/**
48625 * Crawl the attribute tree, recursively calling a callback function
48626 *
48627 * @param {object} attrs
48628 * The node of the attribute tree (e.g. the root) from which recursion originates
48629 * @param {Function} callback
48630 * A callback function with the signature:
48631 * @callback callback
48632 * @param {object} attr an attribute
48633 * @param {String} attrName name string
48634 * @param {object[]} attrs all the attributes
48635 * @param {Number} level the recursion level, 0 at the root
48636 * @param {String} fullAttrString full attribute name (ie 'marker.line')
48637 * @param {Number} [specifiedLevel]
48638 * The level in the tree, in order to let the callback function detect descend or backtrack,
48639 * typically unsupplied (implied 0), just used by the self-recursive call.
48640 * The necessity arises because the tree traversal is not controlled by callback return values.
48641 * The decision to not use callback return values for controlling tree pruning arose from
48642 * the goal of keeping the crawler backwards compatible. Observe that one of the pruning conditions
48643 * precedes the callback call.
48644 * @param {string} [attrString]
48645 * the path to the current attribute, as an attribute string (ie 'marker.line')
48646 * typically unsupplied, but you may supply it if you want to disambiguate which attrs tree you
48647 * are starting from
48648 *
48649 * @return {object} transformOut
48650 * copy of transformIn that contains attribute defaults
48651 */
48652exports.crawl = function(attrs, callback, specifiedLevel, attrString) {
48653 var level = specifiedLevel || 0;
48654 attrString = attrString || '';
48655
48656 Object.keys(attrs).forEach(function(attrName) {
48657 var attr = attrs[attrName];
48658
48659 if(UNDERSCORE_ATTRS.indexOf(attrName) !== -1) return;
48660
48661 var fullAttrString = (attrString ? attrString + '.' : '') + attrName;
48662 callback(attr, attrName, attrs, level, fullAttrString);
48663
48664 if(exports.isValObject(attr)) return;
48665
48666 if(isPlainObject(attr) && attrName !== 'impliedEdits') {
48667 exports.crawl(attr, callback, level + 1, fullAttrString);
48668 }
48669 });
48670};
48671
48672/** Is object a value object (or a container object)?
48673 *
48674 * @param {object} obj
48675 * @return {boolean}
48676 * returns true for a valid value object and
48677 * false for tree nodes in the attribute hierarchy
48678 */
48679exports.isValObject = function(obj) {
48680 return obj && obj.valType !== undefined;
48681};
48682
48683/**
48684 * Find all data array attributes in a given trace object - including
48685 * `arrayOk` attributes.
48686 *
48687 * @param {object} trace
48688 * full trace object that contains a reference to `_module.attributes`
48689 *
48690 * @return {array} arrayAttributes
48691 * list of array attributes for the given trace
48692 */
48693exports.findArrayAttributes = function(trace) {
48694 var arrayAttributes = [];
48695 var stack = [];
48696 var isArrayStack = [];
48697 var baseContainer, baseAttrName;
48698
48699 function callback(attr, attrName, attrs, level) {
48700 stack = stack.slice(0, level).concat([attrName]);
48701 isArrayStack = isArrayStack.slice(0, level).concat([attr && attr._isLinkedToArray]);
48702
48703 var splittableAttr = (
48704 attr &&
48705 (attr.valType === 'data_array' || attr.arrayOk === true) &&
48706 !(stack[level - 1] === 'colorbar' && (attrName === 'ticktext' || attrName === 'tickvals'))
48707 );
48708
48709 // Manually exclude 'colorbar.tickvals' and 'colorbar.ticktext' for now
48710 // which are declared as `valType: 'data_array'` but scale independently of
48711 // the coordinate arrays.
48712 //
48713 // Down the road, we might want to add a schema field (e.g `uncorrelatedArray: true`)
48714 // to distinguish attributes of the likes.
48715
48716 if(!splittableAttr) return;
48717
48718 crawlIntoTrace(baseContainer, 0, '');
48719 }
48720
48721 function crawlIntoTrace(container, i, astrPartial) {
48722 var item = container[stack[i]];
48723 var newAstrPartial = astrPartial + stack[i];
48724 if(i === stack.length - 1) {
48725 if(isArrayOrTypedArray(item)) {
48726 arrayAttributes.push(baseAttrName + newAstrPartial);
48727 }
48728 } else {
48729 if(isArrayStack[i]) {
48730 if(Array.isArray(item)) {
48731 for(var j = 0; j < item.length; j++) {
48732 if(isPlainObject(item[j])) {
48733 crawlIntoTrace(item[j], i + 1, newAstrPartial + '[' + j + '].');
48734 }
48735 }
48736 }
48737 } else if(isPlainObject(item)) {
48738 crawlIntoTrace(item, i + 1, newAstrPartial + '.');
48739 }
48740 }
48741 }
48742
48743 baseContainer = trace;
48744 baseAttrName = '';
48745 exports.crawl(baseAttributes, callback);
48746 if(trace._module && trace._module.attributes) {
48747 exports.crawl(trace._module.attributes, callback);
48748 }
48749
48750 var transforms = trace.transforms;
48751 if(transforms) {
48752 for(var i = 0; i < transforms.length; i++) {
48753 var transform = transforms[i];
48754 var module = transform._module;
48755
48756 if(module) {
48757 baseAttrName = 'transforms[' + i + '].';
48758 baseContainer = transform;
48759
48760 exports.crawl(module.attributes, callback);
48761 }
48762 }
48763 }
48764
48765 return arrayAttributes;
48766};
48767
48768/*
48769 * Find the valObject for one attribute in an existing trace
48770 *
48771 * @param {object} trace
48772 * full trace object that contains a reference to `_module.attributes`
48773 * @param {object} parts
48774 * an array of parts, like ['transforms', 1, 'value']
48775 * typically from nestedProperty(...).parts
48776 *
48777 * @return {object|false}
48778 * the valObject for this attribute, or the last found parent
48779 * in some cases the innermost valObject will not exist, for example
48780 * `valType: 'any'` attributes where we might set a part of the attribute.
48781 * In that case, stop at the deepest valObject we *do* find.
48782 */
48783exports.getTraceValObject = function(trace, parts) {
48784 var head = parts[0];
48785 var i = 1; // index to start recursing from
48786 var moduleAttrs, valObject;
48787
48788 if(head === 'transforms') {
48789 if(parts.length === 1) {
48790 return baseAttributes.transforms;
48791 }
48792 var transforms = trace.transforms;
48793 if(!Array.isArray(transforms) || !transforms.length) return false;
48794 var tNum = parts[1];
48795 if(!isIndex(tNum) || tNum >= transforms.length) {
48796 return false;
48797 }
48798 moduleAttrs = (Registry.transformsRegistry[transforms[tNum].type] || {}).attributes;
48799 valObject = moduleAttrs && moduleAttrs[parts[2]];
48800 i = 3; // start recursing only inside the transform
48801 } else if(trace.type === 'area') {
48802 valObject = polarAreaAttrs[head];
48803 } else {
48804 // first look in the module for this trace
48805 // components have already merged their trace attributes in here
48806 var _module = trace._module;
48807 if(!_module) _module = (Registry.modules[trace.type || baseAttributes.type.dflt] || {})._module;
48808 if(!_module) return false;
48809
48810 moduleAttrs = _module.attributes;
48811 valObject = moduleAttrs && moduleAttrs[head];
48812
48813 // then look in the subplot attributes
48814 if(!valObject) {
48815 var subplotModule = _module.basePlotModule;
48816 if(subplotModule && subplotModule.attributes) {
48817 valObject = subplotModule.attributes[head];
48818 }
48819 }
48820
48821 // finally look in the global attributes
48822 if(!valObject) valObject = baseAttributes[head];
48823 }
48824
48825 return recurseIntoValObject(valObject, parts, i);
48826};
48827
48828/*
48829 * Find the valObject for one layout attribute
48830 *
48831 * @param {array} parts
48832 * an array of parts, like ['annotations', 1, 'x']
48833 * typically from nestedProperty(...).parts
48834 *
48835 * @return {object|false}
48836 * the valObject for this attribute, or the last found parent
48837 * in some cases the innermost valObject will not exist, for example
48838 * `valType: 'any'` attributes where we might set a part of the attribute.
48839 * In that case, stop at the deepest valObject we *do* find.
48840 */
48841exports.getLayoutValObject = function(fullLayout, parts) {
48842 var valObject = layoutHeadAttr(fullLayout, parts[0]);
48843
48844 return recurseIntoValObject(valObject, parts, 1);
48845};
48846
48847function layoutHeadAttr(fullLayout, head) {
48848 var i, key, _module, attributes;
48849
48850 // look for attributes of the subplot types used on the plot
48851 var basePlotModules = fullLayout._basePlotModules;
48852 if(basePlotModules) {
48853 var out;
48854 for(i = 0; i < basePlotModules.length; i++) {
48855 _module = basePlotModules[i];
48856 if(_module.attrRegex && _module.attrRegex.test(head)) {
48857 // if a module defines overrides, these take precedence
48858 // initially this is to allow gl2d different editTypes from svg cartesian
48859 if(_module.layoutAttrOverrides) return _module.layoutAttrOverrides;
48860
48861 // otherwise take the first attributes we find
48862 if(!out && _module.layoutAttributes) out = _module.layoutAttributes;
48863 }
48864
48865 // a module can also override the behavior of base (and component) module layout attrs
48866 // again see gl2d for initial use case
48867 var baseOverrides = _module.baseLayoutAttrOverrides;
48868 if(baseOverrides && head in baseOverrides) return baseOverrides[head];
48869 }
48870 if(out) return out;
48871 }
48872
48873 // look for layout attributes contributed by traces on the plot
48874 var modules = fullLayout._modules;
48875 if(modules) {
48876 for(i = 0; i < modules.length; i++) {
48877 attributes = modules[i].layoutAttributes;
48878 if(attributes && head in attributes) {
48879 return attributes[head];
48880 }
48881 }
48882 }
48883
48884 /*
48885 * Next look in components.
48886 * Components that define a schema have already merged this into
48887 * base and subplot attribute defs, so ignore these.
48888 * Others (older style) all put all their attributes
48889 * inside a container matching the module `name`
48890 * eg `attributes` (array) or `legend` (object)
48891 */
48892 for(key in Registry.componentsRegistry) {
48893 _module = Registry.componentsRegistry[key];
48894 if(_module.name === 'colorscale' && head.indexOf('coloraxis') === 0) {
48895 return _module.layoutAttributes[head];
48896 } else if(!_module.schema && (head === _module.name)) {
48897 return _module.layoutAttributes;
48898 }
48899 }
48900
48901 if(head in baseLayoutAttributes) return baseLayoutAttributes[head];
48902
48903 // Polar doesn't populate _modules or _basePlotModules
48904 // just fall back on these when the others fail
48905 if(head === 'radialaxis' || head === 'angularaxis') {
48906 return polarAxisAttrs[head];
48907 }
48908 return polarAxisAttrs.layout[head] || false;
48909}
48910
48911function recurseIntoValObject(valObject, parts, i) {
48912 if(!valObject) return false;
48913
48914 if(valObject._isLinkedToArray) {
48915 // skip array index, abort if we try to dive into an array without an index
48916 if(isIndex(parts[i])) i++;
48917 else if(i < parts.length) return false;
48918 }
48919
48920 // now recurse as far as we can. Occasionally we have an attribute
48921 // setting an internal part below what's in the schema; just return
48922 // the innermost schema item we find.
48923 for(; i < parts.length; i++) {
48924 var newValObject = valObject[parts[i]];
48925 if(isPlainObject(newValObject)) valObject = newValObject;
48926 else break;
48927
48928 if(i === parts.length - 1) break;
48929
48930 if(valObject._isLinkedToArray) {
48931 i++;
48932 if(!isIndex(parts[i])) return false;
48933 } else if(valObject.valType === 'info_array') {
48934 i++;
48935 var index = parts[i];
48936 if(!isIndex(index)) return false;
48937
48938 var items = valObject.items;
48939 if(Array.isArray(items)) {
48940 if(index >= items.length) return false;
48941 if(valObject.dimensions === 2) {
48942 i++;
48943 if(parts.length === i) return valObject;
48944 var index2 = parts[i];
48945 if(!isIndex(index2)) return false;
48946 valObject = items[index][index2];
48947 } else valObject = items[index];
48948 } else {
48949 valObject = items;
48950 }
48951 }
48952 }
48953
48954 return valObject;
48955}
48956
48957// note: this is different from Lib.isIndex, this one doesn't accept numeric
48958// strings, only actual numbers.
48959function isIndex(val) {
48960 return val === Math.round(val) && val >= 0;
48961}
48962
48963function getTraceAttributes(type) {
48964 var _module, basePlotModule;
48965
48966 if(type === 'area') {
48967 _module = { attributes: polarAreaAttrs };
48968 basePlotModule = {};
48969 } else {
48970 _module = Registry.modules[type]._module,
48971 basePlotModule = _module.basePlotModule;
48972 }
48973
48974 var attributes = {};
48975
48976 // make 'type' the first attribute in the object
48977 attributes.type = null;
48978
48979 var copyBaseAttributes = extendDeepAll({}, baseAttributes);
48980 var copyModuleAttributes = extendDeepAll({}, _module.attributes);
48981
48982 // prune global-level trace attributes that are already defined in a trace
48983 exports.crawl(copyModuleAttributes, function(attr, attrName, attrs, level, fullAttrString) {
48984 nestedProperty(copyBaseAttributes, fullAttrString).set(undefined);
48985 // Prune undefined attributes
48986 if(attr === undefined) nestedProperty(copyModuleAttributes, fullAttrString).set(undefined);
48987 });
48988
48989 // base attributes (same for all trace types)
48990 extendDeepAll(attributes, copyBaseAttributes);
48991
48992 // prune-out base attributes based on trace module categories
48993 if(Registry.traceIs(type, 'noOpacity')) {
48994 delete attributes.opacity;
48995 }
48996 if(!Registry.traceIs(type, 'showLegend')) {
48997 delete attributes.showlegend;
48998 delete attributes.legendgroup;
48999 }
49000 if(Registry.traceIs(type, 'noHover')) {
49001 delete attributes.hoverinfo;
49002 delete attributes.hoverlabel;
49003 }
49004 if(!_module.selectPoints) {
49005 delete attributes.selectedpoints;
49006 }
49007
49008 // module attributes
49009 extendDeepAll(attributes, copyModuleAttributes);
49010
49011 // subplot attributes
49012 if(basePlotModule.attributes) {
49013 extendDeepAll(attributes, basePlotModule.attributes);
49014 }
49015
49016 // 'type' gets overwritten by baseAttributes; reset it here
49017 attributes.type = type;
49018
49019 var out = {
49020 meta: _module.meta || {},
49021 categories: _module.categories || {},
49022 animatable: Boolean(_module.animatable),
49023 type: type,
49024 attributes: formatAttributes(attributes),
49025 };
49026
49027 // trace-specific layout attributes
49028 if(_module.layoutAttributes) {
49029 var layoutAttributes = {};
49030
49031 extendDeepAll(layoutAttributes, _module.layoutAttributes);
49032 out.layoutAttributes = formatAttributes(layoutAttributes);
49033 }
49034
49035 // drop anim:true in non-animatable modules
49036 if(!_module.animatable) {
49037 exports.crawl(out, function(attr) {
49038 if(exports.isValObject(attr) && 'anim' in attr) {
49039 delete attr.anim;
49040 }
49041 });
49042 }
49043
49044 return out;
49045}
49046
49047function getLayoutAttributes() {
49048 var layoutAttributes = {};
49049 var key, _module;
49050
49051 // global layout attributes
49052 extendDeepAll(layoutAttributes, baseLayoutAttributes);
49053
49054 // add base plot module layout attributes
49055 for(key in Registry.subplotsRegistry) {
49056 _module = Registry.subplotsRegistry[key];
49057
49058 if(!_module.layoutAttributes) continue;
49059
49060 if(Array.isArray(_module.attr)) {
49061 for(var i = 0; i < _module.attr.length; i++) {
49062 handleBasePlotModule(layoutAttributes, _module, _module.attr[i]);
49063 }
49064 } else {
49065 var astr = _module.attr === 'subplot' ? _module.name : _module.attr;
49066 handleBasePlotModule(layoutAttributes, _module, astr);
49067 }
49068 }
49069
49070 // polar layout attributes
49071 layoutAttributes = assignPolarLayoutAttrs(layoutAttributes);
49072
49073 // add registered components layout attributes
49074 for(key in Registry.componentsRegistry) {
49075 _module = Registry.componentsRegistry[key];
49076 var schema = _module.schema;
49077
49078 if(schema && (schema.subplots || schema.layout)) {
49079 /*
49080 * Components with defined schema have already been merged in at register time
49081 * but a few components define attributes that apply only to xaxis
49082 * not yaxis (rangeselector, rangeslider) - delete from y schema.
49083 * Note that the input attributes for xaxis/yaxis are the same object
49084 * so it's not possible to only add them to xaxis from the start.
49085 * If we ever have such asymmetry the other way, or anywhere else,
49086 * we will need to extend both this code and mergeComponentAttrsToSubplot
49087 * (which will not find yaxis only for example)
49088 */
49089 var subplots = schema.subplots;
49090 if(subplots && subplots.xaxis && !subplots.yaxis) {
49091 for(var xkey in subplots.xaxis) {
49092 delete layoutAttributes.yaxis[xkey];
49093 }
49094 }
49095 } else if(_module.name === 'colorscale') {
49096 extendDeepAll(layoutAttributes, _module.layoutAttributes);
49097 } else if(_module.layoutAttributes) {
49098 // older style without schema need to be explicitly merged in now
49099 insertAttrs(layoutAttributes, _module.layoutAttributes, _module.name);
49100 }
49101 }
49102
49103 return {
49104 layoutAttributes: formatAttributes(layoutAttributes)
49105 };
49106}
49107
49108function getTransformAttributes(type) {
49109 var _module = Registry.transformsRegistry[type];
49110 var attributes = extendDeepAll({}, _module.attributes);
49111
49112 // add registered components transform attributes
49113 Object.keys(Registry.componentsRegistry).forEach(function(k) {
49114 var _module = Registry.componentsRegistry[k];
49115
49116 if(_module.schema && _module.schema.transforms && _module.schema.transforms[type]) {
49117 Object.keys(_module.schema.transforms[type]).forEach(function(v) {
49118 insertAttrs(attributes, _module.schema.transforms[type][v], v);
49119 });
49120 }
49121 });
49122
49123 return {
49124 attributes: formatAttributes(attributes)
49125 };
49126}
49127
49128function getFramesAttributes() {
49129 var attrs = {
49130 frames: extendDeepAll({}, frameAttributes)
49131 };
49132
49133 formatAttributes(attrs);
49134
49135 return attrs.frames;
49136}
49137
49138function formatAttributes(attrs) {
49139 mergeValTypeAndRole(attrs);
49140 formatArrayContainers(attrs);
49141 stringify(attrs);
49142
49143 return attrs;
49144}
49145
49146function mergeValTypeAndRole(attrs) {
49147 function makeSrcAttr(attrName) {
49148 return {
49149 valType: 'string',
49150
49151
49152 editType: 'none'
49153 };
49154 }
49155
49156 function callback(attr, attrName, attrs) {
49157 if(exports.isValObject(attr)) {
49158 if(attr.valType === 'data_array') {
49159 // all 'data_array' attrs have role 'data'
49160 attr.role = 'data';
49161 // all 'data_array' attrs have a corresponding 'src' attr
49162 attrs[attrName + 'src'] = makeSrcAttr(attrName);
49163 } else if(attr.arrayOk === true) {
49164 // all 'arrayOk' attrs have a corresponding 'src' attr
49165 attrs[attrName + 'src'] = makeSrcAttr(attrName);
49166 }
49167 } else if(isPlainObject(attr)) {
49168 // all attrs container objects get role 'object'
49169 attr.role = 'object';
49170 }
49171 }
49172
49173 exports.crawl(attrs, callback);
49174}
49175
49176function formatArrayContainers(attrs) {
49177 function callback(attr, attrName, attrs) {
49178 if(!attr) return;
49179
49180 var itemName = attr[IS_LINKED_TO_ARRAY];
49181
49182 if(!itemName) return;
49183
49184 delete attr[IS_LINKED_TO_ARRAY];
49185
49186 attrs[attrName] = { items: {} };
49187 attrs[attrName].items[itemName] = attr;
49188 attrs[attrName].role = 'object';
49189 }
49190
49191 exports.crawl(attrs, callback);
49192}
49193
49194// this can take around 10ms and should only be run from PlotSchema.get(),
49195// to ensure JSON.stringify(PlotSchema.get()) gives the intended result.
49196function stringify(attrs) {
49197 function walk(attr) {
49198 for(var k in attr) {
49199 if(isPlainObject(attr[k])) {
49200 walk(attr[k]);
49201 } else if(Array.isArray(attr[k])) {
49202 for(var i = 0; i < attr[k].length; i++) {
49203 walk(attr[k][i]);
49204 }
49205 } else {
49206 // as JSON.stringify(/test/) // => {}
49207 if(attr[k] instanceof RegExp) {
49208 attr[k] = attr[k].toString();
49209 }
49210 }
49211 }
49212 }
49213
49214 walk(attrs);
49215}
49216
49217function assignPolarLayoutAttrs(layoutAttributes) {
49218 extendFlat(layoutAttributes, {
49219 radialaxis: polarAxisAttrs.radialaxis,
49220 angularaxis: polarAxisAttrs.angularaxis
49221 });
49222
49223 extendFlat(layoutAttributes, polarAxisAttrs.layout);
49224
49225 return layoutAttributes;
49226}
49227
49228function handleBasePlotModule(layoutAttributes, _module, astr) {
49229 var np = nestedProperty(layoutAttributes, astr);
49230 var attrs = extendDeepAll({}, _module.layoutAttributes);
49231
49232 attrs[IS_SUBPLOT_OBJ] = true;
49233 np.set(attrs);
49234}
49235
49236function insertAttrs(baseAttrs, newAttrs, astr) {
49237 var np = nestedProperty(baseAttrs, astr);
49238
49239 np.set(extendDeepAll(np.get() || {}, newAttrs));
49240}
49241
49242},{"../lib":178,"../plots/animation_attributes":217,"../plots/attributes":219,"../plots/frame_attributes":251,"../plots/layout_attributes":254,"../plots/polar/legacy/area_attributes":257,"../plots/polar/legacy/axis_attributes":258,"../registry":269,"./edit_types":205,"./plot_config":210}],212:[function(_dereq_,module,exports){
49243/**
49244* Copyright 2012-2020, Plotly, Inc.
49245* All rights reserved.
49246*
49247* This source code is licensed under the MIT license found in the
49248* LICENSE file in the root directory of this source tree.
49249*/
49250
49251
49252'use strict';
49253
49254var Lib = _dereq_('../lib');
49255var plotAttributes = _dereq_('../plots/attributes');
49256
49257var TEMPLATEITEMNAME = 'templateitemname';
49258
49259var templateAttrs = {
49260 name: {
49261 valType: 'string',
49262
49263 editType: 'none',
49264
49265 }
49266};
49267templateAttrs[TEMPLATEITEMNAME] = {
49268 valType: 'string',
49269
49270 editType: 'calc',
49271
49272};
49273
49274/**
49275 * templatedArray: decorate an attributes object with templating (and array)
49276 * properties.
49277 *
49278 * @param {string} name: the singular form of the array name. Sets
49279 * `_isLinkedToArray` to this, so the schema knows to treat this as an array.
49280 * @param {object} attrs: the item attributes. Since all callers are expected
49281 * to be constructing this object on the spot, we mutate it here for
49282 * performance, rather than extending a new object with it.
49283 *
49284 * @returns {object}: the decorated `attrs` object
49285 */
49286exports.templatedArray = function(name, attrs) {
49287 attrs._isLinkedToArray = name;
49288 attrs.name = templateAttrs.name;
49289 attrs[TEMPLATEITEMNAME] = templateAttrs[TEMPLATEITEMNAME];
49290 return attrs;
49291};
49292
49293/**
49294 * traceTemplater: logic for matching traces to trace templates
49295 *
49296 * @param {object} dataTemplate: collection of {traceType: [{template}, ...]}
49297 * ie each type the template applies to contains a list of template objects,
49298 * to be provided cyclically to data traces of that type.
49299 *
49300 * @returns {object}: {newTrace}, a function:
49301 * newTrace(traceIn): that takes the input traceIn, coerces its type, then
49302 * uses that type to find the next template to apply. returns the output
49303 * traceOut with template attached, ready to continue supplyDefaults.
49304 */
49305exports.traceTemplater = function(dataTemplate) {
49306 var traceCounts = {};
49307 var traceType, typeTemplates;
49308
49309 for(traceType in dataTemplate) {
49310 typeTemplates = dataTemplate[traceType];
49311 if(Array.isArray(typeTemplates) && typeTemplates.length) {
49312 traceCounts[traceType] = 0;
49313 }
49314 }
49315
49316 function newTrace(traceIn) {
49317 traceType = Lib.coerce(traceIn, {}, plotAttributes, 'type');
49318 var traceOut = {type: traceType, _template: null};
49319 if(traceType in traceCounts) {
49320 typeTemplates = dataTemplate[traceType];
49321 // cycle through traces in the template set for this type
49322 var typei = traceCounts[traceType] % typeTemplates.length;
49323 traceCounts[traceType]++;
49324 traceOut._template = typeTemplates[typei];
49325 } else {
49326 // TODO: anything we should do for types missing from the template?
49327 // try to apply some other type? Or just bail as we do here?
49328 // Actually I think yes, we should apply other types; would be nice
49329 // if all scatter* could inherit from each other, and if histogram
49330 // could inherit from bar, etc... but how to specify this? And do we
49331 // compose them, or if a type is present require it to be complete?
49332 // Actually this could apply to layout too - 3D annotations
49333 // inheriting from 2D, axes of different types inheriting from each
49334 // other...
49335 }
49336 return traceOut;
49337 }
49338
49339 return {
49340 newTrace: newTrace
49341 // TODO: function to figure out what's left & what didn't work
49342 };
49343};
49344
49345/**
49346 * newContainer: Create a new sub-container inside `container` and propagate any
49347 * applicable template to it. If there's no template, still propagates
49348 * `undefined` so relinkPrivate will not retain an old template!
49349 *
49350 * @param {object} container: the outer container, should already have _template
49351 * if there *is* a template for this plot
49352 * @param {string} name: the key of the new container to make
49353 * @param {string} baseName: if applicable, a base attribute to take the
49354 * template from, ie for xaxis3 the base would be xaxis
49355 *
49356 * @returns {object}: an object for inclusion _full*, empty except for the
49357 * appropriate template piece
49358 */
49359exports.newContainer = function(container, name, baseName) {
49360 var template = container._template;
49361 var part = template && (template[name] || (baseName && template[baseName]));
49362 if(!Lib.isPlainObject(part)) part = null;
49363
49364 var out = container[name] = {_template: part};
49365 return out;
49366};
49367
49368/**
49369 * arrayTemplater: special logic for templating both defaults and specific items
49370 * in a container array (annotations etc)
49371 *
49372 * @param {object} container: the outer container, should already have _template
49373 * if there *is* a template for this plot
49374 * @param {string} name: the name of the array to template (ie 'annotations')
49375 * will be used to find default ('annotationdefaults' object) and specific
49376 * ('annotations' array) template specs.
49377 * @param {string} inclusionAttr: the attribute determining this item's
49378 * inclusion in the output, usually 'visible' or 'enabled'
49379 *
49380 * @returns {object}: {newItem, defaultItems}, both functions:
49381 * newItem(itemIn): create an output item, bare except for the correct
49382 * template and name(s), as the base for supplyDefaults
49383 * defaultItems(): to be called after all newItem calls, return any
49384 * specific template items that have not already beeen included,
49385 * also as bare output items ready for supplyDefaults.
49386 */
49387exports.arrayTemplater = function(container, name, inclusionAttr) {
49388 var template = container._template;
49389 var defaultsTemplate = template && template[arrayDefaultKey(name)];
49390 var templateItems = template && template[name];
49391 if(!Array.isArray(templateItems) || !templateItems.length) {
49392 templateItems = [];
49393 }
49394
49395 var usedNames = {};
49396
49397 function newItem(itemIn) {
49398 // include name and templateitemname in the output object for ALL
49399 // container array items. Note: you could potentially use different
49400 // name and templateitemname, if you're using one template to make
49401 // another template. templateitemname would be the name in the original
49402 // template, and name is the new "subclassed" item name.
49403 var out = {name: itemIn.name, _input: itemIn};
49404 var templateItemName = out[TEMPLATEITEMNAME] = itemIn[TEMPLATEITEMNAME];
49405
49406 // no itemname: use the default template
49407 if(!validItemName(templateItemName)) {
49408 out._template = defaultsTemplate;
49409 return out;
49410 }
49411
49412 // look for an item matching this itemname
49413 // note these do not inherit from the default template, only the item.
49414 for(var i = 0; i < templateItems.length; i++) {
49415 var templateItem = templateItems[i];
49416 if(templateItem.name === templateItemName) {
49417 // Note: it's OK to use a template item more than once
49418 // but using it at least once will stop it from generating
49419 // a default item at the end.
49420 usedNames[templateItemName] = 1;
49421 out._template = templateItem;
49422 return out;
49423 }
49424 }
49425
49426 // Didn't find a matching template item, so since this item is intended
49427 // to only be modifications it's most likely broken. Hide it unless
49428 // it's explicitly marked visible - in which case it gets NO template,
49429 // not even the default.
49430 out[inclusionAttr] = itemIn[inclusionAttr] || false;
49431 // special falsy value we can look for in validateTemplate
49432 out._template = false;
49433 return out;
49434 }
49435
49436 function defaultItems() {
49437 var out = [];
49438 for(var i = 0; i < templateItems.length; i++) {
49439 var templateItem = templateItems[i];
49440 var name = templateItem.name;
49441 // only allow named items to be added as defaults,
49442 // and only allow each name once
49443 if(validItemName(name) && !usedNames[name]) {
49444 var outi = {
49445 _template: templateItem,
49446 name: name,
49447 _input: {_templateitemname: name}
49448 };
49449 outi[TEMPLATEITEMNAME] = templateItem[TEMPLATEITEMNAME];
49450 out.push(outi);
49451 usedNames[name] = 1;
49452 }
49453 }
49454 return out;
49455 }
49456
49457 return {
49458 newItem: newItem,
49459 defaultItems: defaultItems
49460 };
49461};
49462
49463function validItemName(name) {
49464 return name && typeof name === 'string';
49465}
49466
49467function arrayDefaultKey(name) {
49468 var lastChar = name.length - 1;
49469 if(name.charAt(lastChar) !== 's') {
49470 Lib.warn('bad argument to arrayDefaultKey: ' + name);
49471 }
49472 return name.substr(0, name.length - 1) + 'defaults';
49473}
49474exports.arrayDefaultKey = arrayDefaultKey;
49475
49476/**
49477 * arrayEditor: helper for editing array items that may have come from
49478 * template defaults (in which case they will not exist in the input yet)
49479 *
49480 * @param {object} parentIn: the input container (eg gd.layout)
49481 * @param {string} containerStr: the attribute string for the container inside
49482 * `parentIn`.
49483 * @param {object} itemOut: the _full* item (eg gd._fullLayout.annotations[0])
49484 * that we'll be editing. Assumed to have been created by `arrayTemplater`.
49485 *
49486 * @returns {object}: {modifyBase, modifyItem, getUpdateObj, applyUpdate}, all functions:
49487 * modifyBase(attr, value): Add an update that's *not* related to the item.
49488 * `attr` is the full attribute string.
49489 * modifyItem(attr, value): Add an update to the item. `attr` is just the
49490 * portion of the attribute string inside the item.
49491 * getUpdateObj(): Get the final constructed update object, to use in
49492 * `restyle` or `relayout`. Also resets the update object in case this
49493 * update was canceled.
49494 * applyUpdate(attr, value): optionally add an update `attr: value`,
49495 * then apply it to `parent` which should be the parent of `containerIn`,
49496 * ie the object to which `containerStr` is the attribute string.
49497 */
49498exports.arrayEditor = function(parentIn, containerStr, itemOut) {
49499 var lengthIn = (Lib.nestedProperty(parentIn, containerStr).get() || []).length;
49500 var index = itemOut._index;
49501 // Check that we are indeed off the end of this container.
49502 // Otherwise a devious user could put a key `_templateitemname` in their
49503 // own input and break lots of things.
49504 var templateItemName = (index >= lengthIn) && (itemOut._input || {})._templateitemname;
49505 if(templateItemName) index = lengthIn;
49506 var itemStr = containerStr + '[' + index + ']';
49507
49508 var update;
49509 function resetUpdate() {
49510 update = {};
49511 if(templateItemName) {
49512 update[itemStr] = {};
49513 update[itemStr][TEMPLATEITEMNAME] = templateItemName;
49514 }
49515 }
49516 resetUpdate();
49517
49518 function modifyBase(attr, value) {
49519 update[attr] = value;
49520 }
49521
49522 function modifyItem(attr, value) {
49523 if(templateItemName) {
49524 // we're making a new object: edit that object
49525 Lib.nestedProperty(update[itemStr], attr).set(value);
49526 } else {
49527 // we're editing an existing object: include *just* the edit
49528 update[itemStr + '.' + attr] = value;
49529 }
49530 }
49531
49532 function getUpdateObj() {
49533 var updateOut = update;
49534 resetUpdate();
49535 return updateOut;
49536 }
49537
49538 function applyUpdate(attr, value) {
49539 if(attr) modifyItem(attr, value);
49540 var updateToApply = getUpdateObj();
49541 for(var key in updateToApply) {
49542 Lib.nestedProperty(parentIn, key).set(updateToApply[key]);
49543 }
49544 }
49545
49546 return {
49547 modifyBase: modifyBase,
49548 modifyItem: modifyItem,
49549 getUpdateObj: getUpdateObj,
49550 applyUpdate: applyUpdate
49551 };
49552};
49553
49554},{"../lib":178,"../plots/attributes":219}],213:[function(_dereq_,module,exports){
49555/**
49556* Copyright 2012-2020, Plotly, Inc.
49557* All rights reserved.
49558*
49559* This source code is licensed under the MIT license found in the
49560* LICENSE file in the root directory of this source tree.
49561*/
49562
49563'use strict';
49564
49565var d3 = _dereq_('d3');
49566var Registry = _dereq_('../registry');
49567var Plots = _dereq_('../plots/plots');
49568
49569var Lib = _dereq_('../lib');
49570var clearGlCanvases = _dereq_('../lib/clear_gl_canvases');
49571
49572var Color = _dereq_('../components/color');
49573var Drawing = _dereq_('../components/drawing');
49574var Titles = _dereq_('../components/titles');
49575var ModeBar = _dereq_('../components/modebar');
49576
49577var Axes = _dereq_('../plots/cartesian/axes');
49578var alignmentConstants = _dereq_('../constants/alignment');
49579var axisConstraints = _dereq_('../plots/cartesian/constraints');
49580var enforceAxisConstraints = axisConstraints.enforce;
49581var cleanAxisConstraints = axisConstraints.clean;
49582var doAutoRange = _dereq_('../plots/cartesian/autorange').doAutoRange;
49583
49584var SVG_TEXT_ANCHOR_START = 'start';
49585var SVG_TEXT_ANCHOR_MIDDLE = 'middle';
49586var SVG_TEXT_ANCHOR_END = 'end';
49587
49588exports.layoutStyles = function(gd) {
49589 return Lib.syncOrAsync([Plots.doAutoMargin, lsInner], gd);
49590};
49591
49592function overlappingDomain(xDomain, yDomain, domains) {
49593 for(var i = 0; i < domains.length; i++) {
49594 var existingX = domains[i][0];
49595 var existingY = domains[i][1];
49596
49597 if(existingX[0] >= xDomain[1] || existingX[1] <= xDomain[0]) {
49598 continue;
49599 }
49600 if(existingY[0] < yDomain[1] && existingY[1] > yDomain[0]) {
49601 return true;
49602 }
49603 }
49604 return false;
49605}
49606
49607function lsInner(gd) {
49608 var fullLayout = gd._fullLayout;
49609 var gs = fullLayout._size;
49610 var pad = gs.p;
49611 var axList = Axes.list(gd, '', true);
49612 var i, subplot, plotinfo, ax, xa, ya;
49613
49614 fullLayout._paperdiv.style({
49615 width: (gd._context.responsive && fullLayout.autosize && !gd._context._hasZeroWidth && !gd.layout.width) ? '100%' : fullLayout.width + 'px',
49616 height: (gd._context.responsive && fullLayout.autosize && !gd._context._hasZeroHeight && !gd.layout.height) ? '100%' : fullLayout.height + 'px'
49617 })
49618 .selectAll('.main-svg')
49619 .call(Drawing.setSize, fullLayout.width, fullLayout.height);
49620 gd._context.setBackground(gd, fullLayout.paper_bgcolor);
49621
49622 exports.drawMainTitle(gd);
49623 ModeBar.manage(gd);
49624
49625 // _has('cartesian') means SVG specifically, not GL2D - but GL2D
49626 // can still get here because it makes some of the SVG structure
49627 // for shared features like selections.
49628 if(!fullLayout._has('cartesian')) {
49629 return Plots.previousPromises(gd);
49630 }
49631
49632 function getLinePosition(ax, counterAx, side) {
49633 var lwHalf = ax._lw / 2;
49634
49635 if(ax._id.charAt(0) === 'x') {
49636 if(!counterAx) return gs.t + gs.h * (1 - (ax.position || 0)) + (lwHalf % 1);
49637 else if(side === 'top') return counterAx._offset - pad - lwHalf;
49638 return counterAx._offset + counterAx._length + pad + lwHalf;
49639 }
49640
49641 if(!counterAx) return gs.l + gs.w * (ax.position || 0) + (lwHalf % 1);
49642 else if(side === 'right') return counterAx._offset + counterAx._length + pad + lwHalf;
49643 return counterAx._offset - pad - lwHalf;
49644 }
49645
49646 // some preparation of axis position info
49647 for(i = 0; i < axList.length; i++) {
49648 ax = axList[i];
49649
49650 var counterAx = ax._anchorAxis;
49651
49652 // clear axis line positions, to be set in the subplot loop below
49653 ax._linepositions = {};
49654
49655 // stash crispRounded linewidth so we don't need to pass gd all over the place
49656 ax._lw = Drawing.crispRound(gd, ax.linewidth, 1);
49657
49658 // figure out the main axis line and main mirror line position.
49659 // it's easier to follow the logic if we handle these separately from
49660 // ax._linepositions, which are only used by mirror=allticks
49661 // for non-main-subplot ticks, and mirror=all(ticks)? for zero line
49662 // hiding logic
49663 ax._mainLinePosition = getLinePosition(ax, counterAx, ax.side);
49664 ax._mainMirrorPosition = (ax.mirror && counterAx) ?
49665 getLinePosition(ax, counterAx,
49666 alignmentConstants.OPPOSITE_SIDE[ax.side]) : null;
49667 }
49668
49669 // figure out which backgrounds we need to draw,
49670 // and in which layers to put them
49671 var lowerBackgroundIDs = [];
49672 var backgroundIds = [];
49673 var lowerDomains = [];
49674 // no need to draw background when paper and plot color are the same color,
49675 // activate mode just for large splom (which benefit the most from this
49676 // optimization), but this could apply to all cartesian subplots.
49677 var noNeedForBg = (
49678 Color.opacity(fullLayout.paper_bgcolor) === 1 &&
49679 Color.opacity(fullLayout.plot_bgcolor) === 1 &&
49680 fullLayout.paper_bgcolor === fullLayout.plot_bgcolor
49681 );
49682
49683 for(subplot in fullLayout._plots) {
49684 plotinfo = fullLayout._plots[subplot];
49685
49686 if(plotinfo.mainplot) {
49687 // mainplot is a reference to the main plot this one is overlaid on
49688 // so if it exists, this is an overlaid plot and we don't need to
49689 // give it its own background
49690 if(plotinfo.bg) {
49691 plotinfo.bg.remove();
49692 }
49693 plotinfo.bg = undefined;
49694 } else {
49695 var xDomain = plotinfo.xaxis.domain;
49696 var yDomain = plotinfo.yaxis.domain;
49697 var plotgroup = plotinfo.plotgroup;
49698
49699 if(overlappingDomain(xDomain, yDomain, lowerDomains)) {
49700 var pgNode = plotgroup.node();
49701 var plotgroupBg = plotinfo.bg = Lib.ensureSingle(plotgroup, 'rect', 'bg');
49702 pgNode.insertBefore(plotgroupBg.node(), pgNode.childNodes[0]);
49703 backgroundIds.push(subplot);
49704 } else {
49705 plotgroup.select('rect.bg').remove();
49706 lowerDomains.push([xDomain, yDomain]);
49707 if(!noNeedForBg) {
49708 lowerBackgroundIDs.push(subplot);
49709 backgroundIds.push(subplot);
49710 }
49711 }
49712 }
49713 }
49714
49715 // now create all the lower-layer backgrounds at once now that
49716 // we have the list of subplots that need them
49717 var lowerBackgrounds = fullLayout._bgLayer.selectAll('.bg')
49718 .data(lowerBackgroundIDs);
49719
49720 lowerBackgrounds.enter().append('rect')
49721 .classed('bg', true);
49722
49723 lowerBackgrounds.exit().remove();
49724
49725 lowerBackgrounds.each(function(subplot) {
49726 fullLayout._plots[subplot].bg = d3.select(this);
49727 });
49728
49729 // style all backgrounds
49730 for(i = 0; i < backgroundIds.length; i++) {
49731 plotinfo = fullLayout._plots[backgroundIds[i]];
49732 xa = plotinfo.xaxis;
49733 ya = plotinfo.yaxis;
49734
49735 if(plotinfo.bg) {
49736 plotinfo.bg
49737 .call(Drawing.setRect,
49738 xa._offset - pad, ya._offset - pad,
49739 xa._length + 2 * pad, ya._length + 2 * pad)
49740 .call(Color.fill, fullLayout.plot_bgcolor)
49741 .style('stroke-width', 0);
49742 }
49743 }
49744
49745 if(!fullLayout._hasOnlyLargeSploms) {
49746 for(subplot in fullLayout._plots) {
49747 plotinfo = fullLayout._plots[subplot];
49748 xa = plotinfo.xaxis;
49749 ya = plotinfo.yaxis;
49750
49751 // Clip so that data only shows up on the plot area.
49752 var clipId = plotinfo.clipId = 'clip' + fullLayout._uid + subplot + 'plot';
49753
49754 var plotClip = Lib.ensureSingleById(fullLayout._clips, 'clipPath', clipId, function(s) {
49755 s.classed('plotclip', true)
49756 .append('rect');
49757 });
49758
49759 plotinfo.clipRect = plotClip.select('rect').attr({
49760 width: xa._length,
49761 height: ya._length
49762 });
49763
49764 Drawing.setTranslate(plotinfo.plot, xa._offset, ya._offset);
49765
49766 var plotClipId;
49767 var layerClipId;
49768
49769 if(plotinfo._hasClipOnAxisFalse) {
49770 plotClipId = null;
49771 layerClipId = clipId;
49772 } else {
49773 plotClipId = clipId;
49774 layerClipId = null;
49775 }
49776
49777 Drawing.setClipUrl(plotinfo.plot, plotClipId, gd);
49778
49779 // stash layer clipId value (null or same as clipId)
49780 // to DRY up Drawing.setClipUrl calls on trace-module and trace layers
49781 // downstream
49782 plotinfo.layerClipId = layerClipId;
49783 }
49784 }
49785
49786 var xLinesXLeft, xLinesXRight, xLinesYBottom, xLinesYTop,
49787 leftYLineWidth, rightYLineWidth;
49788 var yLinesYBottom, yLinesYTop, yLinesXLeft, yLinesXRight,
49789 connectYBottom, connectYTop;
49790 var extraSubplot;
49791
49792 function xLinePath(y) {
49793 return 'M' + xLinesXLeft + ',' + y + 'H' + xLinesXRight;
49794 }
49795
49796 function xLinePathFree(y) {
49797 return 'M' + xa._offset + ',' + y + 'h' + xa._length;
49798 }
49799
49800 function yLinePath(x) {
49801 return 'M' + x + ',' + yLinesYTop + 'V' + yLinesYBottom;
49802 }
49803
49804 function yLinePathFree(x) {
49805 return 'M' + x + ',' + ya._offset + 'v' + ya._length;
49806 }
49807
49808 function mainPath(ax, pathFn, pathFnFree) {
49809 if(!ax.showline || subplot !== ax._mainSubplot) return '';
49810 if(!ax._anchorAxis) return pathFnFree(ax._mainLinePosition);
49811 var out = pathFn(ax._mainLinePosition);
49812 if(ax.mirror) out += pathFn(ax._mainMirrorPosition);
49813 return out;
49814 }
49815
49816 for(subplot in fullLayout._plots) {
49817 plotinfo = fullLayout._plots[subplot];
49818 xa = plotinfo.xaxis;
49819 ya = plotinfo.yaxis;
49820
49821 /*
49822 * x lines get longer where they meet y lines, to make a crisp corner.
49823 * The x lines get the padding (margin.pad) plus the y line width to
49824 * fill up the corner nicely. Free x lines are excluded - they always
49825 * span exactly the data area of the plot
49826 *
49827 * | XXXXX
49828 * | XXXXX
49829 * |
49830 * +------
49831 * x1
49832 * -----
49833 * x2
49834 */
49835 var xPath = 'M0,0';
49836 if(shouldShowLinesOrTicks(xa, subplot)) {
49837 leftYLineWidth = findCounterAxisLineWidth(xa, 'left', ya, axList);
49838 xLinesXLeft = xa._offset - (leftYLineWidth ? (pad + leftYLineWidth) : 0);
49839 rightYLineWidth = findCounterAxisLineWidth(xa, 'right', ya, axList);
49840 xLinesXRight = xa._offset + xa._length + (rightYLineWidth ? (pad + rightYLineWidth) : 0);
49841 xLinesYBottom = getLinePosition(xa, ya, 'bottom');
49842 xLinesYTop = getLinePosition(xa, ya, 'top');
49843
49844 // save axis line positions for extra ticks to reference
49845 // each subplot that gets ticks from "allticks" gets an entry:
49846 // [left or bottom, right or top]
49847 extraSubplot = (!xa._anchorAxis || subplot !== xa._mainSubplot);
49848 if(extraSubplot && (xa.mirror === 'allticks' || xa.mirror === 'all')) {
49849 xa._linepositions[subplot] = [xLinesYBottom, xLinesYTop];
49850 }
49851
49852 xPath = mainPath(xa, xLinePath, xLinePathFree);
49853 if(extraSubplot && xa.showline && (xa.mirror === 'all' || xa.mirror === 'allticks')) {
49854 xPath += xLinePath(xLinesYBottom) + xLinePath(xLinesYTop);
49855 }
49856
49857 plotinfo.xlines
49858 .style('stroke-width', xa._lw + 'px')
49859 .call(Color.stroke, xa.showline ?
49860 xa.linecolor : 'rgba(0,0,0,0)');
49861 }
49862 plotinfo.xlines.attr('d', xPath);
49863
49864 /*
49865 * y lines that meet x axes get longer only by margin.pad, because
49866 * the x axes fill in the corner space. Free y axes, like free x axes,
49867 * always span exactly the data area of the plot
49868 *
49869 * | | XXXX
49870 * y2| y1| XXXX
49871 * | | XXXX
49872 * |
49873 * +-----
49874 */
49875 var yPath = 'M0,0';
49876 if(shouldShowLinesOrTicks(ya, subplot)) {
49877 connectYBottom = findCounterAxisLineWidth(ya, 'bottom', xa, axList);
49878 yLinesYBottom = ya._offset + ya._length + (connectYBottom ? pad : 0);
49879 connectYTop = findCounterAxisLineWidth(ya, 'top', xa, axList);
49880 yLinesYTop = ya._offset - (connectYTop ? pad : 0);
49881 yLinesXLeft = getLinePosition(ya, xa, 'left');
49882 yLinesXRight = getLinePosition(ya, xa, 'right');
49883
49884 extraSubplot = (!ya._anchorAxis || subplot !== ya._mainSubplot);
49885 if(extraSubplot && (ya.mirror === 'allticks' || ya.mirror === 'all')) {
49886 ya._linepositions[subplot] = [yLinesXLeft, yLinesXRight];
49887 }
49888
49889 yPath = mainPath(ya, yLinePath, yLinePathFree);
49890 if(extraSubplot && ya.showline && (ya.mirror === 'all' || ya.mirror === 'allticks')) {
49891 yPath += yLinePath(yLinesXLeft) + yLinePath(yLinesXRight);
49892 }
49893
49894 plotinfo.ylines
49895 .style('stroke-width', ya._lw + 'px')
49896 .call(Color.stroke, ya.showline ?
49897 ya.linecolor : 'rgba(0,0,0,0)');
49898 }
49899 plotinfo.ylines.attr('d', yPath);
49900 }
49901
49902 Axes.makeClipPaths(gd);
49903
49904 return Plots.previousPromises(gd);
49905}
49906
49907function shouldShowLinesOrTicks(ax, subplot) {
49908 return (ax.ticks || ax.showline) &&
49909 (subplot === ax._mainSubplot || ax.mirror === 'all' || ax.mirror === 'allticks');
49910}
49911
49912/*
49913 * should we draw a line on counterAx at this side of ax?
49914 * It's assumed that counterAx is known to overlay the subplot we're working on
49915 * but it may not be its main axis.
49916 */
49917function shouldShowLineThisSide(ax, side, counterAx) {
49918 // does counterAx get a line at all?
49919 if(!counterAx.showline || !counterAx._lw) return false;
49920
49921 // are we drawing *all* lines for counterAx?
49922 if(counterAx.mirror === 'all' || counterAx.mirror === 'allticks') return true;
49923
49924 var anchorAx = counterAx._anchorAxis;
49925
49926 // is this a free axis? free axes can only have a subplot side-line with all(ticks)? mirroring
49927 if(!anchorAx) return false;
49928
49929 // in order to handle cases where the user forgot to anchor this axis correctly
49930 // (because its default anchor has the same domain on the relevant end)
49931 // check whether the relevant position is the same.
49932 var sideIndex = alignmentConstants.FROM_BL[side];
49933 if(counterAx.side === side) {
49934 return anchorAx.domain[sideIndex] === ax.domain[sideIndex];
49935 }
49936 return counterAx.mirror && anchorAx.domain[1 - sideIndex] === ax.domain[1 - sideIndex];
49937}
49938
49939/*
49940 * Is there another axis intersecting `side` end of `ax`?
49941 * First look at `counterAx` (the axis for this subplot),
49942 * then at all other potential counteraxes on or overlaying this subplot.
49943 * Take the line width from the first one that has a line.
49944 */
49945function findCounterAxisLineWidth(ax, side, counterAx, axList) {
49946 if(shouldShowLineThisSide(ax, side, counterAx)) {
49947 return counterAx._lw;
49948 }
49949 for(var i = 0; i < axList.length; i++) {
49950 var axi = axList[i];
49951 if(axi._mainAxis === counterAx._mainAxis && shouldShowLineThisSide(ax, side, axi)) {
49952 return axi._lw;
49953 }
49954 }
49955 return 0;
49956}
49957
49958exports.drawMainTitle = function(gd) {
49959 var fullLayout = gd._fullLayout;
49960
49961 var textAnchor = getMainTitleTextAnchor(fullLayout);
49962 var dy = getMainTitleDy(fullLayout);
49963
49964 Titles.draw(gd, 'gtitle', {
49965 propContainer: fullLayout,
49966 propName: 'title.text',
49967 placeholder: fullLayout._dfltTitle.plot,
49968 attributes: {
49969 x: getMainTitleX(fullLayout, textAnchor),
49970 y: getMainTitleY(fullLayout, dy),
49971 'text-anchor': textAnchor,
49972 dy: dy
49973 }
49974 });
49975};
49976
49977function getMainTitleX(fullLayout, textAnchor) {
49978 var title = fullLayout.title;
49979 var gs = fullLayout._size;
49980 var hPadShift = 0;
49981
49982 if(textAnchor === SVG_TEXT_ANCHOR_START) {
49983 hPadShift = title.pad.l;
49984 } else if(textAnchor === SVG_TEXT_ANCHOR_END) {
49985 hPadShift = -title.pad.r;
49986 }
49987
49988 switch(title.xref) {
49989 case 'paper':
49990 return gs.l + gs.w * title.x + hPadShift;
49991 case 'container':
49992 default:
49993 return fullLayout.width * title.x + hPadShift;
49994 }
49995}
49996
49997function getMainTitleY(fullLayout, dy) {
49998 var title = fullLayout.title;
49999 var gs = fullLayout._size;
50000 var vPadShift = 0;
50001
50002 if(dy === '0em' || !dy) {
50003 vPadShift = -title.pad.b;
50004 } else if(dy === alignmentConstants.CAP_SHIFT + 'em') {
50005 vPadShift = title.pad.t;
50006 }
50007
50008 if(title.y === 'auto') {
50009 return gs.t / 2;
50010 } else {
50011 switch(title.yref) {
50012 case 'paper':
50013 return gs.t + gs.h - gs.h * title.y + vPadShift;
50014 case 'container':
50015 default:
50016 return fullLayout.height - fullLayout.height * title.y + vPadShift;
50017 }
50018 }
50019}
50020
50021function getMainTitleTextAnchor(fullLayout) {
50022 var title = fullLayout.title;
50023
50024 var textAnchor = SVG_TEXT_ANCHOR_MIDDLE;
50025 if(Lib.isRightAnchor(title)) {
50026 textAnchor = SVG_TEXT_ANCHOR_END;
50027 } else if(Lib.isLeftAnchor(title)) {
50028 textAnchor = SVG_TEXT_ANCHOR_START;
50029 }
50030
50031 return textAnchor;
50032}
50033
50034function getMainTitleDy(fullLayout) {
50035 var title = fullLayout.title;
50036
50037 var dy = '0em';
50038 if(Lib.isTopAnchor(title)) {
50039 dy = alignmentConstants.CAP_SHIFT + 'em';
50040 } else if(Lib.isMiddleAnchor(title)) {
50041 dy = alignmentConstants.MID_SHIFT + 'em';
50042 }
50043
50044 return dy;
50045}
50046
50047exports.doTraceStyle = function(gd) {
50048 var calcdata = gd.calcdata;
50049 var editStyleCalls = [];
50050 var i;
50051
50052 for(i = 0; i < calcdata.length; i++) {
50053 var cd = calcdata[i];
50054 var cd0 = cd[0] || {};
50055 var trace = cd0.trace || {};
50056 var _module = trace._module || {};
50057
50058 // See if we need to do arraysToCalcdata
50059 // call it regardless of what change we made, in case
50060 // supplyDefaults brought in an array that was already
50061 // in gd.data but not in gd._fullData previously
50062 var arraysToCalcdata = _module.arraysToCalcdata;
50063 if(arraysToCalcdata) arraysToCalcdata(cd, trace);
50064
50065 var editStyle = _module.editStyle;
50066 if(editStyle) editStyleCalls.push({fn: editStyle, cd0: cd0});
50067 }
50068
50069 if(editStyleCalls.length) {
50070 for(i = 0; i < editStyleCalls.length; i++) {
50071 var edit = editStyleCalls[i];
50072 edit.fn(gd, edit.cd0);
50073 }
50074 clearGlCanvases(gd);
50075 exports.redrawReglTraces(gd);
50076 }
50077
50078 Plots.style(gd);
50079 Registry.getComponentMethod('legend', 'draw')(gd);
50080
50081 return Plots.previousPromises(gd);
50082};
50083
50084exports.doColorBars = function(gd) {
50085 Registry.getComponentMethod('colorbar', 'draw')(gd);
50086 return Plots.previousPromises(gd);
50087};
50088
50089// force plot() to redo the layout and replot with the modified layout
50090exports.layoutReplot = function(gd) {
50091 var layout = gd.layout;
50092 gd.layout = undefined;
50093 return Registry.call('plot', gd, '', layout);
50094};
50095
50096exports.doLegend = function(gd) {
50097 Registry.getComponentMethod('legend', 'draw')(gd);
50098 return Plots.previousPromises(gd);
50099};
50100
50101exports.doTicksRelayout = function(gd) {
50102 Axes.draw(gd, 'redraw');
50103
50104 if(gd._fullLayout._hasOnlyLargeSploms) {
50105 Registry.subplotsRegistry.splom.updateGrid(gd);
50106 clearGlCanvases(gd);
50107 exports.redrawReglTraces(gd);
50108 }
50109
50110 exports.drawMainTitle(gd);
50111 return Plots.previousPromises(gd);
50112};
50113
50114exports.doModeBar = function(gd) {
50115 var fullLayout = gd._fullLayout;
50116
50117 ModeBar.manage(gd);
50118
50119 for(var i = 0; i < fullLayout._basePlotModules.length; i++) {
50120 var updateFx = fullLayout._basePlotModules[i].updateFx;
50121 if(updateFx) updateFx(gd);
50122 }
50123
50124 return Plots.previousPromises(gd);
50125};
50126
50127exports.doCamera = function(gd) {
50128 var fullLayout = gd._fullLayout;
50129 var sceneIds = fullLayout._subplots.gl3d;
50130
50131 for(var i = 0; i < sceneIds.length; i++) {
50132 var sceneLayout = fullLayout[sceneIds[i]];
50133 var scene = sceneLayout._scene;
50134
50135 scene.setViewport(sceneLayout);
50136 }
50137};
50138
50139exports.drawData = function(gd) {
50140 var fullLayout = gd._fullLayout;
50141
50142 clearGlCanvases(gd);
50143
50144 // loop over the base plot modules present on graph
50145 var basePlotModules = fullLayout._basePlotModules;
50146 for(var i = 0; i < basePlotModules.length; i++) {
50147 basePlotModules[i].plot(gd);
50148 }
50149
50150 exports.redrawReglTraces(gd);
50151
50152 // styling separate from drawing
50153 Plots.style(gd);
50154
50155 // draw components that can be drawn on axes,
50156 // and that do not push the margins
50157 Registry.getComponentMethod('shapes', 'draw')(gd);
50158 Registry.getComponentMethod('annotations', 'draw')(gd);
50159 Registry.getComponentMethod('images', 'draw')(gd);
50160
50161 // Mark the first render as complete
50162 fullLayout._replotting = false;
50163
50164 return Plots.previousPromises(gd);
50165};
50166
50167// Draw (or redraw) all regl-based traces in one go,
50168// useful during drag and selection where buffers of targeted traces are updated,
50169// but all traces need to be redrawn following clearGlCanvases.
50170//
50171// Note that _module.plot for regl trace does NOT draw things
50172// on the canvas, they only update the buffers.
50173// Drawing is perform here.
50174//
50175// TODO try adding per-subplot option using gl.SCISSOR_TEST for
50176// non-overlaying, disjoint subplots.
50177//
50178// TODO try to include parcoords in here.
50179// https://github.com/plotly/plotly.js/issues/3069
50180exports.redrawReglTraces = function(gd) {
50181 var fullLayout = gd._fullLayout;
50182
50183 if(fullLayout._has('regl')) {
50184 var fullData = gd._fullData;
50185 var cartesianIds = [];
50186 var polarIds = [];
50187 var i, sp;
50188
50189 if(fullLayout._hasOnlyLargeSploms) {
50190 fullLayout._splomGrid.draw();
50191 }
50192
50193 // N.B.
50194 // - Loop over fullData (not _splomScenes) to preserve splom trace-to-trace ordering
50195 // - Fill list if subplot ids (instead of fullLayout._subplots) to handle cases where all traces
50196 // of a given module are `visible !== true`
50197 for(i = 0; i < fullData.length; i++) {
50198 var trace = fullData[i];
50199
50200 if(trace.visible === true && trace._length !== 0) {
50201 if(trace.type === 'splom') {
50202 fullLayout._splomScenes[trace.uid].draw();
50203 } else if(trace.type === 'scattergl') {
50204 Lib.pushUnique(cartesianIds, trace.xaxis + trace.yaxis);
50205 } else if(trace.type === 'scatterpolargl') {
50206 Lib.pushUnique(polarIds, trace.subplot);
50207 }
50208 }
50209 }
50210
50211 for(i = 0; i < cartesianIds.length; i++) {
50212 sp = fullLayout._plots[cartesianIds[i]];
50213 if(sp._scene) sp._scene.draw();
50214 }
50215
50216 for(i = 0; i < polarIds.length; i++) {
50217 sp = fullLayout[polarIds[i]]._subplot;
50218 if(sp._scene) sp._scene.draw();
50219 }
50220 }
50221};
50222
50223exports.doAutoRangeAndConstraints = function(gd) {
50224 var fullLayout = gd._fullLayout;
50225 var axList = Axes.list(gd, '', true);
50226 var matchGroups = fullLayout._axisMatchGroups || [];
50227 var axLookup = {};
50228 var ax;
50229 var axRng;
50230
50231 for(var i = 0; i < axList.length; i++) {
50232 ax = axList[i];
50233 cleanAxisConstraints(gd, ax);
50234 doAutoRange(gd, ax);
50235 axLookup[ax._id] = 1;
50236 }
50237
50238 enforceAxisConstraints(gd);
50239
50240 groupLoop:
50241 for(var j = 0; j < matchGroups.length; j++) {
50242 var group = matchGroups[j];
50243 var rng = null;
50244 var id;
50245
50246 for(id in group) {
50247 ax = Axes.getFromId(gd, id);
50248
50249 // skip over 'missing' axes which do not pass through doAutoRange
50250 if(!axLookup[ax._id]) continue;
50251 // if one axis has autorange false, we're done
50252 if(ax.autorange === false) continue groupLoop;
50253
50254 axRng = Lib.simpleMap(ax.range, ax.r2l);
50255 if(rng) {
50256 if(rng[0] < rng[1]) {
50257 rng[0] = Math.min(rng[0], axRng[0]);
50258 rng[1] = Math.max(rng[1], axRng[1]);
50259 } else {
50260 rng[0] = Math.max(rng[0], axRng[0]);
50261 rng[1] = Math.min(rng[1], axRng[1]);
50262 }
50263 } else {
50264 rng = axRng;
50265 }
50266 }
50267
50268 for(id in group) {
50269 ax = Axes.getFromId(gd, id);
50270 ax.range = Lib.simpleMap(rng, ax.l2r);
50271 ax._input.range = ax.range.slice();
50272 ax.setScale();
50273 }
50274 }
50275};
50276
50277// An initial paint must be completed before these components can be
50278// correctly sized and the whole plot re-margined. fullLayout._replotting must
50279// be set to false before these will work properly.
50280exports.finalDraw = function(gd) {
50281 // TODO: rangesliders really belong in marginPushers but they need to be
50282 // drawn after data - can we at least get the margin pushing part separated
50283 // out and done earlier?
50284 Registry.getComponentMethod('rangeslider', 'draw')(gd);
50285 // TODO: rangeselector only needs to be here (in addition to drawMarginPushers)
50286 // because the margins need to be fully determined before we can call
50287 // autorange and update axis ranges (which rangeselector needs to know which
50288 // button is active). Can we break out its automargin step from its draw step?
50289 Registry.getComponentMethod('rangeselector', 'draw')(gd);
50290};
50291
50292exports.drawMarginPushers = function(gd) {
50293 Registry.getComponentMethod('legend', 'draw')(gd);
50294 Registry.getComponentMethod('rangeselector', 'draw')(gd);
50295 Registry.getComponentMethod('sliders', 'draw')(gd);
50296 Registry.getComponentMethod('updatemenus', 'draw')(gd);
50297 Registry.getComponentMethod('colorbar', 'draw')(gd);
50298};
50299
50300},{"../components/color":52,"../components/drawing":74,"../components/modebar":112,"../components/titles":147,"../constants/alignment":154,"../lib":178,"../lib/clear_gl_canvases":167,"../plots/cartesian/autorange":221,"../plots/cartesian/axes":222,"../plots/cartesian/constraints":229,"../plots/plots":256,"../registry":269,"d3":16}],214:[function(_dereq_,module,exports){
50301/**
50302* Copyright 2012-2020, Plotly, Inc.
50303* All rights reserved.
50304*
50305* This source code is licensed under the MIT license found in the
50306* LICENSE file in the root directory of this source tree.
50307*/
50308
50309
50310'use strict';
50311
50312var Lib = _dereq_('../lib');
50313var isPlainObject = Lib.isPlainObject;
50314var PlotSchema = _dereq_('./plot_schema');
50315var Plots = _dereq_('../plots/plots');
50316var plotAttributes = _dereq_('../plots/attributes');
50317var Template = _dereq_('./plot_template');
50318var dfltConfig = _dereq_('./plot_config').dfltConfig;
50319
50320/**
50321 * Plotly.makeTemplate: create a template off an existing figure to reuse
50322 * style attributes on other figures.
50323 *
50324 * Note: separated from the rest of templates because otherwise we get circular
50325 * references due to PlotSchema.
50326 *
50327 * @param {object|DOM element|string} figure: The figure to base the template on
50328 * should contain a trace array `figure.data`
50329 * and a layout object `figure.layout`
50330 * @returns {object} template: the extracted template - can then be used as
50331 * `layout.template` in another figure.
50332 */
50333exports.makeTemplate = function(figure) {
50334 figure = Lib.isPlainObject(figure) ? figure : Lib.getGraphDiv(figure);
50335 figure = Lib.extendDeep({_context: dfltConfig}, {data: figure.data, layout: figure.layout});
50336 Plots.supplyDefaults(figure);
50337 var data = figure.data || [];
50338 var layout = figure.layout || {};
50339 // copy over a few items to help follow the schema
50340 layout._basePlotModules = figure._fullLayout._basePlotModules;
50341 layout._modules = figure._fullLayout._modules;
50342
50343 var template = {
50344 data: {},
50345 layout: {}
50346 };
50347
50348 /*
50349 * Note: we do NOT validate template values, we just take what's in the
50350 * user inputs data and layout, not the validated values in fullData and
50351 * fullLayout. Even if we were to validate here, there's no guarantee that
50352 * these values would still be valid when applied to a new figure, which
50353 * may contain different trace modes, different axes, etc. So it's
50354 * important that when applying a template we still validate the template
50355 * values, rather than just using them as defaults.
50356 */
50357
50358 data.forEach(function(trace) {
50359 // TODO: What if no style info is extracted for this trace. We may
50360 // not want an empty object as the null value.
50361 // TODO: allow transforms to contribute to templates?
50362 // as it stands they are ignored, which may be for the best...
50363
50364 var traceTemplate = {};
50365 walkStyleKeys(trace, traceTemplate, getTraceInfo.bind(null, trace));
50366
50367 var traceType = Lib.coerce(trace, {}, plotAttributes, 'type');
50368 var typeTemplates = template.data[traceType];
50369 if(!typeTemplates) typeTemplates = template.data[traceType] = [];
50370 typeTemplates.push(traceTemplate);
50371 });
50372
50373 walkStyleKeys(layout, template.layout, getLayoutInfo.bind(null, layout));
50374
50375 /*
50376 * Compose the new template with an existing one to the same effect
50377 *
50378 * NOTE: there's a possibility of slightly different behavior: if the plot
50379 * has an invalid value and the old template has a valid value for the same
50380 * attribute, the plot will use the old template value but this routine
50381 * will pull the invalid value (resulting in the original default).
50382 * In the general case it's not possible to solve this with a single value,
50383 * since valid options can be context-dependent. It could be solved with
50384 * a *list* of values, but that would be huge complexity for little gain.
50385 */
50386 delete template.layout.template;
50387 var oldTemplate = layout.template;
50388 if(isPlainObject(oldTemplate)) {
50389 var oldLayoutTemplate = oldTemplate.layout;
50390
50391 var i, traceType, oldTypeTemplates, oldTypeLen, typeTemplates, typeLen;
50392
50393 if(isPlainObject(oldLayoutTemplate)) {
50394 mergeTemplates(oldLayoutTemplate, template.layout);
50395 }
50396 var oldDataTemplate = oldTemplate.data;
50397 if(isPlainObject(oldDataTemplate)) {
50398 for(traceType in template.data) {
50399 oldTypeTemplates = oldDataTemplate[traceType];
50400 if(Array.isArray(oldTypeTemplates)) {
50401 typeTemplates = template.data[traceType];
50402 typeLen = typeTemplates.length;
50403 oldTypeLen = oldTypeTemplates.length;
50404 for(i = 0; i < typeLen; i++) {
50405 mergeTemplates(oldTypeTemplates[i % oldTypeLen], typeTemplates[i]);
50406 }
50407 for(i = typeLen; i < oldTypeLen; i++) {
50408 typeTemplates.push(Lib.extendDeep({}, oldTypeTemplates[i]));
50409 }
50410 }
50411 }
50412 for(traceType in oldDataTemplate) {
50413 if(!(traceType in template.data)) {
50414 template.data[traceType] = Lib.extendDeep([], oldDataTemplate[traceType]);
50415 }
50416 }
50417 }
50418 }
50419
50420 return template;
50421};
50422
50423function mergeTemplates(oldTemplate, newTemplate) {
50424 // we don't care about speed here, just make sure we have a totally
50425 // distinct object from the previous template
50426 oldTemplate = Lib.extendDeep({}, oldTemplate);
50427
50428 // sort keys so we always get annotationdefaults before annotations etc
50429 // so arrayTemplater will work right
50430 var oldKeys = Object.keys(oldTemplate).sort();
50431 var i, j;
50432
50433 function mergeOne(oldVal, newVal, key) {
50434 if(isPlainObject(newVal) && isPlainObject(oldVal)) {
50435 mergeTemplates(oldVal, newVal);
50436 } else if(Array.isArray(newVal) && Array.isArray(oldVal)) {
50437 // Note: omitted `inclusionAttr` from arrayTemplater here,
50438 // it's irrelevant as we only want the resulting `_template`.
50439 var templater = Template.arrayTemplater({_template: oldTemplate}, key);
50440 for(j = 0; j < newVal.length; j++) {
50441 var item = newVal[j];
50442 var oldItem = templater.newItem(item)._template;
50443 if(oldItem) mergeTemplates(oldItem, item);
50444 }
50445 var defaultItems = templater.defaultItems();
50446 for(j = 0; j < defaultItems.length; j++) newVal.push(defaultItems[j]._template);
50447
50448 // templateitemname only applies to receiving plots
50449 for(j = 0; j < newVal.length; j++) delete newVal[j].templateitemname;
50450 }
50451 }
50452
50453 for(i = 0; i < oldKeys.length; i++) {
50454 var key = oldKeys[i];
50455 var oldVal = oldTemplate[key];
50456 if(key in newTemplate) {
50457 mergeOne(oldVal, newTemplate[key], key);
50458 } else newTemplate[key] = oldVal;
50459
50460 // if this is a base key from the old template (eg xaxis), look for
50461 // extended keys (eg xaxis2) in the new template to merge into
50462 if(getBaseKey(key) === key) {
50463 for(var key2 in newTemplate) {
50464 var baseKey2 = getBaseKey(key2);
50465 if(key2 !== baseKey2 && baseKey2 === key && !(key2 in oldTemplate)) {
50466 mergeOne(oldVal, newTemplate[key2], key);
50467 }
50468 }
50469 }
50470 }
50471}
50472
50473function getBaseKey(key) {
50474 return key.replace(/[0-9]+$/, '');
50475}
50476
50477function walkStyleKeys(parent, templateOut, getAttributeInfo, path, basePath) {
50478 var pathAttr = basePath && getAttributeInfo(basePath);
50479 for(var key in parent) {
50480 var child = parent[key];
50481 var nextPath = getNextPath(parent, key, path);
50482 var nextBasePath = getNextPath(parent, key, basePath);
50483 var attr = getAttributeInfo(nextBasePath);
50484 if(!attr) {
50485 var baseKey = getBaseKey(key);
50486 if(baseKey !== key) {
50487 nextBasePath = getNextPath(parent, baseKey, basePath);
50488 attr = getAttributeInfo(nextBasePath);
50489 }
50490 }
50491
50492 // we'll get an attr if path starts with a valid part, then has an
50493 // invalid ending. Make sure we got all the way to the end.
50494 if(pathAttr && (pathAttr === attr)) continue;
50495
50496 if(!attr || attr._noTemplating ||
50497 attr.valType === 'data_array' ||
50498 (attr.arrayOk && Array.isArray(child))
50499 ) {
50500 continue;
50501 }
50502
50503 if(!attr.valType && isPlainObject(child)) {
50504 walkStyleKeys(child, templateOut, getAttributeInfo, nextPath, nextBasePath);
50505 } else if(attr._isLinkedToArray && Array.isArray(child)) {
50506 var dfltDone = false;
50507 var namedIndex = 0;
50508 var usedNames = {};
50509 for(var i = 0; i < child.length; i++) {
50510 var item = child[i];
50511 if(isPlainObject(item)) {
50512 var name = item.name;
50513 if(name) {
50514 if(!usedNames[name]) {
50515 // named array items: allow all attributes except data arrays
50516 walkStyleKeys(item, templateOut, getAttributeInfo,
50517 getNextPath(child, namedIndex, nextPath),
50518 getNextPath(child, namedIndex, nextBasePath));
50519 namedIndex++;
50520 usedNames[name] = 1;
50521 }
50522 } else if(!dfltDone) {
50523 var dfltKey = Template.arrayDefaultKey(key);
50524 var dfltPath = getNextPath(parent, dfltKey, path);
50525
50526 // getAttributeInfo will fail if we try to use dfltKey directly.
50527 // Instead put this item into the next array element, then
50528 // pull it out and move it to dfltKey.
50529 var pathInArray = getNextPath(child, namedIndex, nextPath);
50530 walkStyleKeys(item, templateOut, getAttributeInfo, pathInArray,
50531 getNextPath(child, namedIndex, nextBasePath));
50532 var itemPropInArray = Lib.nestedProperty(templateOut, pathInArray);
50533 var dfltProp = Lib.nestedProperty(templateOut, dfltPath);
50534 dfltProp.set(itemPropInArray.get());
50535 itemPropInArray.set(null);
50536
50537 dfltDone = true;
50538 }
50539 }
50540 }
50541 } else {
50542 var templateProp = Lib.nestedProperty(templateOut, nextPath);
50543 templateProp.set(child);
50544 }
50545 }
50546}
50547
50548function getLayoutInfo(layout, path) {
50549 return PlotSchema.getLayoutValObject(
50550 layout, Lib.nestedProperty({}, path).parts
50551 );
50552}
50553
50554function getTraceInfo(trace, path) {
50555 return PlotSchema.getTraceValObject(
50556 trace, Lib.nestedProperty({}, path).parts
50557 );
50558}
50559
50560function getNextPath(parent, key, path) {
50561 var nextPath;
50562 if(!path) nextPath = key;
50563 else if(Array.isArray(parent)) nextPath = path + '[' + key + ']';
50564 else nextPath = path + '.' + key;
50565
50566 return nextPath;
50567}
50568
50569/**
50570 * validateTemplate: Test for consistency between the given figure and
50571 * a template, either already included in the figure or given separately.
50572 * Note that not every issue we identify here is necessarily a problem,
50573 * it depends on what you're using the template for.
50574 *
50575 * @param {object|DOM element} figure: the plot, with {data, layout} members,
50576 * to test the template against
50577 * @param {Optional(object)} template: the template, with its own {data, layout},
50578 * to test. If omitted, we will look for a template already attached as the
50579 * plot's `layout.template` attribute.
50580 *
50581 * @returns {array} array of error objects each containing:
50582 * - {string} code
50583 * error code ('missing', 'unused', 'reused', 'noLayout', 'noData')
50584 * - {string} msg
50585 * a full readable description of the issue.
50586 */
50587exports.validateTemplate = function(figureIn, template) {
50588 var figure = Lib.extendDeep({}, {
50589 _context: dfltConfig,
50590 data: figureIn.data,
50591 layout: figureIn.layout
50592 });
50593 var layout = figure.layout || {};
50594 if(!isPlainObject(template)) template = layout.template || {};
50595 var layoutTemplate = template.layout;
50596 var dataTemplate = template.data;
50597 var errorList = [];
50598
50599 figure.layout = layout;
50600 figure.layout.template = template;
50601 Plots.supplyDefaults(figure);
50602
50603 var fullLayout = figure._fullLayout;
50604 var fullData = figure._fullData;
50605
50606 var layoutPaths = {};
50607 function crawlLayoutForContainers(obj, paths) {
50608 for(var key in obj) {
50609 if(key.charAt(0) !== '_' && isPlainObject(obj[key])) {
50610 var baseKey = getBaseKey(key);
50611 var nextPaths = [];
50612 var i;
50613 for(i = 0; i < paths.length; i++) {
50614 nextPaths.push(getNextPath(obj, key, paths[i]));
50615 if(baseKey !== key) nextPaths.push(getNextPath(obj, baseKey, paths[i]));
50616 }
50617 for(i = 0; i < nextPaths.length; i++) {
50618 layoutPaths[nextPaths[i]] = 1;
50619 }
50620 crawlLayoutForContainers(obj[key], nextPaths);
50621 }
50622 }
50623 }
50624
50625 function crawlLayoutTemplateForContainers(obj, path) {
50626 for(var key in obj) {
50627 if(key.indexOf('defaults') === -1 && isPlainObject(obj[key])) {
50628 var nextPath = getNextPath(obj, key, path);
50629 if(layoutPaths[nextPath]) {
50630 crawlLayoutTemplateForContainers(obj[key], nextPath);
50631 } else {
50632 errorList.push({code: 'unused', path: nextPath});
50633 }
50634 }
50635 }
50636 }
50637
50638 if(!isPlainObject(layoutTemplate)) {
50639 errorList.push({code: 'layout'});
50640 } else {
50641 crawlLayoutForContainers(fullLayout, ['layout']);
50642 crawlLayoutTemplateForContainers(layoutTemplate, 'layout');
50643 }
50644
50645 if(!isPlainObject(dataTemplate)) {
50646 errorList.push({code: 'data'});
50647 } else {
50648 var typeCount = {};
50649 var traceType;
50650 for(var i = 0; i < fullData.length; i++) {
50651 var fullTrace = fullData[i];
50652 traceType = fullTrace.type;
50653 typeCount[traceType] = (typeCount[traceType] || 0) + 1;
50654 if(!fullTrace._fullInput._template) {
50655 // this takes care of the case of traceType in the data but not
50656 // the template
50657 errorList.push({
50658 code: 'missing',
50659 index: fullTrace._fullInput.index,
50660 traceType: traceType
50661 });
50662 }
50663 }
50664 for(traceType in dataTemplate) {
50665 var templateCount = dataTemplate[traceType].length;
50666 var dataCount = typeCount[traceType] || 0;
50667 if(templateCount > dataCount) {
50668 errorList.push({
50669 code: 'unused',
50670 traceType: traceType,
50671 templateCount: templateCount,
50672 dataCount: dataCount
50673 });
50674 } else if(dataCount > templateCount) {
50675 errorList.push({
50676 code: 'reused',
50677 traceType: traceType,
50678 templateCount: templateCount,
50679 dataCount: dataCount
50680 });
50681 }
50682 }
50683 }
50684
50685 // _template: false is when someone tried to modify an array item
50686 // but there was no template with matching name
50687 function crawlForMissingTemplates(obj, path) {
50688 for(var key in obj) {
50689 if(key.charAt(0) === '_') continue;
50690 var val = obj[key];
50691 var nextPath = getNextPath(obj, key, path);
50692 if(isPlainObject(val)) {
50693 if(Array.isArray(obj) && val._template === false && val.templateitemname) {
50694 errorList.push({
50695 code: 'missing',
50696 path: nextPath,
50697 templateitemname: val.templateitemname
50698 });
50699 }
50700 crawlForMissingTemplates(val, nextPath);
50701 } else if(Array.isArray(val) && hasPlainObject(val)) {
50702 crawlForMissingTemplates(val, nextPath);
50703 }
50704 }
50705 }
50706 crawlForMissingTemplates({data: fullData, layout: fullLayout}, '');
50707
50708 if(errorList.length) return errorList.map(format);
50709};
50710
50711function hasPlainObject(arr) {
50712 for(var i = 0; i < arr.length; i++) {
50713 if(isPlainObject(arr[i])) return true;
50714 }
50715}
50716
50717function format(opts) {
50718 var msg;
50719 switch(opts.code) {
50720 case 'data':
50721 msg = 'The template has no key data.';
50722 break;
50723 case 'layout':
50724 msg = 'The template has no key layout.';
50725 break;
50726 case 'missing':
50727 if(opts.path) {
50728 msg = 'There are no templates for item ' + opts.path +
50729 ' with name ' + opts.templateitemname;
50730 } else {
50731 msg = 'There are no templates for trace ' + opts.index +
50732 ', of type ' + opts.traceType + '.';
50733 }
50734 break;
50735 case 'unused':
50736 if(opts.path) {
50737 msg = 'The template item at ' + opts.path +
50738 ' was not used in constructing the plot.';
50739 } else if(opts.dataCount) {
50740 msg = 'Some of the templates of type ' + opts.traceType +
50741 ' were not used. The template has ' + opts.templateCount +
50742 ' traces, the data only has ' + opts.dataCount +
50743 ' of this type.';
50744 } else {
50745 msg = 'The template has ' + opts.templateCount +
50746 ' traces of type ' + opts.traceType +
50747 ' but there are none in the data.';
50748 }
50749 break;
50750 case 'reused':
50751 msg = 'Some of the templates of type ' + opts.traceType +
50752 ' were used more than once. The template has ' +
50753 opts.templateCount + ' traces, the data has ' +
50754 opts.dataCount + ' of this type.';
50755 break;
50756 }
50757 opts.msg = msg;
50758
50759 return opts;
50760}
50761
50762},{"../lib":178,"../plots/attributes":219,"../plots/plots":256,"./plot_config":210,"./plot_schema":211,"./plot_template":212}],215:[function(_dereq_,module,exports){
50763/**
50764* Copyright 2012-2020, Plotly, Inc.
50765* All rights reserved.
50766*
50767* This source code is licensed under the MIT license found in the
50768* LICENSE file in the root directory of this source tree.
50769*/
50770
50771'use strict';
50772
50773var isNumeric = _dereq_('fast-isnumeric');
50774
50775var plotApi = _dereq_('./plot_api');
50776var plots = _dereq_('../plots/plots');
50777var Lib = _dereq_('../lib');
50778
50779var helpers = _dereq_('../snapshot/helpers');
50780var toSVG = _dereq_('../snapshot/tosvg');
50781var svgToImg = _dereq_('../snapshot/svgtoimg');
50782var version = _dereq_('../version').version;
50783
50784var attrs = {
50785 format: {
50786 valType: 'enumerated',
50787 values: ['png', 'jpeg', 'webp', 'svg', 'full-json'],
50788 dflt: 'png',
50789
50790 },
50791 width: {
50792 valType: 'number',
50793 min: 1,
50794
50795 },
50796 height: {
50797 valType: 'number',
50798 min: 1,
50799
50800 },
50801 scale: {
50802 valType: 'number',
50803 min: 0,
50804 dflt: 1,
50805
50806 },
50807 setBackground: {
50808 valType: 'any',
50809 dflt: false,
50810
50811 },
50812 imageDataOnly: {
50813 valType: 'boolean',
50814 dflt: false,
50815
50816 }
50817};
50818
50819/** Plotly.toImage
50820 *
50821 * @param {object | string | HTML div} gd
50822 * can either be a data/layout/config object
50823 * or an existing graph <div>
50824 * or an id to an existing graph <div>
50825 * @param {object} opts (see above)
50826 * @return {promise}
50827 */
50828function toImage(gd, opts) {
50829 opts = opts || {};
50830
50831 var data;
50832 var layout;
50833 var config;
50834 var fullLayout;
50835
50836 if(Lib.isPlainObject(gd)) {
50837 data = gd.data || [];
50838 layout = gd.layout || {};
50839 config = gd.config || {};
50840 fullLayout = {};
50841 } else {
50842 gd = Lib.getGraphDiv(gd);
50843 data = Lib.extendDeep([], gd.data);
50844 layout = Lib.extendDeep({}, gd.layout);
50845 config = gd._context;
50846 fullLayout = gd._fullLayout || {};
50847 }
50848
50849 function isImpliedOrValid(attr) {
50850 return !(attr in opts) || Lib.validate(opts[attr], attrs[attr]);
50851 }
50852
50853 if((!isImpliedOrValid('width') && opts.width !== null) ||
50854 (!isImpliedOrValid('height') && opts.height !== null)) {
50855 throw new Error('Height and width should be pixel values.');
50856 }
50857
50858 if(!isImpliedOrValid('format')) {
50859 throw new Error('Image format is not jpeg, png, svg or webp.');
50860 }
50861
50862 var fullOpts = {};
50863
50864 function coerce(attr, dflt) {
50865 return Lib.coerce(opts, fullOpts, attrs, attr, dflt);
50866 }
50867
50868 var format = coerce('format');
50869 var width = coerce('width');
50870 var height = coerce('height');
50871 var scale = coerce('scale');
50872 var setBackground = coerce('setBackground');
50873 var imageDataOnly = coerce('imageDataOnly');
50874
50875 // put the cloned div somewhere off screen before attaching to DOM
50876 var clonedGd = document.createElement('div');
50877 clonedGd.style.position = 'absolute';
50878 clonedGd.style.left = '-5000px';
50879 document.body.appendChild(clonedGd);
50880
50881 // extend layout with image options
50882 var layoutImage = Lib.extendFlat({}, layout);
50883 if(width) {
50884 layoutImage.width = width;
50885 } else if(opts.width === null && isNumeric(fullLayout.width)) {
50886 layoutImage.width = fullLayout.width;
50887 }
50888 if(height) {
50889 layoutImage.height = height;
50890 } else if(opts.height === null && isNumeric(fullLayout.height)) {
50891 layoutImage.height = fullLayout.height;
50892 }
50893
50894 // extend config for static plot
50895 var configImage = Lib.extendFlat({}, config, {
50896 _exportedPlot: true,
50897 staticPlot: true,
50898 setBackground: setBackground
50899 });
50900
50901 var redrawFunc = helpers.getRedrawFunc(clonedGd);
50902
50903 function wait() {
50904 return new Promise(function(resolve) {
50905 setTimeout(resolve, helpers.getDelay(clonedGd._fullLayout));
50906 });
50907 }
50908
50909 function convert() {
50910 return new Promise(function(resolve, reject) {
50911 var svg = toSVG(clonedGd, format, scale);
50912 var width = clonedGd._fullLayout.width;
50913 var height = clonedGd._fullLayout.height;
50914
50915 function cleanup() {
50916 plotApi.purge(clonedGd);
50917 document.body.removeChild(clonedGd);
50918 }
50919
50920 if(format === 'full-json') {
50921 var json = plots.graphJson(clonedGd, false, 'keepdata', 'object', true, true);
50922 json.version = version;
50923 json = JSON.stringify(json);
50924 cleanup();
50925 if(imageDataOnly) {
50926 return resolve(json);
50927 } else {
50928 return resolve(helpers.encodeJSON(json));
50929 }
50930 }
50931
50932 cleanup();
50933
50934 if(format === 'svg') {
50935 if(imageDataOnly) {
50936 return resolve(svg);
50937 } else {
50938 return resolve(helpers.encodeSVG(svg));
50939 }
50940 }
50941
50942 var canvas = document.createElement('canvas');
50943 canvas.id = Lib.randstr();
50944
50945 svgToImg({
50946 format: format,
50947 width: width,
50948 height: height,
50949 scale: scale,
50950 canvas: canvas,
50951 svg: svg,
50952 // ask svgToImg to return a Promise
50953 // rather than EventEmitter
50954 // leave EventEmitter for backward
50955 // compatibility
50956 promise: true
50957 })
50958 .then(resolve)
50959 .catch(reject);
50960 });
50961 }
50962
50963 function urlToImageData(url) {
50964 if(imageDataOnly) {
50965 return url.replace(helpers.IMAGE_URL_PREFIX, '');
50966 } else {
50967 return url;
50968 }
50969 }
50970
50971 return new Promise(function(resolve, reject) {
50972 plotApi.plot(clonedGd, data, layoutImage, configImage)
50973 .then(redrawFunc)
50974 .then(wait)
50975 .then(convert)
50976 .then(function(url) { resolve(urlToImageData(url)); })
50977 .catch(function(err) { reject(err); });
50978 });
50979}
50980
50981module.exports = toImage;
50982
50983},{"../lib":178,"../plots/plots":256,"../snapshot/helpers":273,"../snapshot/svgtoimg":275,"../snapshot/tosvg":277,"../version":435,"./plot_api":209,"fast-isnumeric":18}],216:[function(_dereq_,module,exports){
50984/**
50985* Copyright 2012-2020, Plotly, Inc.
50986* All rights reserved.
50987*
50988* This source code is licensed under the MIT license found in the
50989* LICENSE file in the root directory of this source tree.
50990*/
50991
50992'use strict';
50993
50994var Lib = _dereq_('../lib');
50995var Plots = _dereq_('../plots/plots');
50996var PlotSchema = _dereq_('./plot_schema');
50997var dfltConfig = _dereq_('./plot_config').dfltConfig;
50998
50999var isPlainObject = Lib.isPlainObject;
51000var isArray = Array.isArray;
51001var isArrayOrTypedArray = Lib.isArrayOrTypedArray;
51002
51003/**
51004 * Validate a data array and layout object.
51005 *
51006 * @param {array} data
51007 * @param {object} layout
51008 *
51009 * @return {array} array of error objects each containing:
51010 * - {string} code
51011 * error code ('object', 'array', 'schema', 'unused', 'invisible' or 'value')
51012 * - {string} container
51013 * container where the error occurs ('data' or 'layout')
51014 * - {number} trace
51015 * trace index of the 'data' container where the error occurs
51016 * - {array} path
51017 * nested path to the key that causes the error
51018 * - {string} astr
51019 * attribute string variant of 'path' compatible with Plotly.restyle and
51020 * Plotly.relayout.
51021 * - {string} msg
51022 * error message (shown in console in logger config argument is enable)
51023 */
51024module.exports = function validate(data, layout) {
51025 var schema = PlotSchema.get();
51026 var errorList = [];
51027 var gd = {_context: Lib.extendFlat({}, dfltConfig)};
51028
51029 var dataIn, layoutIn;
51030
51031 if(isArray(data)) {
51032 gd.data = Lib.extendDeep([], data);
51033 dataIn = data;
51034 } else {
51035 gd.data = [];
51036 dataIn = [];
51037 errorList.push(format('array', 'data'));
51038 }
51039
51040 if(isPlainObject(layout)) {
51041 gd.layout = Lib.extendDeep({}, layout);
51042 layoutIn = layout;
51043 } else {
51044 gd.layout = {};
51045 layoutIn = {};
51046 if(arguments.length > 1) {
51047 errorList.push(format('object', 'layout'));
51048 }
51049 }
51050
51051 // N.B. dataIn and layoutIn are in general not the same as
51052 // gd.data and gd.layout after supplyDefaults as some attributes
51053 // in gd.data and gd.layout (still) get mutated during this step.
51054
51055 Plots.supplyDefaults(gd);
51056
51057 var dataOut = gd._fullData;
51058 var len = dataIn.length;
51059
51060 for(var i = 0; i < len; i++) {
51061 var traceIn = dataIn[i];
51062 var base = ['data', i];
51063
51064 if(!isPlainObject(traceIn)) {
51065 errorList.push(format('object', base));
51066 continue;
51067 }
51068
51069 var traceOut = dataOut[i];
51070 var traceType = traceOut.type;
51071 var traceSchema = schema.traces[traceType].attributes;
51072
51073 // PlotSchema does something fancy with trace 'type', reset it here
51074 // to make the trace schema compatible with Lib.validate.
51075 traceSchema.type = {
51076 valType: 'enumerated',
51077 values: [traceType]
51078 };
51079
51080 if(traceOut.visible === false && traceIn.visible !== false) {
51081 errorList.push(format('invisible', base));
51082 }
51083
51084 crawl(traceIn, traceOut, traceSchema, errorList, base);
51085
51086 var transformsIn = traceIn.transforms;
51087 var transformsOut = traceOut.transforms;
51088
51089 if(transformsIn) {
51090 if(!isArray(transformsIn)) {
51091 errorList.push(format('array', base, ['transforms']));
51092 }
51093
51094 base.push('transforms');
51095
51096 for(var j = 0; j < transformsIn.length; j++) {
51097 var path = ['transforms', j];
51098 var transformType = transformsIn[j].type;
51099
51100 if(!isPlainObject(transformsIn[j])) {
51101 errorList.push(format('object', base, path));
51102 continue;
51103 }
51104
51105 var transformSchema = schema.transforms[transformType] ?
51106 schema.transforms[transformType].attributes :
51107 {};
51108
51109 // add 'type' to transform schema to validate the transform type
51110 transformSchema.type = {
51111 valType: 'enumerated',
51112 values: Object.keys(schema.transforms)
51113 };
51114
51115 crawl(transformsIn[j], transformsOut[j], transformSchema, errorList, base, path);
51116 }
51117 }
51118 }
51119
51120 var layoutOut = gd._fullLayout;
51121 var layoutSchema = fillLayoutSchema(schema, dataOut);
51122
51123 crawl(layoutIn, layoutOut, layoutSchema, errorList, 'layout');
51124
51125 // return undefined if no validation errors were found
51126 return (errorList.length === 0) ? void(0) : errorList;
51127};
51128
51129function crawl(objIn, objOut, schema, list, base, path) {
51130 path = path || [];
51131
51132 var keys = Object.keys(objIn);
51133
51134 for(var i = 0; i < keys.length; i++) {
51135 var k = keys[i];
51136
51137 // transforms are handled separately
51138 if(k === 'transforms') continue;
51139
51140 var p = path.slice();
51141 p.push(k);
51142
51143 var valIn = objIn[k];
51144 var valOut = objOut[k];
51145
51146 var nestedSchema = getNestedSchema(schema, k);
51147 var nestedValType = (nestedSchema || {}).valType;
51148 var isInfoArray = nestedValType === 'info_array';
51149 var isColorscale = nestedValType === 'colorscale';
51150 var items = (nestedSchema || {}).items;
51151
51152 if(!isInSchema(schema, k)) {
51153 list.push(format('schema', base, p));
51154 } else if(isPlainObject(valIn) && isPlainObject(valOut) && nestedValType !== 'any') {
51155 crawl(valIn, valOut, nestedSchema, list, base, p);
51156 } else if(isInfoArray && isArray(valIn)) {
51157 if(valIn.length > valOut.length) {
51158 list.push(format('unused', base, p.concat(valOut.length)));
51159 }
51160 var len = valOut.length;
51161 var arrayItems = Array.isArray(items);
51162 if(arrayItems) len = Math.min(len, items.length);
51163 var m, n, item, valInPart, valOutPart;
51164 if(nestedSchema.dimensions === 2) {
51165 for(n = 0; n < len; n++) {
51166 if(isArray(valIn[n])) {
51167 if(valIn[n].length > valOut[n].length) {
51168 list.push(format('unused', base, p.concat(n, valOut[n].length)));
51169 }
51170 var len2 = valOut[n].length;
51171 for(m = 0; m < (arrayItems ? Math.min(len2, items[n].length) : len2); m++) {
51172 item = arrayItems ? items[n][m] : items;
51173 valInPart = valIn[n][m];
51174 valOutPart = valOut[n][m];
51175 if(!Lib.validate(valInPart, item)) {
51176 list.push(format('value', base, p.concat(n, m), valInPart));
51177 } else if(valOutPart !== valInPart && valOutPart !== +valInPart) {
51178 list.push(format('dynamic', base, p.concat(n, m), valInPart, valOutPart));
51179 }
51180 }
51181 } else {
51182 list.push(format('array', base, p.concat(n), valIn[n]));
51183 }
51184 }
51185 } else {
51186 for(n = 0; n < len; n++) {
51187 item = arrayItems ? items[n] : items;
51188 valInPart = valIn[n];
51189 valOutPart = valOut[n];
51190 if(!Lib.validate(valInPart, item)) {
51191 list.push(format('value', base, p.concat(n), valInPart));
51192 } else if(valOutPart !== valInPart && valOutPart !== +valInPart) {
51193 list.push(format('dynamic', base, p.concat(n), valInPart, valOutPart));
51194 }
51195 }
51196 }
51197 } else if(nestedSchema.items && !isInfoArray && isArray(valIn)) {
51198 var _nestedSchema = items[Object.keys(items)[0]];
51199 var indexList = [];
51200
51201 var j, _p;
51202
51203 // loop over valOut items while keeping track of their
51204 // corresponding input container index (given by _index)
51205 for(j = 0; j < valOut.length; j++) {
51206 var _index = valOut[j]._index || j;
51207
51208 _p = p.slice();
51209 _p.push(_index);
51210
51211 if(isPlainObject(valIn[_index]) && isPlainObject(valOut[j])) {
51212 indexList.push(_index);
51213 var valInj = valIn[_index];
51214 var valOutj = valOut[j];
51215 if(isPlainObject(valInj) && valInj.visible !== false && valOutj.visible === false) {
51216 list.push(format('invisible', base, _p));
51217 } else crawl(valInj, valOutj, _nestedSchema, list, base, _p);
51218 }
51219 }
51220
51221 // loop over valIn to determine where it went wrong for some items
51222 for(j = 0; j < valIn.length; j++) {
51223 _p = p.slice();
51224 _p.push(j);
51225
51226 if(!isPlainObject(valIn[j])) {
51227 list.push(format('object', base, _p, valIn[j]));
51228 } else if(indexList.indexOf(j) === -1) {
51229 list.push(format('unused', base, _p));
51230 }
51231 }
51232 } else if(!isPlainObject(valIn) && isPlainObject(valOut)) {
51233 list.push(format('object', base, p, valIn));
51234 } else if(!isArrayOrTypedArray(valIn) && isArrayOrTypedArray(valOut) && !isInfoArray && !isColorscale) {
51235 list.push(format('array', base, p, valIn));
51236 } else if(!(k in objOut)) {
51237 list.push(format('unused', base, p, valIn));
51238 } else if(!Lib.validate(valIn, nestedSchema)) {
51239 list.push(format('value', base, p, valIn));
51240 } else if(nestedSchema.valType === 'enumerated' &&
51241 ((nestedSchema.coerceNumber && valIn !== +valOut) || valIn !== valOut)
51242 ) {
51243 list.push(format('dynamic', base, p, valIn, valOut));
51244 }
51245 }
51246
51247 return list;
51248}
51249
51250// the 'full' layout schema depends on the traces types presents
51251function fillLayoutSchema(schema, dataOut) {
51252 var layoutSchema = schema.layout.layoutAttributes;
51253
51254 for(var i = 0; i < dataOut.length; i++) {
51255 var traceOut = dataOut[i];
51256 var traceSchema = schema.traces[traceOut.type];
51257 var traceLayoutAttr = traceSchema.layoutAttributes;
51258
51259 if(traceLayoutAttr) {
51260 if(traceOut.subplot) {
51261 Lib.extendFlat(layoutSchema[traceSchema.attributes.subplot.dflt], traceLayoutAttr);
51262 } else {
51263 Lib.extendFlat(layoutSchema, traceLayoutAttr);
51264 }
51265 }
51266 }
51267
51268 return layoutSchema;
51269}
51270
51271// validation error codes
51272var code2msgFunc = {
51273 object: function(base, astr) {
51274 var prefix;
51275
51276 if(base === 'layout' && astr === '') prefix = 'The layout argument';
51277 else if(base[0] === 'data' && astr === '') {
51278 prefix = 'Trace ' + base[1] + ' in the data argument';
51279 } else prefix = inBase(base) + 'key ' + astr;
51280
51281 return prefix + ' must be linked to an object container';
51282 },
51283 array: function(base, astr) {
51284 var prefix;
51285
51286 if(base === 'data') prefix = 'The data argument';
51287 else prefix = inBase(base) + 'key ' + astr;
51288
51289 return prefix + ' must be linked to an array container';
51290 },
51291 schema: function(base, astr) {
51292 return inBase(base) + 'key ' + astr + ' is not part of the schema';
51293 },
51294 unused: function(base, astr, valIn) {
51295 var target = isPlainObject(valIn) ? 'container' : 'key';
51296
51297 return inBase(base) + target + ' ' + astr + ' did not get coerced';
51298 },
51299 dynamic: function(base, astr, valIn, valOut) {
51300 return [
51301 inBase(base) + 'key',
51302 astr,
51303 '(set to \'' + valIn + '\')',
51304 'got reset to',
51305 '\'' + valOut + '\'',
51306 'during defaults.'
51307 ].join(' ');
51308 },
51309 invisible: function(base, astr) {
51310 return (
51311 astr ? (inBase(base) + 'item ' + astr) : ('Trace ' + base[1])
51312 ) + ' got defaulted to be not visible';
51313 },
51314 value: function(base, astr, valIn) {
51315 return [
51316 inBase(base) + 'key ' + astr,
51317 'is set to an invalid value (' + valIn + ')'
51318 ].join(' ');
51319 }
51320};
51321
51322function inBase(base) {
51323 if(isArray(base)) return 'In data trace ' + base[1] + ', ';
51324
51325 return 'In ' + base + ', ';
51326}
51327
51328function format(code, base, path, valIn, valOut) {
51329 path = path || '';
51330
51331 var container, trace;
51332
51333 // container is either 'data' or 'layout
51334 // trace is the trace index if 'data', null otherwise
51335
51336 if(isArray(base)) {
51337 container = base[0];
51338 trace = base[1];
51339 } else {
51340 container = base;
51341 trace = null;
51342 }
51343
51344 var astr = convertPathToAttributeString(path);
51345 var msg = code2msgFunc[code](base, astr, valIn, valOut);
51346
51347 // log to console if logger config option is enabled
51348 Lib.log(msg);
51349
51350 return {
51351 code: code,
51352 container: container,
51353 trace: trace,
51354 path: path,
51355 astr: astr,
51356 msg: msg
51357 };
51358}
51359
51360function isInSchema(schema, key) {
51361 var parts = splitKey(key);
51362 var keyMinusId = parts.keyMinusId;
51363 var id = parts.id;
51364
51365 if((keyMinusId in schema) && schema[keyMinusId]._isSubplotObj && id) {
51366 return true;
51367 }
51368
51369 return (key in schema);
51370}
51371
51372function getNestedSchema(schema, key) {
51373 if(key in schema) return schema[key];
51374
51375 var parts = splitKey(key);
51376
51377 return schema[parts.keyMinusId];
51378}
51379
51380var idRegex = Lib.counterRegex('([a-z]+)');
51381
51382function splitKey(key) {
51383 var idMatch = key.match(idRegex);
51384
51385 return {
51386 keyMinusId: idMatch && idMatch[1],
51387 id: idMatch && idMatch[2]
51388 };
51389}
51390
51391function convertPathToAttributeString(path) {
51392 if(!isArray(path)) return String(path);
51393
51394 var astr = '';
51395
51396 for(var i = 0; i < path.length; i++) {
51397 var p = path[i];
51398
51399 if(typeof p === 'number') {
51400 astr = astr.substr(0, astr.length - 1) + '[' + p + ']';
51401 } else {
51402 astr += p;
51403 }
51404
51405 if(i < path.length - 1) astr += '.';
51406 }
51407
51408 return astr;
51409}
51410
51411},{"../lib":178,"../plots/plots":256,"./plot_config":210,"./plot_schema":211}],217:[function(_dereq_,module,exports){
51412/**
51413* Copyright 2012-2020, Plotly, Inc.
51414* All rights reserved.
51415*
51416* This source code is licensed under the MIT license found in the
51417* LICENSE file in the root directory of this source tree.
51418*/
51419
51420'use strict';
51421
51422module.exports = {
51423 mode: {
51424 valType: 'enumerated',
51425 dflt: 'afterall',
51426
51427 values: ['immediate', 'next', 'afterall'],
51428
51429 },
51430 direction: {
51431 valType: 'enumerated',
51432
51433 values: ['forward', 'reverse'],
51434 dflt: 'forward',
51435
51436 },
51437 fromcurrent: {
51438 valType: 'boolean',
51439 dflt: false,
51440
51441
51442 },
51443 frame: {
51444 duration: {
51445 valType: 'number',
51446
51447 min: 0,
51448 dflt: 500,
51449
51450 },
51451 redraw: {
51452 valType: 'boolean',
51453
51454 dflt: true,
51455
51456 },
51457 },
51458 transition: {
51459 duration: {
51460 valType: 'number',
51461
51462 min: 0,
51463 dflt: 500,
51464 editType: 'none',
51465
51466 },
51467 easing: {
51468 valType: 'enumerated',
51469 dflt: 'cubic-in-out',
51470 values: [
51471 'linear',
51472 'quad',
51473 'cubic',
51474 'sin',
51475 'exp',
51476 'circle',
51477 'elastic',
51478 'back',
51479 'bounce',
51480 'linear-in',
51481 'quad-in',
51482 'cubic-in',
51483 'sin-in',
51484 'exp-in',
51485 'circle-in',
51486 'elastic-in',
51487 'back-in',
51488 'bounce-in',
51489 'linear-out',
51490 'quad-out',
51491 'cubic-out',
51492 'sin-out',
51493 'exp-out',
51494 'circle-out',
51495 'elastic-out',
51496 'back-out',
51497 'bounce-out',
51498 'linear-in-out',
51499 'quad-in-out',
51500 'cubic-in-out',
51501 'sin-in-out',
51502 'exp-in-out',
51503 'circle-in-out',
51504 'elastic-in-out',
51505 'back-in-out',
51506 'bounce-in-out'
51507 ],
51508
51509 editType: 'none',
51510
51511 },
51512 ordering: {
51513 valType: 'enumerated',
51514 values: ['layout first', 'traces first'],
51515 dflt: 'layout first',
51516
51517 editType: 'none',
51518
51519 }
51520 }
51521};
51522
51523},{}],218:[function(_dereq_,module,exports){
51524/**
51525* Copyright 2012-2020, Plotly, Inc.
51526* All rights reserved.
51527*
51528* This source code is licensed under the MIT license found in the
51529* LICENSE file in the root directory of this source tree.
51530*/
51531
51532'use strict';
51533
51534var Lib = _dereq_('../lib');
51535var Template = _dereq_('../plot_api/plot_template');
51536
51537/** Convenience wrapper for making array container logic DRY and consistent
51538 *
51539 * @param {object} parentObjIn
51540 * user input object where the container in question is linked
51541 * (i.e. either a user trace object or the user layout object)
51542 *
51543 * @param {object} parentObjOut
51544 * full object where the coerced container will be linked
51545 * (i.e. either a full trace object or the full layout object)
51546 *
51547 * @param {object} opts
51548 * options object:
51549 * - name {string}
51550 * name of the key linking the container in question
51551 * - inclusionAttr {string}
51552 * name of the item attribute for inclusion/exclusion. Default is 'visible'.
51553 * Since inclusion is true, use eg 'enabled' instead of 'disabled'.
51554 * - handleItemDefaults {function}
51555 * defaults method to be called on each item in the array container in question
51556 *
51557 * Its arguments are:
51558 * - itemIn {object} item in user layout
51559 * - itemOut {object} item in full layout
51560 * - parentObj {object} (as in closure)
51561 * - opts {object} (as in closure)
51562 * N.B.
51563 *
51564 * - opts is passed to handleItemDefaults so it can also store
51565 * links to supplementary data (e.g. fullData for layout components)
51566 *
51567 */
51568module.exports = function handleArrayContainerDefaults(parentObjIn, parentObjOut, opts) {
51569 var name = opts.name;
51570 var inclusionAttr = opts.inclusionAttr || 'visible';
51571
51572 var previousContOut = parentObjOut[name];
51573
51574 var contIn = Lib.isArrayOrTypedArray(parentObjIn[name]) ? parentObjIn[name] : [];
51575 var contOut = parentObjOut[name] = [];
51576 var templater = Template.arrayTemplater(parentObjOut, name, inclusionAttr);
51577 var i, itemOut;
51578
51579 for(i = 0; i < contIn.length; i++) {
51580 var itemIn = contIn[i];
51581
51582 if(!Lib.isPlainObject(itemIn)) {
51583 itemOut = templater.newItem({});
51584 itemOut[inclusionAttr] = false;
51585 } else {
51586 itemOut = templater.newItem(itemIn);
51587 }
51588
51589 itemOut._index = i;
51590
51591 if(itemOut[inclusionAttr] !== false) {
51592 opts.handleItemDefaults(itemIn, itemOut, parentObjOut, opts);
51593 }
51594
51595 contOut.push(itemOut);
51596 }
51597
51598 var defaultItems = templater.defaultItems();
51599 for(i = 0; i < defaultItems.length; i++) {
51600 itemOut = defaultItems[i];
51601 itemOut._index = contOut.length;
51602 opts.handleItemDefaults({}, itemOut, parentObjOut, opts, {});
51603 contOut.push(itemOut);
51604 }
51605
51606 // in case this array gets its defaults rebuilt independent of the whole layout,
51607 // relink the private keys just for this array.
51608 if(Lib.isArrayOrTypedArray(previousContOut)) {
51609 var len = Math.min(previousContOut.length, contOut.length);
51610 for(i = 0; i < len; i++) {
51611 Lib.relinkPrivateKeys(contOut[i], previousContOut[i]);
51612 }
51613 }
51614
51615 return contOut;
51616};
51617
51618},{"../lib":178,"../plot_api/plot_template":212}],219:[function(_dereq_,module,exports){
51619/**
51620* Copyright 2012-2020, Plotly, Inc.
51621* All rights reserved.
51622*
51623* This source code is licensed under the MIT license found in the
51624* LICENSE file in the root directory of this source tree.
51625*/
51626
51627'use strict';
51628
51629var fxAttrs = _dereq_('../components/fx/attributes');
51630
51631module.exports = {
51632 type: {
51633 valType: 'enumerated',
51634
51635 values: [], // listed dynamically
51636 dflt: 'scatter',
51637 editType: 'calc+clearAxisTypes',
51638 _noTemplating: true // we handle this at a higher level
51639 },
51640 visible: {
51641 valType: 'enumerated',
51642 values: [true, false, 'legendonly'],
51643
51644 dflt: true,
51645 editType: 'calc',
51646
51647 },
51648 showlegend: {
51649 valType: 'boolean',
51650
51651 dflt: true,
51652 editType: 'style',
51653
51654 },
51655 legendgroup: {
51656 valType: 'string',
51657
51658 dflt: '',
51659 editType: 'style',
51660
51661 },
51662 opacity: {
51663 valType: 'number',
51664
51665 min: 0,
51666 max: 1,
51667 dflt: 1,
51668 editType: 'style',
51669
51670 },
51671 name: {
51672 valType: 'string',
51673
51674 editType: 'style',
51675
51676 },
51677 uid: {
51678 valType: 'string',
51679
51680 editType: 'plot',
51681 anim: true,
51682
51683 },
51684 ids: {
51685 valType: 'data_array',
51686 editType: 'calc',
51687 anim: true,
51688
51689 },
51690 customdata: {
51691 valType: 'data_array',
51692 editType: 'calc',
51693
51694 },
51695 meta: {
51696 valType: 'any',
51697 arrayOk: true,
51698
51699 editType: 'plot',
51700
51701 },
51702
51703 // N.B. these cannot be 'data_array' as they do not have the same length as
51704 // other data arrays and arrayOk attributes in general
51705 //
51706 // Maybe add another valType:
51707 // https://github.com/plotly/plotly.js/issues/1894
51708 selectedpoints: {
51709 valType: 'any',
51710
51711 editType: 'calc',
51712
51713 },
51714
51715 hoverinfo: {
51716 valType: 'flaglist',
51717
51718 flags: ['x', 'y', 'z', 'text', 'name'],
51719 extras: ['all', 'none', 'skip'],
51720 arrayOk: true,
51721 dflt: 'all',
51722 editType: 'none',
51723
51724 },
51725 hoverlabel: fxAttrs.hoverlabel,
51726 stream: {
51727 token: {
51728 valType: 'string',
51729 noBlank: true,
51730 strict: true,
51731
51732 editType: 'calc',
51733
51734 },
51735 maxpoints: {
51736 valType: 'number',
51737 min: 0,
51738 max: 10000,
51739 dflt: 500,
51740
51741 editType: 'calc',
51742
51743 },
51744 editType: 'calc'
51745 },
51746 transforms: {
51747 _isLinkedToArray: 'transform',
51748 editType: 'calc',
51749
51750 },
51751 uirevision: {
51752 valType: 'any',
51753
51754 editType: 'none',
51755
51756 }
51757};
51758
51759},{"../components/fx/attributes":83}],220:[function(_dereq_,module,exports){
51760/**
51761* Copyright 2012-2020, Plotly, Inc.
51762* All rights reserved.
51763*
51764* This source code is licensed under the MIT license found in the
51765* LICENSE file in the root directory of this source tree.
51766*/
51767
51768'use strict';
51769
51770
51771module.exports = {
51772 xaxis: {
51773 valType: 'subplotid',
51774
51775 dflt: 'x',
51776 editType: 'calc+clearAxisTypes',
51777
51778 },
51779 yaxis: {
51780 valType: 'subplotid',
51781
51782 dflt: 'y',
51783 editType: 'calc+clearAxisTypes',
51784
51785 }
51786};
51787
51788},{}],221:[function(_dereq_,module,exports){
51789/**
51790* Copyright 2012-2020, Plotly, Inc.
51791* All rights reserved.
51792*
51793* This source code is licensed under the MIT license found in the
51794* LICENSE file in the root directory of this source tree.
51795*/
51796
51797'use strict';
51798
51799var isNumeric = _dereq_('fast-isnumeric');
51800
51801var Lib = _dereq_('../../lib');
51802var FP_SAFE = _dereq_('../../constants/numerical').FP_SAFE;
51803var Registry = _dereq_('../../registry');
51804
51805module.exports = {
51806 getAutoRange: getAutoRange,
51807 makePadFn: makePadFn,
51808 doAutoRange: doAutoRange,
51809 findExtremes: findExtremes,
51810 concatExtremes: concatExtremes
51811};
51812
51813/**
51814 * getAutoRange
51815 *
51816 * Collects all _extremes values corresponding to a given axis
51817 * and computes its auto range.
51818 *
51819 * Note that getAutoRange uses return values from findExtremes.
51820 *
51821 * @param {object} gd:
51822 * graph div object with filled-in fullData and fullLayout, in particular
51823 * with filled-in '_extremes' containers:
51824 * {
51825 * val: calcdata value,
51826 * pad: extra pixels beyond this value,
51827 * extrapad: bool, does this point want 5% extra padding
51828 * }
51829 * @param {object} ax:
51830 * full axis object, in particular with filled-in '_traceIndices'
51831 * and '_annIndices' / '_shapeIndices' if applicable
51832 * @return {array}
51833 * an array of [min, max]. These are calcdata for log and category axes
51834 * and data for linear and date axes.
51835 *
51836 * TODO: we want to change log to data as well, but it's hard to do this
51837 * maintaining backward compatibility. category will always have to use calcdata
51838 * though, because otherwise values between categories (or outside all categories)
51839 * would be impossible.
51840 */
51841function getAutoRange(gd, ax) {
51842 var i, j;
51843 var newRange = [];
51844
51845 var getPad = makePadFn(ax);
51846 var extremes = concatExtremes(gd, ax);
51847 var minArray = extremes.min;
51848 var maxArray = extremes.max;
51849
51850 if(minArray.length === 0 || maxArray.length === 0) {
51851 return Lib.simpleMap(ax.range, ax.r2l);
51852 }
51853
51854 var minmin = minArray[0].val;
51855 var maxmax = maxArray[0].val;
51856
51857 for(i = 1; i < minArray.length; i++) {
51858 if(minmin !== maxmax) break;
51859 minmin = Math.min(minmin, minArray[i].val);
51860 }
51861 for(i = 1; i < maxArray.length; i++) {
51862 if(minmin !== maxmax) break;
51863 maxmax = Math.max(maxmax, maxArray[i].val);
51864 }
51865
51866 var axReverse = false;
51867
51868 if(ax.range) {
51869 var rng = Lib.simpleMap(ax.range, ax.r2l);
51870 axReverse = rng[1] < rng[0];
51871 }
51872 // one-time setting to easily reverse the axis
51873 // when plotting from code
51874 if(ax.autorange === 'reversed') {
51875 axReverse = true;
51876 ax.autorange = true;
51877 }
51878
51879 var rangeMode = ax.rangemode;
51880 var toZero = rangeMode === 'tozero';
51881 var nonNegative = rangeMode === 'nonnegative';
51882 var axLen = ax._length;
51883 // don't allow padding to reduce the data to < 10% of the length
51884 var minSpan = axLen / 10;
51885
51886 // find axis rangebreaks in [v0,v1] and compute its length in value space
51887 var calcBreaksLength = function(v0, v1) {
51888 var lBreaks = 0;
51889 if(ax.rangebreaks) {
51890 var rangebreaksOut = ax.locateBreaks(v0, v1);
51891 for(var i = 0; i < rangebreaksOut.length; i++) {
51892 var brk = rangebreaksOut[i];
51893 lBreaks += brk.max - brk.min;
51894 }
51895 }
51896 return lBreaks;
51897 };
51898
51899 var mbest = 0;
51900 var minpt, maxpt, minbest, maxbest, dp, dv;
51901
51902 for(i = 0; i < minArray.length; i++) {
51903 minpt = minArray[i];
51904 for(j = 0; j < maxArray.length; j++) {
51905 maxpt = maxArray[j];
51906 dv = maxpt.val - minpt.val - calcBreaksLength(minpt.val, maxpt.val);
51907 if(dv > 0) {
51908 dp = axLen - getPad(minpt) - getPad(maxpt);
51909 if(dp > minSpan) {
51910 if(dv / dp > mbest) {
51911 minbest = minpt;
51912 maxbest = maxpt;
51913 mbest = dv / dp;
51914 }
51915 } else if(dv / axLen > mbest) {
51916 // in case of padding longer than the axis
51917 // at least include the unpadded data values.
51918 minbest = {val: minpt.val, pad: 0};
51919 maxbest = {val: maxpt.val, pad: 0};
51920 mbest = dv / axLen;
51921 }
51922 }
51923 }
51924 }
51925
51926 function getMaxPad(prev, pt) {
51927 return Math.max(prev, getPad(pt));
51928 }
51929
51930 if(minmin === maxmax) {
51931 var lower = minmin - 1;
51932 var upper = minmin + 1;
51933 if(toZero) {
51934 if(minmin === 0) {
51935 // The only value we have on this axis is 0, and we want to
51936 // autorange so zero is one end.
51937 // In principle this could be [0, 1] or [-1, 0] but usually
51938 // 'tozero' pins 0 to the low end, so follow that.
51939 newRange = [0, 1];
51940 } else {
51941 var maxPad = (minmin > 0 ? maxArray : minArray).reduce(getMaxPad, 0);
51942 // we're pushing a single value away from the edge due to its
51943 // padding, with the other end clamped at zero
51944 // 0.5 means don't push it farther than the center.
51945 var rangeEnd = minmin / (1 - Math.min(0.5, maxPad / axLen));
51946 newRange = minmin > 0 ? [0, rangeEnd] : [rangeEnd, 0];
51947 }
51948 } else if(nonNegative) {
51949 newRange = [Math.max(0, lower), Math.max(1, upper)];
51950 } else {
51951 newRange = [lower, upper];
51952 }
51953 } else {
51954 if(toZero) {
51955 if(minbest.val >= 0) {
51956 minbest = {val: 0, pad: 0};
51957 }
51958 if(maxbest.val <= 0) {
51959 maxbest = {val: 0, pad: 0};
51960 }
51961 } else if(nonNegative) {
51962 if(minbest.val - mbest * getPad(minbest) < 0) {
51963 minbest = {val: 0, pad: 0};
51964 }
51965 if(maxbest.val <= 0) {
51966 maxbest = {val: 1, pad: 0};
51967 }
51968 }
51969
51970 // in case it changed again...
51971 mbest = (maxbest.val - minbest.val - calcBreaksLength(minpt.val, maxpt.val)) /
51972 (axLen - getPad(minbest) - getPad(maxbest));
51973
51974 newRange = [
51975 minbest.val - mbest * getPad(minbest),
51976 maxbest.val + mbest * getPad(maxbest)
51977 ];
51978 }
51979
51980 // maintain reversal
51981 if(axReverse) newRange.reverse();
51982
51983 return Lib.simpleMap(newRange, ax.l2r || Number);
51984}
51985
51986/*
51987 * calculate the pixel padding for ax._min and ax._max entries with
51988 * optional extrapad as 5% of the total axis length
51989 */
51990function makePadFn(ax) {
51991 // 5% padding for points that specify extrapad: true
51992 var extrappad = ax._length / 20;
51993
51994 // domain-constrained axes: base extrappad on the unconstrained
51995 // domain so it's consistent as the domain changes
51996 if((ax.constrain === 'domain') && ax._inputDomain) {
51997 extrappad *= (ax._inputDomain[1] - ax._inputDomain[0]) /
51998 (ax.domain[1] - ax.domain[0]);
51999 }
52000
52001 return function getPad(pt) { return pt.pad + (pt.extrapad ? extrappad : 0); };
52002}
52003
52004function concatExtremes(gd, ax) {
52005 var axId = ax._id;
52006 var fullData = gd._fullData;
52007 var fullLayout = gd._fullLayout;
52008 var minArray = [];
52009 var maxArray = [];
52010 var i, j, d;
52011
52012 function _concat(cont, indices) {
52013 for(i = 0; i < indices.length; i++) {
52014 var item = cont[indices[i]];
52015 var extremes = (item._extremes || {})[axId];
52016 if(item.visible === true && extremes) {
52017 for(j = 0; j < extremes.min.length; j++) {
52018 d = extremes.min[j];
52019 collapseMinArray(minArray, d.val, d.pad, {extrapad: d.extrapad});
52020 }
52021 for(j = 0; j < extremes.max.length; j++) {
52022 d = extremes.max[j];
52023 collapseMaxArray(maxArray, d.val, d.pad, {extrapad: d.extrapad});
52024 }
52025 }
52026 }
52027 }
52028
52029 _concat(fullData, ax._traceIndices);
52030 _concat(fullLayout.annotations || [], ax._annIndices || []);
52031 _concat(fullLayout.shapes || [], ax._shapeIndices || []);
52032
52033 return {min: minArray, max: maxArray};
52034}
52035
52036function doAutoRange(gd, ax) {
52037 ax.setScale();
52038
52039 if(ax.autorange) {
52040 ax.range = getAutoRange(gd, ax);
52041
52042 ax._r = ax.range.slice();
52043 ax._rl = Lib.simpleMap(ax._r, ax.r2l);
52044
52045 // doAutoRange will get called on fullLayout,
52046 // but we want to report its results back to layout
52047
52048 var axIn = ax._input;
52049
52050 // before we edit _input, store preGUI values
52051 var edits = {};
52052 edits[ax._attr + '.range'] = ax.range;
52053 edits[ax._attr + '.autorange'] = ax.autorange;
52054 Registry.call('_storeDirectGUIEdit', gd.layout, gd._fullLayout._preGUI, edits);
52055
52056 axIn.range = ax.range.slice();
52057 axIn.autorange = ax.autorange;
52058 }
52059
52060 var anchorAx = ax._anchorAxis;
52061
52062 if(anchorAx && anchorAx.rangeslider) {
52063 var axeRangeOpts = anchorAx.rangeslider[ax._name];
52064 if(axeRangeOpts) {
52065 if(axeRangeOpts.rangemode === 'auto') {
52066 axeRangeOpts.range = getAutoRange(gd, ax);
52067 }
52068 }
52069 anchorAx._input.rangeslider[ax._name] = Lib.extendFlat({}, axeRangeOpts);
52070 }
52071}
52072
52073/**
52074 * findExtremes
52075 *
52076 * Find min/max extremes of an array of coordinates on a given axis.
52077 *
52078 * Note that findExtremes is called during `calc`, when we don't yet know the axis
52079 * length; all the inputs should be based solely on the trace data, nothing
52080 * about the axis layout.
52081 *
52082 * Note that `ppad` and `vpad` as well as their asymmetric variants refer to
52083 * the before and after padding of the passed `data` array, not to the whole axis.
52084 *
52085 * @param {object} ax: full axis object
52086 * relies on
52087 * - ax.type
52088 * - ax._m (just its sign)
52089 * - ax.d2l
52090 * @param {array} data:
52091 * array of numbers (i.e. already run though ax.d2c)
52092 * @param {object} opts:
52093 * available keys are:
52094 * vpad: (number or number array) pad values (data value +-vpad)
52095 * ppad: (number or number array) pad pixels (pixel location +-ppad)
52096 * ppadplus, ppadminus, vpadplus, vpadminus:
52097 * separate padding for each side, overrides symmetric
52098 * padded: (boolean) add 5% padding to both ends
52099 * (unless one end is overridden by tozero)
52100 * tozero: (boolean) make sure to include zero if axis is linear,
52101 * and make it a tight bound if possible
52102 * vpadLinearized: (boolean) whether or not vpad (or vpadplus/vpadminus)
52103 * is linearized (for log scale axes)
52104 *
52105 * @return {object}
52106 * - min {array of objects}
52107 * - max {array of objects}
52108 * each object item has fields:
52109 * - val {number}
52110 * - pad {number}
52111 * - extrappad {number}
52112 * - opts {object}: a ref to the passed "options" object
52113 */
52114function findExtremes(ax, data, opts) {
52115 if(!opts) opts = {};
52116 if(!ax._m) ax.setScale();
52117
52118 var minArray = [];
52119 var maxArray = [];
52120
52121 var len = data.length;
52122 var extrapad = opts.padded || false;
52123 var tozero = opts.tozero && (ax.type === 'linear' || ax.type === '-');
52124 var isLog = ax.type === 'log';
52125 var hasArrayOption = false;
52126 var vpadLinearized = opts.vpadLinearized || false;
52127 var i, v, di, dmin, dmax, ppadiplus, ppadiminus, vmin, vmax;
52128
52129 function makePadAccessor(item) {
52130 if(Array.isArray(item)) {
52131 hasArrayOption = true;
52132 return function(i) { return Math.max(Number(item[i]||0), 0); };
52133 } else {
52134 var v = Math.max(Number(item||0), 0);
52135 return function() { return v; };
52136 }
52137 }
52138
52139 var ppadplus = makePadAccessor((ax._m > 0 ?
52140 opts.ppadplus : opts.ppadminus) || opts.ppad || 0);
52141 var ppadminus = makePadAccessor((ax._m > 0 ?
52142 opts.ppadminus : opts.ppadplus) || opts.ppad || 0);
52143 var vpadplus = makePadAccessor(opts.vpadplus || opts.vpad);
52144 var vpadminus = makePadAccessor(opts.vpadminus || opts.vpad);
52145
52146 if(!hasArrayOption) {
52147 // with no arrays other than `data` we don't need to consider
52148 // every point, only the extreme data points
52149 vmin = Infinity;
52150 vmax = -Infinity;
52151
52152 if(isLog) {
52153 for(i = 0; i < len; i++) {
52154 v = data[i];
52155 // data is not linearized yet so we still have to filter out negative logs
52156 if(v < vmin && v > 0) vmin = v;
52157 if(v > vmax && v < FP_SAFE) vmax = v;
52158 }
52159 } else {
52160 for(i = 0; i < len; i++) {
52161 v = data[i];
52162 if(v < vmin && v > -FP_SAFE) vmin = v;
52163 if(v > vmax && v < FP_SAFE) vmax = v;
52164 }
52165 }
52166
52167 data = [vmin, vmax];
52168 len = 2;
52169 }
52170
52171 var collapseOpts = {tozero: tozero, extrapad: extrapad};
52172
52173 function addItem(i) {
52174 di = data[i];
52175 if(!isNumeric(di)) return;
52176 ppadiplus = ppadplus(i);
52177 ppadiminus = ppadminus(i);
52178
52179 if(vpadLinearized) {
52180 dmin = ax.c2l(di) - vpadminus(i);
52181 dmax = ax.c2l(di) + vpadplus(i);
52182 } else {
52183 vmin = di - vpadminus(i);
52184 vmax = di + vpadplus(i);
52185 // special case for log axes: if vpad makes this object span
52186 // more than an order of mag, clip it to one order. This is so
52187 // we don't have non-positive errors or absurdly large lower
52188 // range due to rounding errors
52189 if(isLog && vmin < vmax / 10) vmin = vmax / 10;
52190
52191 dmin = ax.c2l(vmin);
52192 dmax = ax.c2l(vmax);
52193 }
52194
52195 if(tozero) {
52196 dmin = Math.min(0, dmin);
52197 dmax = Math.max(0, dmax);
52198 }
52199 if(goodNumber(dmin)) {
52200 collapseMinArray(minArray, dmin, ppadiminus, collapseOpts);
52201 }
52202 if(goodNumber(dmax)) {
52203 collapseMaxArray(maxArray, dmax, ppadiplus, collapseOpts);
52204 }
52205 }
52206
52207 // For efficiency covering monotonic or near-monotonic data,
52208 // check a few points at both ends first and then sweep
52209 // through the middle
52210 var iMax = Math.min(6, len);
52211 for(i = 0; i < iMax; i++) addItem(i);
52212 for(i = len - 1; i >= iMax; i--) addItem(i);
52213
52214 return {
52215 min: minArray,
52216 max: maxArray,
52217 opts: opts
52218 };
52219}
52220
52221function collapseMinArray(array, newVal, newPad, opts) {
52222 collapseArray(array, newVal, newPad, opts, lessOrEqual);
52223}
52224
52225function collapseMaxArray(array, newVal, newPad, opts) {
52226 collapseArray(array, newVal, newPad, opts, greaterOrEqual);
52227}
52228
52229/**
52230 * collapseArray
52231 *
52232 * Takes items from 'array' and compares them to 'newVal', 'newPad'.
52233 *
52234 * @param {array} array:
52235 * current set of min or max extremes
52236 * @param {number} newVal:
52237 * new value to compare against
52238 * @param {number} newPad:
52239 * pad value associated with 'newVal'
52240 * @param {object} opts:
52241 * - tozero {boolean}
52242 * - extrapad {number}
52243 * @param {function} atLeastAsExtreme:
52244 * comparison function, use
52245 * - lessOrEqual for min 'array' and
52246 * - greaterOrEqual for max 'array'
52247 *
52248 * In practice, 'array' is either
52249 * - 'extremes[ax._id].min' or
52250 * - 'extremes[ax._id].max
52251 * found in traces and layout items that affect autorange.
52252 *
52253 * Since we don't yet know the relationship between pixels and values
52254 * (that's what we're trying to figure out!) AND we don't yet know how
52255 * many pixels `extrapad` represents (it's going to be 5% of the length,
52256 * but we don't want to have to redo calc just because length changed)
52257 * two point must satisfy three criteria simultaneously for one to supersede the other:
52258 * - at least as extreme a `val`
52259 * - at least as big a `pad`
52260 * - an unpadded point cannot supersede a padded point, but any other combination can
52261 *
52262 * Then:
52263 * - If the item supersedes the new point, set includeThis false
52264 * - If the new pt supersedes the item, delete it from 'array'
52265 */
52266function collapseArray(array, newVal, newPad, opts, atLeastAsExtreme) {
52267 var tozero = opts.tozero;
52268 var extrapad = opts.extrapad;
52269 var includeThis = true;
52270
52271 for(var j = 0; j < array.length && includeThis; j++) {
52272 var v = array[j];
52273 if(atLeastAsExtreme(v.val, newVal) && v.pad >= newPad && (v.extrapad || !extrapad)) {
52274 includeThis = false;
52275 break;
52276 } else if(atLeastAsExtreme(newVal, v.val) && v.pad <= newPad && (extrapad || !v.extrapad)) {
52277 array.splice(j, 1);
52278 j--;
52279 }
52280 }
52281 if(includeThis) {
52282 var clipAtZero = (tozero && newVal === 0);
52283 array.push({
52284 val: newVal,
52285 pad: clipAtZero ? 0 : newPad,
52286 extrapad: clipAtZero ? false : extrapad
52287 });
52288 }
52289}
52290
52291// In order to stop overflow errors, don't consider points
52292// too close to the limits of js floating point
52293function goodNumber(v) {
52294 return isNumeric(v) && Math.abs(v) < FP_SAFE;
52295}
52296
52297function lessOrEqual(v0, v1) { return v0 <= v1; }
52298function greaterOrEqual(v0, v1) { return v0 >= v1; }
52299
52300},{"../../constants/numerical":158,"../../lib":178,"../../registry":269,"fast-isnumeric":18}],222:[function(_dereq_,module,exports){
52301/**
52302* Copyright 2012-2020, Plotly, Inc.
52303* All rights reserved.
52304*
52305* This source code is licensed under the MIT license found in the
52306* LICENSE file in the root directory of this source tree.
52307*/
52308
52309'use strict';
52310
52311var d3 = _dereq_('d3');
52312var isNumeric = _dereq_('fast-isnumeric');
52313var Plots = _dereq_('../../plots/plots');
52314
52315var Registry = _dereq_('../../registry');
52316var Lib = _dereq_('../../lib');
52317var svgTextUtils = _dereq_('../../lib/svg_text_utils');
52318var Titles = _dereq_('../../components/titles');
52319var Color = _dereq_('../../components/color');
52320var Drawing = _dereq_('../../components/drawing');
52321
52322var axAttrs = _dereq_('./layout_attributes');
52323var cleanTicks = _dereq_('./clean_ticks');
52324
52325var constants = _dereq_('../../constants/numerical');
52326var ONEAVGYEAR = constants.ONEAVGYEAR;
52327var ONEAVGMONTH = constants.ONEAVGMONTH;
52328var ONEDAY = constants.ONEDAY;
52329var ONEHOUR = constants.ONEHOUR;
52330var ONEMIN = constants.ONEMIN;
52331var ONESEC = constants.ONESEC;
52332var MINUS_SIGN = constants.MINUS_SIGN;
52333var BADNUM = constants.BADNUM;
52334
52335var alignmentConstants = _dereq_('../../constants/alignment');
52336var MID_SHIFT = alignmentConstants.MID_SHIFT;
52337var CAP_SHIFT = alignmentConstants.CAP_SHIFT;
52338var LINE_SPACING = alignmentConstants.LINE_SPACING;
52339var OPPOSITE_SIDE = alignmentConstants.OPPOSITE_SIDE;
52340
52341var axes = module.exports = {};
52342
52343axes.setConvert = _dereq_('./set_convert');
52344var autoType = _dereq_('./axis_autotype');
52345
52346var axisIds = _dereq_('./axis_ids');
52347axes.id2name = axisIds.id2name;
52348axes.name2id = axisIds.name2id;
52349axes.cleanId = axisIds.cleanId;
52350axes.list = axisIds.list;
52351axes.listIds = axisIds.listIds;
52352axes.getFromId = axisIds.getFromId;
52353axes.getFromTrace = axisIds.getFromTrace;
52354
52355var autorange = _dereq_('./autorange');
52356axes.getAutoRange = autorange.getAutoRange;
52357axes.findExtremes = autorange.findExtremes;
52358
52359var epsilon = 0.0001;
52360function expandRange(range) {
52361 var delta = (range[1] - range[0]) * epsilon;
52362 return [
52363 range[0] - delta,
52364 range[1] + delta
52365 ];
52366}
52367
52368/*
52369 * find the list of possible axes to reference with an xref or yref attribute
52370 * and coerce it to that list
52371 *
52372 * attr: the attribute we're generating a reference for. Should end in 'x' or 'y'
52373 * but can be prefixed, like 'ax' for annotation's arrow x
52374 * dflt: the default to coerce to, or blank to use the first axis (falling back on
52375 * extraOption if there is no axis)
52376 * extraOption: aside from existing axes with this letter, what non-axis value is allowed?
52377 * Only required if it's different from `dflt`
52378 */
52379axes.coerceRef = function(containerIn, containerOut, gd, attr, dflt, extraOption) {
52380 var axLetter = attr.charAt(attr.length - 1);
52381 var axlist = gd._fullLayout._subplots[axLetter + 'axis'];
52382 var refAttr = attr + 'ref';
52383 var attrDef = {};
52384
52385 if(!dflt) dflt = axlist[0] || extraOption;
52386 if(!extraOption) extraOption = dflt;
52387
52388 // data-ref annotations are not supported in gl2d yet
52389
52390 attrDef[refAttr] = {
52391 valType: 'enumerated',
52392 values: axlist.concat(extraOption ? [extraOption] : []),
52393 dflt: dflt
52394 };
52395
52396 // xref, yref
52397 return Lib.coerce(containerIn, containerOut, attrDef, refAttr);
52398};
52399
52400/*
52401 * coerce position attributes (range-type) that can be either on axes or absolute
52402 * (paper or pixel) referenced. The biggest complication here is that we don't know
52403 * before looking at the axis whether the value must be a number or not (it may be
52404 * a date string), so we can't use the regular valType='number' machinery
52405 *
52406 * axRef (string): the axis this position is referenced to, or:
52407 * paper: fraction of the plot area
52408 * pixel: pixels relative to some starting position
52409 * attr (string): the attribute in containerOut we are coercing
52410 * dflt (number): the default position, as a fraction or pixels. If the attribute
52411 * is to be axis-referenced, this will be converted to an axis data value
52412 *
52413 * Also cleans the values, since the attribute definition itself has to say
52414 * valType: 'any' to handle date axes. This allows us to accept:
52415 * - for category axes: category names, and convert them here into serial numbers.
52416 * Note that this will NOT work for axis range endpoints, because we don't know
52417 * the category list yet (it's set by ax.makeCalcdata during calc)
52418 * but it works for component (note, shape, images) positions.
52419 * - for date axes: JS Dates or milliseconds, and convert to date strings
52420 * - for other types: coerce them to numbers
52421 */
52422axes.coercePosition = function(containerOut, gd, coerce, axRef, attr, dflt) {
52423 var cleanPos, pos;
52424
52425 if(axRef === 'paper' || axRef === 'pixel') {
52426 cleanPos = Lib.ensureNumber;
52427 pos = coerce(attr, dflt);
52428 } else {
52429 var ax = axes.getFromId(gd, axRef);
52430 dflt = ax.fraction2r(dflt);
52431 pos = coerce(attr, dflt);
52432 cleanPos = ax.cleanPos;
52433 }
52434
52435 containerOut[attr] = cleanPos(pos);
52436};
52437
52438axes.cleanPosition = function(pos, gd, axRef) {
52439 var cleanPos = (axRef === 'paper' || axRef === 'pixel') ?
52440 Lib.ensureNumber :
52441 axes.getFromId(gd, axRef).cleanPos;
52442
52443 return cleanPos(pos);
52444};
52445
52446axes.redrawComponents = function(gd, axIds) {
52447 axIds = axIds ? axIds : axes.listIds(gd);
52448
52449 var fullLayout = gd._fullLayout;
52450
52451 function _redrawOneComp(moduleName, methodName, stashName, shortCircuit) {
52452 var method = Registry.getComponentMethod(moduleName, methodName);
52453 var stash = {};
52454
52455 for(var i = 0; i < axIds.length; i++) {
52456 var ax = fullLayout[axes.id2name(axIds[i])];
52457 var indices = ax[stashName];
52458
52459 for(var j = 0; j < indices.length; j++) {
52460 var ind = indices[j];
52461
52462 if(!stash[ind]) {
52463 method(gd, ind);
52464 stash[ind] = 1;
52465 // once is enough for images (which doesn't use the `i` arg anyway)
52466 if(shortCircuit) return;
52467 }
52468 }
52469 }
52470 }
52471
52472 // annotations and shapes 'draw' method is slow,
52473 // use the finer-grained 'drawOne' method instead
52474 _redrawOneComp('annotations', 'drawOne', '_annIndices');
52475 _redrawOneComp('shapes', 'drawOne', '_shapeIndices');
52476 _redrawOneComp('images', 'draw', '_imgIndices', true);
52477};
52478
52479var getDataConversions = axes.getDataConversions = function(gd, trace, target, targetArray) {
52480 var ax;
52481
52482 // If target points to an axis, use the type we already have for that
52483 // axis to find the data type. Otherwise use the values to autotype.
52484 var d2cTarget = (target === 'x' || target === 'y' || target === 'z') ?
52485 target :
52486 targetArray;
52487
52488 // In the case of an array target, make a mock data array
52489 // and call supplyDefaults to the data type and
52490 // setup the data-to-calc method.
52491 if(Array.isArray(d2cTarget)) {
52492 ax = {
52493 type: autoType(targetArray),
52494 _categories: []
52495 };
52496 axes.setConvert(ax);
52497
52498 // build up ax._categories (usually done during ax.makeCalcdata()
52499 if(ax.type === 'category') {
52500 for(var i = 0; i < targetArray.length; i++) {
52501 ax.d2c(targetArray[i]);
52502 }
52503 }
52504 // TODO what to do for transforms?
52505 } else {
52506 ax = axes.getFromTrace(gd, trace, d2cTarget);
52507 }
52508
52509 // if 'target' has corresponding axis
52510 // -> use setConvert method
52511 if(ax) return {d2c: ax.d2c, c2d: ax.c2d};
52512
52513 // special case for 'ids'
52514 // -> cast to String
52515 if(d2cTarget === 'ids') return {d2c: toString, c2d: toString};
52516
52517 // otherwise (e.g. numeric-array of 'marker.color' or 'marker.size')
52518 // -> cast to Number
52519
52520 return {d2c: toNum, c2d: toNum};
52521};
52522
52523function toNum(v) { return +v; }
52524function toString(v) { return String(v); }
52525
52526axes.getDataToCoordFunc = function(gd, trace, target, targetArray) {
52527 return getDataConversions(gd, trace, target, targetArray).d2c;
52528};
52529
52530// get counteraxis letter for this axis (name or id)
52531// this can also be used as the id for default counter axis
52532axes.counterLetter = function(id) {
52533 var axLetter = id.charAt(0);
52534 if(axLetter === 'x') return 'y';
52535 if(axLetter === 'y') return 'x';
52536};
52537
52538// incorporate a new minimum difference and first tick into
52539// forced
52540// note that _forceTick0 is linearized, so needs to be turned into
52541// a range value for setting tick0
52542axes.minDtick = function(ax, newDiff, newFirst, allow) {
52543 // doesn't make sense to do forced min dTick on log or category axes,
52544 // and the plot itself may decide to cancel (ie non-grouped bars)
52545 if(['log', 'category', 'multicategory'].indexOf(ax.type) !== -1 || !allow) {
52546 ax._minDtick = 0;
52547 } else if(ax._minDtick === undefined) {
52548 // undefined means there's nothing there yet
52549
52550 ax._minDtick = newDiff;
52551 ax._forceTick0 = newFirst;
52552 } else if(ax._minDtick) {
52553 if((ax._minDtick / newDiff + 1e-6) % 1 < 2e-6 &&
52554 // existing minDtick is an integer multiple of newDiff
52555 // (within rounding err)
52556 // and forceTick0 can be shifted to newFirst
52557
52558 (((newFirst - ax._forceTick0) / newDiff % 1) +
52559 1.000001) % 1 < 2e-6) {
52560 ax._minDtick = newDiff;
52561 ax._forceTick0 = newFirst;
52562 } else if((newDiff / ax._minDtick + 1e-6) % 1 > 2e-6 ||
52563 // if the converse is true (newDiff is a multiple of minDtick and
52564 // newFirst can be shifted to forceTick0) then do nothing - same
52565 // forcing stands. Otherwise, cancel forced minimum
52566
52567 (((newFirst - ax._forceTick0) / ax._minDtick % 1) +
52568 1.000001) % 1 > 2e-6) {
52569 ax._minDtick = 0;
52570 }
52571 }
52572};
52573
52574// save a copy of the initial axis ranges in fullLayout
52575// use them in mode bar and dblclick events
52576axes.saveRangeInitial = function(gd, overwrite) {
52577 var axList = axes.list(gd, '', true);
52578 var hasOneAxisChanged = false;
52579
52580 for(var i = 0; i < axList.length; i++) {
52581 var ax = axList[i];
52582 var isNew = (ax._rangeInitial === undefined);
52583 var hasChanged = isNew || !(
52584 ax.range[0] === ax._rangeInitial[0] &&
52585 ax.range[1] === ax._rangeInitial[1]
52586 );
52587
52588 if((isNew && ax.autorange === false) || (overwrite && hasChanged)) {
52589 ax._rangeInitial = ax.range.slice();
52590 hasOneAxisChanged = true;
52591 }
52592 }
52593
52594 return hasOneAxisChanged;
52595};
52596
52597// save a copy of the initial spike visibility
52598axes.saveShowSpikeInitial = function(gd, overwrite) {
52599 var axList = axes.list(gd, '', true);
52600 var hasOneAxisChanged = false;
52601 var allSpikesEnabled = 'on';
52602
52603 for(var i = 0; i < axList.length; i++) {
52604 var ax = axList[i];
52605 var isNew = (ax._showSpikeInitial === undefined);
52606 var hasChanged = isNew || !(ax.showspikes === ax._showspikes);
52607
52608 if(isNew || (overwrite && hasChanged)) {
52609 ax._showSpikeInitial = ax.showspikes;
52610 hasOneAxisChanged = true;
52611 }
52612
52613 if(allSpikesEnabled === 'on' && !ax.showspikes) {
52614 allSpikesEnabled = 'off';
52615 }
52616 }
52617 gd._fullLayout._cartesianSpikesEnabled = allSpikesEnabled;
52618 return hasOneAxisChanged;
52619};
52620
52621axes.autoBin = function(data, ax, nbins, is2d, calendar, size) {
52622 var dataMin = Lib.aggNums(Math.min, null, data);
52623 var dataMax = Lib.aggNums(Math.max, null, data);
52624
52625 if(ax.type === 'category' || ax.type === 'multicategory') {
52626 return {
52627 start: dataMin - 0.5,
52628 end: dataMax + 0.5,
52629 size: Math.max(1, Math.round(size) || 1),
52630 _dataSpan: dataMax - dataMin,
52631 };
52632 }
52633
52634 if(!calendar) calendar = ax.calendar;
52635
52636 // piggyback off tick code to make "nice" bin sizes and edges
52637 var dummyAx;
52638 if(ax.type === 'log') {
52639 dummyAx = {
52640 type: 'linear',
52641 range: [dataMin, dataMax]
52642 };
52643 } else {
52644 dummyAx = {
52645 type: ax.type,
52646 range: Lib.simpleMap([dataMin, dataMax], ax.c2r, 0, calendar),
52647 calendar: calendar
52648 };
52649 }
52650 axes.setConvert(dummyAx);
52651
52652 size = size && cleanTicks.dtick(size, dummyAx.type);
52653
52654 if(size) {
52655 dummyAx.dtick = size;
52656 dummyAx.tick0 = cleanTicks.tick0(undefined, dummyAx.type, calendar);
52657 } else {
52658 var size0;
52659 if(nbins) size0 = ((dataMax - dataMin) / nbins);
52660 else {
52661 // totally auto: scale off std deviation so the highest bin is
52662 // somewhat taller than the total number of bins, but don't let
52663 // the size get smaller than the 'nice' rounded down minimum
52664 // difference between values
52665 var distinctData = Lib.distinctVals(data);
52666 var msexp = Math.pow(10, Math.floor(
52667 Math.log(distinctData.minDiff) / Math.LN10));
52668 var minSize = msexp * Lib.roundUp(
52669 distinctData.minDiff / msexp, [0.9, 1.9, 4.9, 9.9], true);
52670 size0 = Math.max(minSize, 2 * Lib.stdev(data) /
52671 Math.pow(data.length, is2d ? 0.25 : 0.4));
52672
52673 // fallback if ax.d2c output BADNUMs
52674 // e.g. when user try to plot categorical bins
52675 // on a layout.xaxis.type: 'linear'
52676 if(!isNumeric(size0)) size0 = 1;
52677 }
52678
52679 axes.autoTicks(dummyAx, size0);
52680 }
52681
52682 var finalSize = dummyAx.dtick;
52683 var binStart = axes.tickIncrement(
52684 axes.tickFirst(dummyAx), finalSize, 'reverse', calendar);
52685 var binEnd, bincount;
52686
52687 // check for too many data points right at the edges of bins
52688 // (>50% within 1% of bin edges) or all data points integral
52689 // and offset the bins accordingly
52690 if(typeof finalSize === 'number') {
52691 binStart = autoShiftNumericBins(binStart, data, dummyAx, dataMin, dataMax);
52692
52693 bincount = 1 + Math.floor((dataMax - binStart) / finalSize);
52694 binEnd = binStart + bincount * finalSize;
52695 } else {
52696 // month ticks - should be the only nonlinear kind we have at this point.
52697 // dtick (as supplied by axes.autoTick) only has nonlinear values on
52698 // date and log axes, but even if you display a histogram on a log axis
52699 // we bin it on a linear axis (which one could argue against, but that's
52700 // a separate issue)
52701 if(dummyAx.dtick.charAt(0) === 'M') {
52702 binStart = autoShiftMonthBins(binStart, data, finalSize, dataMin, calendar);
52703 }
52704
52705 // calculate the endpoint for nonlinear ticks - you have to
52706 // just increment until you're done
52707 binEnd = binStart;
52708 bincount = 0;
52709 while(binEnd <= dataMax) {
52710 binEnd = axes.tickIncrement(binEnd, finalSize, false, calendar);
52711 bincount++;
52712 }
52713 }
52714
52715 return {
52716 start: ax.c2r(binStart, 0, calendar),
52717 end: ax.c2r(binEnd, 0, calendar),
52718 size: finalSize,
52719 _dataSpan: dataMax - dataMin
52720 };
52721};
52722
52723
52724function autoShiftNumericBins(binStart, data, ax, dataMin, dataMax) {
52725 var edgecount = 0;
52726 var midcount = 0;
52727 var intcount = 0;
52728 var blankCount = 0;
52729
52730 function nearEdge(v) {
52731 // is a value within 1% of a bin edge?
52732 return (1 + (v - binStart) * 100 / ax.dtick) % 100 < 2;
52733 }
52734
52735 for(var i = 0; i < data.length; i++) {
52736 if(data[i] % 1 === 0) intcount++;
52737 else if(!isNumeric(data[i])) blankCount++;
52738
52739 if(nearEdge(data[i])) edgecount++;
52740 if(nearEdge(data[i] + ax.dtick / 2)) midcount++;
52741 }
52742 var dataCount = data.length - blankCount;
52743
52744 if(intcount === dataCount && ax.type !== 'date') {
52745 if(ax.dtick < 1) {
52746 // all integers: if bin size is <1, it's because
52747 // that was specifically requested (large nbins)
52748 // so respect that... but center the bins containing
52749 // integers on those integers
52750
52751 binStart = dataMin - 0.5 * ax.dtick;
52752 } else {
52753 // otherwise start half an integer down regardless of
52754 // the bin size, just enough to clear up endpoint
52755 // ambiguity about which integers are in which bins.
52756
52757 binStart -= 0.5;
52758 if(binStart + ax.dtick < dataMin) binStart += ax.dtick;
52759 }
52760 } else if(midcount < dataCount * 0.1) {
52761 if(edgecount > dataCount * 0.3 ||
52762 nearEdge(dataMin) || nearEdge(dataMax)) {
52763 // lots of points at the edge, not many in the middle
52764 // shift half a bin
52765 var binshift = ax.dtick / 2;
52766 binStart += (binStart + binshift < dataMin) ? binshift : -binshift;
52767 }
52768 }
52769 return binStart;
52770}
52771
52772
52773function autoShiftMonthBins(binStart, data, dtick, dataMin, calendar) {
52774 var stats = Lib.findExactDates(data, calendar);
52775 // number of data points that needs to be an exact value
52776 // to shift that increment to (near) the bin center
52777 var threshold = 0.8;
52778
52779 if(stats.exactDays > threshold) {
52780 var numMonths = Number(dtick.substr(1));
52781
52782 if((stats.exactYears > threshold) && (numMonths % 12 === 0)) {
52783 // The exact middle of a non-leap-year is 1.5 days into July
52784 // so if we start the bins here, all but leap years will
52785 // get hover-labeled as exact years.
52786 binStart = axes.tickIncrement(binStart, 'M6', 'reverse') + ONEDAY * 1.5;
52787 } else if(stats.exactMonths > threshold) {
52788 // Months are not as clean, but if we shift half the *longest*
52789 // month (31/2 days) then 31-day months will get labeled exactly
52790 // and shorter months will get labeled with the correct month
52791 // but shifted 12-36 hours into it.
52792 binStart = axes.tickIncrement(binStart, 'M1', 'reverse') + ONEDAY * 15.5;
52793 } else {
52794 // Shifting half a day is exact, but since these are month bins it
52795 // will always give a somewhat odd-looking label, until we do something
52796 // smarter like showing the bin boundaries (or the bounds of the actual
52797 // data in each bin)
52798 binStart -= ONEDAY / 2;
52799 }
52800 var nextBinStart = axes.tickIncrement(binStart, dtick);
52801
52802 if(nextBinStart <= dataMin) return nextBinStart;
52803 }
52804 return binStart;
52805}
52806
52807// ----------------------------------------------------
52808// Ticks and grids
52809// ----------------------------------------------------
52810
52811// ensure we have tick0, dtick, and tick rounding calculated
52812axes.prepTicks = function(ax, opts) {
52813 var rng = Lib.simpleMap(ax.range, ax.r2l, undefined, undefined, opts);
52814
52815 // calculate max number of (auto) ticks to display based on plot size
52816 if(ax.tickmode === 'auto' || !ax.dtick) {
52817 var nt = ax.nticks;
52818 var minPx;
52819
52820 if(!nt) {
52821 if(ax.type === 'category' || ax.type === 'multicategory') {
52822 minPx = ax.tickfont ? (ax.tickfont.size || 12) * 1.2 : 15;
52823 nt = ax._length / minPx;
52824 } else {
52825 minPx = ax._id.charAt(0) === 'y' ? 40 : 80;
52826 nt = Lib.constrain(ax._length / minPx, 4, 9) + 1;
52827 }
52828
52829 // radial axes span half their domain,
52830 // multiply nticks value by two to get correct number of auto ticks.
52831 if(ax._name === 'radialaxis') nt *= 2;
52832 }
52833
52834 // add a couple of extra digits for filling in ticks when we
52835 // have explicit tickvals without tick text
52836 if(ax.tickmode === 'array') nt *= 100;
52837
52838
52839 ax._roughDTick = (Math.abs(rng[1] - rng[0]) - (ax._lBreaks || 0)) / nt;
52840 axes.autoTicks(ax, ax._roughDTick);
52841
52842 // check for a forced minimum dtick
52843 if(ax._minDtick > 0 && ax.dtick < ax._minDtick * 2) {
52844 ax.dtick = ax._minDtick;
52845 ax.tick0 = ax.l2r(ax._forceTick0);
52846 }
52847 }
52848
52849 // check for missing tick0
52850 if(!ax.tick0) {
52851 ax.tick0 = (ax.type === 'date') ? '2000-01-01' : 0;
52852 }
52853
52854 // ensure we don't try to make ticks below our minimum precision
52855 // see https://github.com/plotly/plotly.js/issues/2892
52856 if(ax.type === 'date' && ax.dtick < 0.1) ax.dtick = 0.1;
52857
52858 // now figure out rounding of tick values
52859 autoTickRound(ax);
52860};
52861
52862// calculate the ticks: text, values, positioning
52863// if ticks are set to automatic, determine the right values (tick0,dtick)
52864// in any case, set tickround to # of digits to round tick labels to,
52865// or codes to this effect for log and date scales
52866axes.calcTicks = function calcTicks(ax, opts) {
52867 axes.prepTicks(ax, opts);
52868 var rng = Lib.simpleMap(ax.range, ax.r2l, undefined, undefined, opts);
52869
52870 // now that we've figured out the auto values for formatting
52871 // in case we're missing some ticktext, we can break out for array ticks
52872 if(ax.tickmode === 'array') return arrayTicks(ax);
52873
52874 // find the first tick
52875 ax._tmin = axes.tickFirst(ax, opts);
52876
52877 // add a tiny bit so we get ticks which may have rounded out
52878 var exRng = expandRange(rng);
52879 var startTick = exRng[0];
52880 var endTick = exRng[1];
52881 // check for reversed axis
52882 var axrev = (rng[1] < rng[0]);
52883
52884 // No visible ticks? Quit.
52885 // I've only seen this on category axes with all categories off the edge.
52886 if((ax._tmin < startTick) !== axrev) return [];
52887
52888 // return the full set of tick vals
52889 if(ax.type === 'category' || ax.type === 'multicategory') {
52890 endTick = (axrev) ? Math.max(-0.5, endTick) :
52891 Math.min(ax._categories.length - 0.5, endTick);
52892 }
52893
52894 var isDLog = (ax.type === 'log') && !(isNumeric(ax.dtick) || ax.dtick.charAt(0) === 'L');
52895
52896 var tickVals;
52897 function generateTicks() {
52898 var xPrevious = null;
52899 var maxTicks = Math.max(1000, ax._length || 0);
52900 tickVals = [];
52901 for(var x = ax._tmin;
52902 (axrev) ? (x >= endTick) : (x <= endTick);
52903 x = axes.tickIncrement(x, ax.dtick, axrev, ax.calendar)) {
52904 // prevent infinite loops - no more than one tick per pixel,
52905 // and make sure each value is different from the previous
52906 if(tickVals.length > maxTicks || x === xPrevious) break;
52907 xPrevious = x;
52908
52909 var minor = false;
52910 if(isDLog && (x !== (x | 0))) {
52911 minor = true;
52912 }
52913
52914 tickVals.push({
52915 minor: minor,
52916 value: x
52917 });
52918 }
52919 }
52920
52921 generateTicks();
52922
52923 if(ax.rangebreaks) {
52924 // replace ticks inside breaks that would get a tick
52925 // and reduce ticks
52926 var len = tickVals.length;
52927 if(len) {
52928 var tf = 0;
52929 if(ax.tickmode === 'auto') {
52930 tf =
52931 (ax._id.charAt(0) === 'y' ? 2 : 6) *
52932 (ax.tickfont ? ax.tickfont.size : 12);
52933 }
52934
52935 var newTickVals = [];
52936 var prevPos;
52937
52938 var dir = axrev ? 1 : -1;
52939 var first = axrev ? 0 : len - 1;
52940 var last = axrev ? len - 1 : 0;
52941 for(var q = first; dir * q <= dir * last; q += dir) {
52942 var tickVal = tickVals[q];
52943 if(ax.maskBreaks(tickVal.value) === BADNUM) {
52944 tickVal.value = moveOutsideBreak(tickVal.value, ax);
52945
52946 if(ax._rl && (
52947 ax._rl[0] === tickVal.value ||
52948 ax._rl[1] === tickVal.value
52949 )) continue;
52950 }
52951
52952 var pos = ax.c2p(tickVal.value);
52953
52954 if(pos === prevPos) {
52955 if(newTickVals[newTickVals.length - 1].value < tickVal.value) {
52956 newTickVals[newTickVals.length - 1] = tickVal;
52957 }
52958 } else if(prevPos === undefined || Math.abs(pos - prevPos) > tf) {
52959 prevPos = pos;
52960 newTickVals.push(tickVal);
52961 }
52962 }
52963 tickVals = newTickVals.reverse();
52964 }
52965 }
52966
52967 // If same angle over a full circle, the last tick vals is a duplicate.
52968 // TODO must do something similar for angular date axes.
52969 if(isAngular(ax) && Math.abs(rng[1] - rng[0]) === 360) {
52970 tickVals.pop();
52971 }
52972
52973 // save the last tick as well as first, so we can
52974 // show the exponent only on the last one
52975 ax._tmax = (tickVals[tickVals.length - 1] || {}).value;
52976
52977 // for showing the rest of a date when the main tick label is only the
52978 // latter part: ax._prevDateHead holds what we showed most recently.
52979 // Start with it cleared and mark that we're in calcTicks (ie calculating a
52980 // whole string of these so we should care what the previous date head was!)
52981 ax._prevDateHead = '';
52982 ax._inCalcTicks = true;
52983
52984 var ticksOut = new Array(tickVals.length);
52985 for(var i = 0; i < tickVals.length; i++) {
52986 var _minor = tickVals[i].minor;
52987 var _value = tickVals[i].value;
52988
52989 ticksOut[i] = axes.tickText(
52990 ax,
52991 _value,
52992 false, // hover
52993 _minor // noSuffixPrefix
52994 );
52995 }
52996
52997 ax._inCalcTicks = false;
52998
52999 return ticksOut;
53000};
53001
53002function arrayTicks(ax) {
53003 var vals = ax.tickvals;
53004 var text = ax.ticktext;
53005 var ticksOut = new Array(vals.length);
53006 var rng = Lib.simpleMap(ax.range, ax.r2l);
53007 var exRng = expandRange(rng);
53008 var tickMin = Math.min(exRng[0], exRng[1]);
53009 var tickMax = Math.max(exRng[0], exRng[1]);
53010 var j = 0;
53011
53012 // without a text array, just format the given values as any other ticks
53013 // except with more precision to the numbers
53014 if(!Array.isArray(text)) text = [];
53015
53016 // make sure showing ticks doesn't accidentally add new categories
53017 // TODO multicategory, if we allow ticktext / tickvals
53018 var tickVal2l = ax.type === 'category' ? ax.d2l_noadd : ax.d2l;
53019
53020 // array ticks on log axes always show the full number
53021 // (if no explicit ticktext overrides it)
53022 if(ax.type === 'log' && String(ax.dtick).charAt(0) !== 'L') {
53023 ax.dtick = 'L' + Math.pow(10, Math.floor(Math.min(ax.range[0], ax.range[1])) - 1);
53024 }
53025
53026 for(var i = 0; i < vals.length; i++) {
53027 var vali = tickVal2l(vals[i]);
53028 if(vali > tickMin && vali < tickMax) {
53029 if(text[i] === undefined) ticksOut[j] = axes.tickText(ax, vali);
53030 else ticksOut[j] = tickTextObj(ax, vali, String(text[i]));
53031 j++;
53032 }
53033 }
53034
53035 if(j < vals.length) ticksOut.splice(j, vals.length - j);
53036
53037 if(ax.rangebreaks) {
53038 // remove ticks falling inside rangebreaks
53039 ticksOut = ticksOut.filter(function(d) {
53040 return ax.maskBreaks(d.x) !== BADNUM;
53041 });
53042 }
53043
53044 return ticksOut;
53045}
53046
53047var roundBase10 = [2, 5, 10];
53048var roundBase24 = [1, 2, 3, 6, 12];
53049var roundBase60 = [1, 2, 5, 10, 15, 30];
53050// 2&3 day ticks are weird, but need something btwn 1&7
53051var roundDays = [1, 2, 3, 7, 14];
53052// approx. tick positions for log axes, showing all (1) and just 1, 2, 5 (2)
53053// these don't have to be exact, just close enough to round to the right value
53054var roundLog1 = [-0.046, 0, 0.301, 0.477, 0.602, 0.699, 0.778, 0.845, 0.903, 0.954, 1];
53055var roundLog2 = [-0.301, 0, 0.301, 0.699, 1];
53056// N.B. `thetaunit; 'radians' angular axes must be converted to degrees
53057var roundAngles = [15, 30, 45, 90, 180];
53058
53059function roundDTick(roughDTick, base, roundingSet) {
53060 return base * Lib.roundUp(roughDTick / base, roundingSet);
53061}
53062
53063// autoTicks: calculate best guess at pleasant ticks for this axis
53064// inputs:
53065// ax - an axis object
53066// roughDTick - rough tick spacing (to be turned into a nice round number)
53067// outputs (into ax):
53068// tick0: starting point for ticks (not necessarily on the graph)
53069// usually 0 for numeric (=10^0=1 for log) or jan 1, 2000 for dates
53070// dtick: the actual, nice round tick spacing, usually a little larger than roughDTick
53071// if the ticks are spaced linearly (linear scale, categories,
53072// log with only full powers, date ticks < month),
53073// this will just be a number
53074// months: M#
53075// years: M# where # is 12*number of years
53076// log with linear ticks: L# where # is the linear tick spacing
53077// log showing powers plus some intermediates:
53078// D1 shows all digits, D2 shows 2 and 5
53079axes.autoTicks = function(ax, roughDTick) {
53080 var base;
53081
53082 function getBase(v) {
53083 return Math.pow(v, Math.floor(Math.log(roughDTick) / Math.LN10));
53084 }
53085
53086 if(ax.type === 'date') {
53087 ax.tick0 = Lib.dateTick0(ax.calendar);
53088 // the criteria below are all based on the rough spacing we calculate
53089 // being > half of the final unit - so precalculate twice the rough val
53090 var roughX2 = 2 * roughDTick;
53091
53092 if(roughX2 > ONEAVGYEAR) {
53093 roughDTick /= ONEAVGYEAR;
53094 base = getBase(10);
53095 ax.dtick = 'M' + (12 * roundDTick(roughDTick, base, roundBase10));
53096 } else if(roughX2 > ONEAVGMONTH) {
53097 roughDTick /= ONEAVGMONTH;
53098 ax.dtick = 'M' + roundDTick(roughDTick, 1, roundBase24);
53099 } else if(roughX2 > ONEDAY) {
53100 ax.dtick = roundDTick(roughDTick, ONEDAY, ax._hasDayOfWeekBreaks ? [1, 2, 7, 14] : roundDays);
53101 // get week ticks on sunday
53102 // this will also move the base tick off 2000-01-01 if dtick is
53103 // 2 or 3 days... but that's a weird enough case that we'll ignore it.
53104 ax.tick0 = Lib.dateTick0(ax.calendar, true);
53105 } else if(roughX2 > ONEHOUR) {
53106 ax.dtick = roundDTick(roughDTick, ONEHOUR, roundBase24);
53107 } else if(roughX2 > ONEMIN) {
53108 ax.dtick = roundDTick(roughDTick, ONEMIN, roundBase60);
53109 } else if(roughX2 > ONESEC) {
53110 ax.dtick = roundDTick(roughDTick, ONESEC, roundBase60);
53111 } else {
53112 // milliseconds
53113 base = getBase(10);
53114 ax.dtick = roundDTick(roughDTick, base, roundBase10);
53115 }
53116 } else if(ax.type === 'log') {
53117 ax.tick0 = 0;
53118 var rng = Lib.simpleMap(ax.range, ax.r2l);
53119
53120 if(roughDTick > 0.7) {
53121 // only show powers of 10
53122 ax.dtick = Math.ceil(roughDTick);
53123 } else if(Math.abs(rng[1] - rng[0]) < 1) {
53124 // span is less than one power of 10
53125 var nt = 1.5 * Math.abs((rng[1] - rng[0]) / roughDTick);
53126
53127 // ticks on a linear scale, labeled fully
53128 roughDTick = Math.abs(Math.pow(10, rng[1]) -
53129 Math.pow(10, rng[0])) / nt;
53130 base = getBase(10);
53131 ax.dtick = 'L' + roundDTick(roughDTick, base, roundBase10);
53132 } else {
53133 // include intermediates between powers of 10,
53134 // labeled with small digits
53135 // ax.dtick = "D2" (show 2 and 5) or "D1" (show all digits)
53136 ax.dtick = (roughDTick > 0.3) ? 'D2' : 'D1';
53137 }
53138 } else if(ax.type === 'category' || ax.type === 'multicategory') {
53139 ax.tick0 = 0;
53140 ax.dtick = Math.ceil(Math.max(roughDTick, 1));
53141 } else if(isAngular(ax)) {
53142 ax.tick0 = 0;
53143 base = 1;
53144 ax.dtick = roundDTick(roughDTick, base, roundAngles);
53145 } else {
53146 // auto ticks always start at 0
53147 ax.tick0 = 0;
53148 base = getBase(10);
53149 ax.dtick = roundDTick(roughDTick, base, roundBase10);
53150 }
53151
53152 // prevent infinite loops
53153 if(ax.dtick === 0) ax.dtick = 1;
53154
53155 // TODO: this is from log axis histograms with autorange off
53156 if(!isNumeric(ax.dtick) && typeof ax.dtick !== 'string') {
53157 var olddtick = ax.dtick;
53158 ax.dtick = 1;
53159 throw 'ax.dtick error: ' + String(olddtick);
53160 }
53161};
53162
53163// after dtick is already known, find tickround = precision
53164// to display in tick labels
53165// for numeric ticks, integer # digits after . to round to
53166// for date ticks, the last date part to show (y,m,d,H,M,S)
53167// or an integer # digits past seconds
53168function autoTickRound(ax) {
53169 var dtick = ax.dtick;
53170
53171 ax._tickexponent = 0;
53172 if(!isNumeric(dtick) && typeof dtick !== 'string') {
53173 dtick = 1;
53174 }
53175
53176 if(ax.type === 'category' || ax.type === 'multicategory') {
53177 ax._tickround = null;
53178 }
53179 if(ax.type === 'date') {
53180 // If tick0 is unusual, give tickround a bit more information
53181 // not necessarily *all* the information in tick0 though, if it's really odd
53182 // minimal string length for tick0: 'd' is 10, 'M' is 16, 'S' is 19
53183 // take off a leading minus (year < 0) and i (intercalary month) so length is consistent
53184 var tick0ms = ax.r2l(ax.tick0);
53185 var tick0str = ax.l2r(tick0ms).replace(/(^-|i)/g, '');
53186 var tick0len = tick0str.length;
53187
53188 if(String(dtick).charAt(0) === 'M') {
53189 // any tick0 more specific than a year: alway show the full date
53190 if(tick0len > 10 || tick0str.substr(5) !== '01-01') ax._tickround = 'd';
53191 // show the month unless ticks are full multiples of a year
53192 else ax._tickround = (+(dtick.substr(1)) % 12 === 0) ? 'y' : 'm';
53193 } else if((dtick >= ONEDAY && tick0len <= 10) || (dtick >= ONEDAY * 15)) ax._tickround = 'd';
53194 else if((dtick >= ONEMIN && tick0len <= 16) || (dtick >= ONEHOUR)) ax._tickround = 'M';
53195 else if((dtick >= ONESEC && tick0len <= 19) || (dtick >= ONEMIN)) ax._tickround = 'S';
53196 else {
53197 // tickround is a number of digits of fractional seconds
53198 // of any two adjacent ticks, at least one will have the maximum fractional digits
53199 // of all possible ticks - so take the max. length of tick0 and the next one
53200 var tick1len = ax.l2r(tick0ms + dtick).replace(/^-/, '').length;
53201 ax._tickround = Math.max(tick0len, tick1len) - 20;
53202
53203 // We shouldn't get here... but in case there's a situation I'm
53204 // not thinking of where tick0str and tick1str are identical or
53205 // something, fall back on maximum precision
53206 if(ax._tickround < 0) ax._tickround = 4;
53207 }
53208 } else if(isNumeric(dtick) || dtick.charAt(0) === 'L') {
53209 // linear or log (except D1, D2)
53210 var rng = ax.range.map(ax.r2d || Number);
53211 if(!isNumeric(dtick)) dtick = Number(dtick.substr(1));
53212 // 2 digits past largest digit of dtick
53213 ax._tickround = 2 - Math.floor(Math.log(dtick) / Math.LN10 + 0.01);
53214
53215 var maxend = Math.max(Math.abs(rng[0]), Math.abs(rng[1]));
53216 var rangeexp = Math.floor(Math.log(maxend) / Math.LN10 + 0.01);
53217 if(Math.abs(rangeexp) > 3) {
53218 if(isSIFormat(ax.exponentformat) && !beyondSI(rangeexp)) {
53219 ax._tickexponent = 3 * Math.round((rangeexp - 1) / 3);
53220 } else ax._tickexponent = rangeexp;
53221 }
53222 } else {
53223 // D1 or D2 (log)
53224 ax._tickround = null;
53225 }
53226}
53227
53228// months and years don't have constant millisecond values
53229// (but a year is always 12 months so we only need months)
53230// log-scale ticks are also not consistently spaced, except
53231// for pure powers of 10
53232// numeric ticks always have constant differences, other datetime ticks
53233// can all be calculated as constant number of milliseconds
53234axes.tickIncrement = function(x, dtick, axrev, calendar) {
53235 var axSign = axrev ? -1 : 1;
53236
53237 // includes linear, all dates smaller than month, and pure 10^n in log
53238 if(isNumeric(dtick)) return x + axSign * dtick;
53239
53240 // everything else is a string, one character plus a number
53241 var tType = dtick.charAt(0);
53242 var dtSigned = axSign * Number(dtick.substr(1));
53243
53244 // Dates: months (or years - see Lib.incrementMonth)
53245 if(tType === 'M') return Lib.incrementMonth(x, dtSigned, calendar);
53246
53247 // Log scales: Linear, Digits
53248 if(tType === 'L') return Math.log(Math.pow(10, x) + dtSigned) / Math.LN10;
53249
53250 // log10 of 2,5,10, or all digits (logs just have to be
53251 // close enough to round)
53252 if(tType === 'D') {
53253 var tickset = (dtick === 'D2') ? roundLog2 : roundLog1;
53254 var x2 = x + axSign * 0.01;
53255 var frac = Lib.roundUp(Lib.mod(x2, 1), tickset, axrev);
53256
53257 return Math.floor(x2) +
53258 Math.log(d3.round(Math.pow(10, frac), 1)) / Math.LN10;
53259 }
53260
53261 throw 'unrecognized dtick ' + String(dtick);
53262};
53263
53264// calculate the first tick on an axis
53265axes.tickFirst = function(ax, opts) {
53266 var r2l = ax.r2l || Number;
53267 var rng = Lib.simpleMap(ax.range, r2l, undefined, undefined, opts);
53268 var axrev = rng[1] < rng[0];
53269 var sRound = axrev ? Math.floor : Math.ceil;
53270 // add a tiny extra bit to make sure we get ticks
53271 // that may have been rounded out
53272 var r0 = expandRange(rng)[0];
53273 var dtick = ax.dtick;
53274 var tick0 = r2l(ax.tick0);
53275
53276 if(isNumeric(dtick)) {
53277 var tmin = sRound((r0 - tick0) / dtick) * dtick + tick0;
53278
53279 // make sure no ticks outside the category list
53280 if(ax.type === 'category' || ax.type === 'multicategory') {
53281 tmin = Lib.constrain(tmin, 0, ax._categories.length - 1);
53282 }
53283 return tmin;
53284 }
53285
53286 var tType = dtick.charAt(0);
53287 var dtNum = Number(dtick.substr(1));
53288
53289 // Dates: months (or years)
53290 if(tType === 'M') {
53291 var cnt = 0;
53292 var t0 = tick0;
53293 var t1, mult, newDTick;
53294
53295 // This algorithm should work for *any* nonlinear (but close to linear!)
53296 // tick spacing. Limit to 10 iterations, for gregorian months it's normally <=3.
53297 while(cnt < 10) {
53298 t1 = axes.tickIncrement(t0, dtick, axrev, ax.calendar);
53299 if((t1 - r0) * (t0 - r0) <= 0) {
53300 // t1 and t0 are on opposite sides of r0! we've succeeded!
53301 if(axrev) return Math.min(t0, t1);
53302 return Math.max(t0, t1);
53303 }
53304 mult = (r0 - ((t0 + t1) / 2)) / (t1 - t0);
53305 newDTick = tType + ((Math.abs(Math.round(mult)) || 1) * dtNum);
53306 t0 = axes.tickIncrement(t0, newDTick, mult < 0 ? !axrev : axrev, ax.calendar);
53307 cnt++;
53308 }
53309 Lib.error('tickFirst did not converge', ax);
53310 return t0;
53311 } else if(tType === 'L') {
53312 // Log scales: Linear, Digits
53313
53314 return Math.log(sRound(
53315 (Math.pow(10, r0) - tick0) / dtNum) * dtNum + tick0) / Math.LN10;
53316 } else if(tType === 'D') {
53317 var tickset = (dtick === 'D2') ? roundLog2 : roundLog1;
53318 var frac = Lib.roundUp(Lib.mod(r0, 1), tickset, axrev);
53319
53320 return Math.floor(r0) +
53321 Math.log(d3.round(Math.pow(10, frac), 1)) / Math.LN10;
53322 } else throw 'unrecognized dtick ' + String(dtick);
53323};
53324
53325// draw the text for one tick.
53326// px,py are the location on gd.paper
53327// prefix is there so the x axis ticks can be dropped a line
53328// ax is the axis layout, x is the tick value
53329// hover is a (truthy) flag for whether to show numbers with a bit
53330// more precision for hovertext
53331axes.tickText = function(ax, x, hover, noSuffixPrefix) {
53332 var out = tickTextObj(ax, x);
53333 var arrayMode = ax.tickmode === 'array';
53334 var extraPrecision = hover || arrayMode;
53335 var axType = ax.type;
53336 // TODO multicategory, if we allow ticktext / tickvals
53337 var tickVal2l = axType === 'category' ? ax.d2l_noadd : ax.d2l;
53338 var i;
53339
53340 if(arrayMode && Array.isArray(ax.ticktext)) {
53341 var rng = Lib.simpleMap(ax.range, ax.r2l);
53342 var minDiff = (Math.abs(rng[1] - rng[0]) - (ax._lBreaks || 0)) / 10000;
53343
53344 for(i = 0; i < ax.ticktext.length; i++) {
53345 if(Math.abs(x - tickVal2l(ax.tickvals[i])) < minDiff) break;
53346 }
53347 if(i < ax.ticktext.length) {
53348 out.text = String(ax.ticktext[i]);
53349 return out;
53350 }
53351 }
53352
53353 function isHidden(showAttr) {
53354 if(showAttr === undefined) return true;
53355 if(hover) return showAttr === 'none';
53356
53357 var firstOrLast = {
53358 first: ax._tmin,
53359 last: ax._tmax
53360 }[showAttr];
53361
53362 return showAttr !== 'all' && x !== firstOrLast;
53363 }
53364
53365 var hideexp = hover ?
53366 'never' :
53367 ax.exponentformat !== 'none' && isHidden(ax.showexponent) ? 'hide' : '';
53368
53369 if(axType === 'date') formatDate(ax, out, hover, extraPrecision);
53370 else if(axType === 'log') formatLog(ax, out, hover, extraPrecision, hideexp);
53371 else if(axType === 'category') formatCategory(ax, out);
53372 else if(axType === 'multicategory') formatMultiCategory(ax, out, hover);
53373 else if(isAngular(ax)) formatAngle(ax, out, hover, extraPrecision, hideexp);
53374 else formatLinear(ax, out, hover, extraPrecision, hideexp);
53375
53376 // add prefix and suffix
53377 if(!noSuffixPrefix) {
53378 if(ax.tickprefix && !isHidden(ax.showtickprefix)) out.text = ax.tickprefix + out.text;
53379 if(ax.ticksuffix && !isHidden(ax.showticksuffix)) out.text += ax.ticksuffix;
53380 }
53381
53382 // Setup ticks and grid lines boundaries
53383 // at 1/2 a 'category' to the left/bottom
53384 if(ax.tickson === 'boundaries' || ax.showdividers) {
53385 var inbounds = function(v) {
53386 var p = ax.l2p(v);
53387 return p >= 0 && p <= ax._length ? v : null;
53388 };
53389
53390 out.xbnd = [
53391 inbounds(out.x - 0.5),
53392 inbounds(out.x + ax.dtick - 0.5)
53393 ];
53394 }
53395
53396 return out;
53397};
53398
53399/**
53400 * create text for a hover label on this axis, with special handling of
53401 * log axes (where negative values can't be displayed but can appear in hover text)
53402 *
53403 * @param {object} ax: the axis to format text for
53404 * @param {number} val: calcdata value to format
53405 * @param {Optional(number)} val2: a second value to display
53406 *
53407 * @returns {string} `val` formatted as a string appropriate to this axis, or
53408 * `val` and `val2` as a range (ie '<val> - <val2>') if `val2` is provided and
53409 * it's different from `val`.
53410 */
53411axes.hoverLabelText = function(ax, val, val2) {
53412 if(val2 !== BADNUM && val2 !== val) {
53413 return axes.hoverLabelText(ax, val) + ' - ' + axes.hoverLabelText(ax, val2);
53414 }
53415
53416 var logOffScale = (ax.type === 'log' && val <= 0);
53417 var tx = axes.tickText(ax, ax.c2l(logOffScale ? -val : val), 'hover').text;
53418
53419 if(logOffScale) {
53420 return val === 0 ? '0' : MINUS_SIGN + tx;
53421 }
53422
53423 // TODO: should we do something special if the axis calendar and
53424 // the data calendar are different? Somehow display both dates with
53425 // their system names? Right now it will just display in the axis calendar
53426 // but users could add the other one as text.
53427 return tx;
53428};
53429
53430function tickTextObj(ax, x, text) {
53431 var tf = ax.tickfont || {};
53432
53433 return {
53434 x: x,
53435 dx: 0,
53436 dy: 0,
53437 text: text || '',
53438 fontSize: tf.size,
53439 font: tf.family,
53440 fontColor: tf.color
53441 };
53442}
53443
53444function formatDate(ax, out, hover, extraPrecision) {
53445 var tr = ax._tickround;
53446 var fmt = (hover && ax.hoverformat) || axes.getTickFormat(ax);
53447
53448 if(extraPrecision) {
53449 // second or sub-second precision: extra always shows max digits.
53450 // for other fields, extra precision just adds one field.
53451 if(isNumeric(tr)) tr = 4;
53452 else tr = {y: 'm', m: 'd', d: 'M', M: 'S', S: 4}[tr];
53453 }
53454
53455 var dateStr = Lib.formatDate(out.x, fmt, tr, ax._dateFormat, ax.calendar, ax._extraFormat);
53456 var headStr;
53457
53458 var splitIndex = dateStr.indexOf('\n');
53459 if(splitIndex !== -1) {
53460 headStr = dateStr.substr(splitIndex + 1);
53461 dateStr = dateStr.substr(0, splitIndex);
53462 }
53463
53464 if(extraPrecision) {
53465 // if extraPrecision led to trailing zeros, strip them off
53466 // actually, this can lead to removing even more zeros than
53467 // in the original rounding, but that's fine because in these
53468 // contexts uniformity is not so important (if there's even
53469 // anything to be uniform with!)
53470
53471 // can we remove the whole time part?
53472 if(dateStr === '00:00:00' || dateStr === '00:00') {
53473 dateStr = headStr;
53474 headStr = '';
53475 } else if(dateStr.length === 8) {
53476 // strip off seconds if they're zero (zero fractional seconds
53477 // are already omitted)
53478 // but we never remove minutes and leave just hours
53479 dateStr = dateStr.replace(/:00$/, '');
53480 }
53481 }
53482
53483 if(headStr) {
53484 if(hover) {
53485 // hover puts it all on one line, so headPart works best up front
53486 // except for year headPart: turn this into "Jan 1, 2000" etc.
53487 if(tr === 'd') dateStr += ', ' + headStr;
53488 else dateStr = headStr + (dateStr ? ', ' + dateStr : '');
53489 } else if(!ax._inCalcTicks || (headStr !== ax._prevDateHead)) {
53490 dateStr += '<br>' + headStr;
53491 ax._prevDateHead = headStr;
53492 }
53493 }
53494
53495 out.text = dateStr;
53496}
53497
53498function formatLog(ax, out, hover, extraPrecision, hideexp) {
53499 var dtick = ax.dtick;
53500 var x = out.x;
53501 var tickformat = ax.tickformat;
53502 var dtChar0 = typeof dtick === 'string' && dtick.charAt(0);
53503
53504 if(hideexp === 'never') {
53505 // If this is a hover label, then we must *never* hide the exponent
53506 // for the sake of display, which could give the wrong value by
53507 // potentially many orders of magnitude. If hideexp was 'never', then
53508 // it's now succeeded by preventing the other condition from automating
53509 // this choice. Thus we can unset it so that the axis formatting takes
53510 // precedence.
53511 hideexp = '';
53512 }
53513
53514 if(extraPrecision && (dtChar0 !== 'L')) {
53515 dtick = 'L3';
53516 dtChar0 = 'L';
53517 }
53518
53519 if(tickformat || (dtChar0 === 'L')) {
53520 out.text = numFormat(Math.pow(10, x), ax, hideexp, extraPrecision);
53521 } else if(isNumeric(dtick) || ((dtChar0 === 'D') && (Lib.mod(x + 0.01, 1) < 0.1))) {
53522 var p = Math.round(x);
53523 var absP = Math.abs(p);
53524 var exponentFormat = ax.exponentformat;
53525 if(exponentFormat === 'power' || (isSIFormat(exponentFormat) && beyondSI(p))) {
53526 if(p === 0) out.text = 1;
53527 else if(p === 1) out.text = '10';
53528 else out.text = '10<sup>' + (p > 1 ? '' : MINUS_SIGN) + absP + '</sup>';
53529
53530 out.fontSize *= 1.25;
53531 } else if((exponentFormat === 'e' || exponentFormat === 'E') && absP > 2) {
53532 out.text = '1' + exponentFormat + (p > 0 ? '+' : MINUS_SIGN) + absP;
53533 } else {
53534 out.text = numFormat(Math.pow(10, x), ax, '', 'fakehover');
53535 if(dtick === 'D1' && ax._id.charAt(0) === 'y') {
53536 out.dy -= out.fontSize / 6;
53537 }
53538 }
53539 } else if(dtChar0 === 'D') {
53540 out.text = String(Math.round(Math.pow(10, Lib.mod(x, 1))));
53541 out.fontSize *= 0.75;
53542 } else throw 'unrecognized dtick ' + String(dtick);
53543
53544 // if 9's are printed on log scale, move the 10's away a bit
53545 if(ax.dtick === 'D1') {
53546 var firstChar = String(out.text).charAt(0);
53547 if(firstChar === '0' || firstChar === '1') {
53548 if(ax._id.charAt(0) === 'y') {
53549 out.dx -= out.fontSize / 4;
53550 } else {
53551 out.dy += out.fontSize / 2;
53552 out.dx += (ax.range[1] > ax.range[0] ? 1 : -1) *
53553 out.fontSize * (x < 0 ? 0.5 : 0.25);
53554 }
53555 }
53556 }
53557}
53558
53559function formatCategory(ax, out) {
53560 var tt = ax._categories[Math.round(out.x)];
53561 if(tt === undefined) tt = '';
53562 out.text = String(tt);
53563}
53564
53565function formatMultiCategory(ax, out, hover) {
53566 var v = Math.round(out.x);
53567 var cats = ax._categories[v] || [];
53568 var tt = cats[1] === undefined ? '' : String(cats[1]);
53569 var tt2 = cats[0] === undefined ? '' : String(cats[0]);
53570
53571 if(hover) {
53572 // TODO is this what we want?
53573 out.text = tt2 + ' - ' + tt;
53574 } else {
53575 // setup for secondary labels
53576 out.text = tt;
53577 out.text2 = tt2;
53578 }
53579}
53580
53581function formatLinear(ax, out, hover, extraPrecision, hideexp) {
53582 if(hideexp === 'never') {
53583 // If this is a hover label, then we must *never* hide the exponent
53584 // for the sake of display, which could give the wrong value by
53585 // potentially many orders of magnitude. If hideexp was 'never', then
53586 // it's now succeeded by preventing the other condition from automating
53587 // this choice. Thus we can unset it so that the axis formatting takes
53588 // precedence.
53589 hideexp = '';
53590 } else if(ax.showexponent === 'all' && Math.abs(out.x / ax.dtick) < 1e-6) {
53591 // don't add an exponent to zero if we're showing all exponents
53592 // so the only reason you'd show an exponent on zero is if it's the
53593 // ONLY tick to get an exponent (first or last)
53594 hideexp = 'hide';
53595 }
53596 out.text = numFormat(out.x, ax, hideexp, extraPrecision);
53597}
53598
53599function formatAngle(ax, out, hover, extraPrecision, hideexp) {
53600 if(ax.thetaunit === 'radians' && !hover) {
53601 var num = out.x / 180;
53602
53603 if(num === 0) {
53604 out.text = '0';
53605 } else {
53606 var frac = num2frac(num);
53607
53608 if(frac[1] >= 100) {
53609 out.text = numFormat(Lib.deg2rad(out.x), ax, hideexp, extraPrecision);
53610 } else {
53611 var isNeg = out.x < 0;
53612
53613 if(frac[1] === 1) {
53614 if(frac[0] === 1) out.text = 'π';
53615 else out.text = frac[0] + 'π';
53616 } else {
53617 out.text = [
53618 '<sup>', frac[0], '</sup>',
53619 '⁄',
53620 '<sub>', frac[1], '</sub>',
53621 'π'
53622 ].join('');
53623 }
53624
53625 if(isNeg) out.text = MINUS_SIGN + out.text;
53626 }
53627 }
53628 } else {
53629 out.text = numFormat(out.x, ax, hideexp, extraPrecision);
53630 }
53631}
53632
53633// inspired by
53634// https://github.com/yisibl/num2fraction/blob/master/index.js
53635function num2frac(num) {
53636 function almostEq(a, b) {
53637 return Math.abs(a - b) <= 1e-6;
53638 }
53639
53640 function findGCD(a, b) {
53641 return almostEq(b, 0) ? a : findGCD(b, a % b);
53642 }
53643
53644 function findPrecision(n) {
53645 var e = 1;
53646 while(!almostEq(Math.round(n * e) / e, n)) {
53647 e *= 10;
53648 }
53649 return e;
53650 }
53651
53652 var precision = findPrecision(num);
53653 var number = num * precision;
53654 var gcd = Math.abs(findGCD(number, precision));
53655
53656 return [
53657 // numerator
53658 Math.round(number / gcd),
53659 // denominator
53660 Math.round(precision / gcd)
53661 ];
53662}
53663
53664// format a number (tick value) according to the axis settings
53665// new, more reliable procedure than d3.round or similar:
53666// add half the rounding increment, then stringify and truncate
53667// also automatically switch to sci. notation
53668var SIPREFIXES = ['f', 'p', 'n', 'μ', 'm', '', 'k', 'M', 'G', 'T'];
53669
53670function isSIFormat(exponentFormat) {
53671 return exponentFormat === 'SI' || exponentFormat === 'B';
53672}
53673
53674// are we beyond the range of common SI prefixes?
53675// 10^-16 -> 1x10^-16
53676// 10^-15 -> 1f
53677// ...
53678// 10^14 -> 100T
53679// 10^15 -> 1x10^15
53680// 10^16 -> 1x10^16
53681function beyondSI(exponent) {
53682 return exponent > 14 || exponent < -15;
53683}
53684
53685function numFormat(v, ax, fmtoverride, hover) {
53686 var isNeg = v < 0;
53687 // max number of digits past decimal point to show
53688 var tickRound = ax._tickround;
53689 var exponentFormat = fmtoverride || ax.exponentformat || 'B';
53690 var exponent = ax._tickexponent;
53691 var tickformat = axes.getTickFormat(ax);
53692 var separatethousands = ax.separatethousands;
53693
53694 // special case for hover: set exponent just for this value, and
53695 // add a couple more digits of precision over tick labels
53696 if(hover) {
53697 // make a dummy axis obj to get the auto rounding and exponent
53698 var ah = {
53699 exponentformat: exponentFormat,
53700 dtick: ax.showexponent === 'none' ? ax.dtick :
53701 (isNumeric(v) ? Math.abs(v) || 1 : 1),
53702 // if not showing any exponents, don't change the exponent
53703 // from what we calculate
53704 range: ax.showexponent === 'none' ? ax.range.map(ax.r2d) : [0, v || 1]
53705 };
53706 autoTickRound(ah);
53707 tickRound = (Number(ah._tickround) || 0) + 4;
53708 exponent = ah._tickexponent;
53709 if(ax.hoverformat) tickformat = ax.hoverformat;
53710 }
53711
53712 if(tickformat) return ax._numFormat(tickformat)(v).replace(/-/g, MINUS_SIGN);
53713
53714 // 'epsilon' - rounding increment
53715 var e = Math.pow(10, -tickRound) / 2;
53716
53717 // exponentFormat codes:
53718 // 'e' (1.2e+6, default)
53719 // 'E' (1.2E+6)
53720 // 'SI' (1.2M)
53721 // 'B' (same as SI except 10^9=B not G)
53722 // 'none' (1200000)
53723 // 'power' (1.2x10^6)
53724 // 'hide' (1.2, use 3rd argument=='hide' to eg
53725 // only show exponent on last tick)
53726 if(exponentFormat === 'none') exponent = 0;
53727
53728 // take the sign out, put it back manually at the end
53729 // - makes cases easier
53730 v = Math.abs(v);
53731 if(v < e) {
53732 // 0 is just 0, but may get exponent if it's the last tick
53733 v = '0';
53734 isNeg = false;
53735 } else {
53736 v += e;
53737 // take out a common exponent, if any
53738 if(exponent) {
53739 v *= Math.pow(10, -exponent);
53740 tickRound += exponent;
53741 }
53742 // round the mantissa
53743 if(tickRound === 0) v = String(Math.floor(v));
53744 else if(tickRound < 0) {
53745 v = String(Math.round(v));
53746 v = v.substr(0, v.length + tickRound);
53747 for(var i = tickRound; i < 0; i++) v += '0';
53748 } else {
53749 v = String(v);
53750 var dp = v.indexOf('.') + 1;
53751 if(dp) v = v.substr(0, dp + tickRound).replace(/\.?0+$/, '');
53752 }
53753 // insert appropriate decimal point and thousands separator
53754 v = Lib.numSeparate(v, ax._separators, separatethousands);
53755 }
53756
53757 // add exponent
53758 if(exponent && exponentFormat !== 'hide') {
53759 if(isSIFormat(exponentFormat) && beyondSI(exponent)) exponentFormat = 'power';
53760
53761 var signedExponent;
53762 if(exponent < 0) signedExponent = MINUS_SIGN + -exponent;
53763 else if(exponentFormat !== 'power') signedExponent = '+' + exponent;
53764 else signedExponent = String(exponent);
53765
53766 if(exponentFormat === 'e' || exponentFormat === 'E') {
53767 v += exponentFormat + signedExponent;
53768 } else if(exponentFormat === 'power') {
53769 v += '×10<sup>' + signedExponent + '</sup>';
53770 } else if(exponentFormat === 'B' && exponent === 9) {
53771 v += 'B';
53772 } else if(isSIFormat(exponentFormat)) {
53773 v += SIPREFIXES[exponent / 3 + 5];
53774 }
53775 }
53776
53777 // put sign back in and return
53778 // replace standard minus character (which is technically a hyphen)
53779 // with a true minus sign
53780 if(isNeg) return MINUS_SIGN + v;
53781 return v;
53782}
53783
53784axes.getTickFormat = function(ax) {
53785 var i;
53786
53787 function convertToMs(dtick) {
53788 return typeof dtick !== 'string' ? dtick : Number(dtick.replace('M', '')) * ONEAVGMONTH;
53789 }
53790
53791 function compareLogTicks(left, right) {
53792 var priority = ['L', 'D'];
53793 if(typeof left === typeof right) {
53794 if(typeof left === 'number') {
53795 return left - right;
53796 } else {
53797 var leftPriority = priority.indexOf(left.charAt(0));
53798 var rightPriority = priority.indexOf(right.charAt(0));
53799 if(leftPriority === rightPriority) {
53800 return Number(left.replace(/(L|D)/g, '')) - Number(right.replace(/(L|D)/g, ''));
53801 } else {
53802 return leftPriority - rightPriority;
53803 }
53804 }
53805 } else {
53806 return typeof left === 'number' ? 1 : -1;
53807 }
53808 }
53809
53810 function isProperStop(dtick, range, convert) {
53811 var convertFn = convert || function(x) { return x;};
53812 var leftDtick = range[0];
53813 var rightDtick = range[1];
53814 return ((!leftDtick && typeof leftDtick !== 'number') || convertFn(leftDtick) <= convertFn(dtick)) &&
53815 ((!rightDtick && typeof rightDtick !== 'number') || convertFn(rightDtick) >= convertFn(dtick));
53816 }
53817
53818 function isProperLogStop(dtick, range) {
53819 var isLeftDtickNull = range[0] === null;
53820 var isRightDtickNull = range[1] === null;
53821 var isDtickInRangeLeft = compareLogTicks(dtick, range[0]) >= 0;
53822 var isDtickInRangeRight = compareLogTicks(dtick, range[1]) <= 0;
53823 return (isLeftDtickNull || isDtickInRangeLeft) && (isRightDtickNull || isDtickInRangeRight);
53824 }
53825
53826 var tickstop, stopi;
53827 if(ax.tickformatstops && ax.tickformatstops.length > 0) {
53828 switch(ax.type) {
53829 case 'date':
53830 case 'linear': {
53831 for(i = 0; i < ax.tickformatstops.length; i++) {
53832 stopi = ax.tickformatstops[i];
53833 if(stopi.enabled && isProperStop(ax.dtick, stopi.dtickrange, convertToMs)) {
53834 tickstop = stopi;
53835 break;
53836 }
53837 }
53838 break;
53839 }
53840 case 'log': {
53841 for(i = 0; i < ax.tickformatstops.length; i++) {
53842 stopi = ax.tickformatstops[i];
53843 if(stopi.enabled && isProperLogStop(ax.dtick, stopi.dtickrange)) {
53844 tickstop = stopi;
53845 break;
53846 }
53847 }
53848 break;
53849 }
53850 default:
53851 }
53852 }
53853 return tickstop ? tickstop.value : ax.tickformat;
53854};
53855
53856// getSubplots - extract all subplot IDs we need
53857// as an array of items like 'xy', 'x2y', 'x2y2'...
53858// sorted by x (x,x2,x3...) then y
53859// optionally restrict to only subplots containing axis object ax
53860//
53861// NOTE: this is currently only used OUTSIDE plotly.js (toolpanel, webapp)
53862// ideally we get rid of it there (or just copy this there) and remove it here
53863axes.getSubplots = function(gd, ax) {
53864 var subplotObj = gd._fullLayout._subplots;
53865 var allSubplots = subplotObj.cartesian.concat(subplotObj.gl2d || []);
53866
53867 var out = ax ? axes.findSubplotsWithAxis(allSubplots, ax) : allSubplots;
53868
53869 out.sort(function(a, b) {
53870 var aParts = a.substr(1).split('y');
53871 var bParts = b.substr(1).split('y');
53872
53873 if(aParts[0] === bParts[0]) return +aParts[1] - +bParts[1];
53874 return +aParts[0] - +bParts[0];
53875 });
53876
53877 return out;
53878};
53879
53880// find all subplots with axis 'ax'
53881// NOTE: this is only used in axes.getSubplots (only used outside plotly.js) and
53882// gl2d/convert (where it restricts axis subplots to only those with gl2d)
53883axes.findSubplotsWithAxis = function(subplots, ax) {
53884 var axMatch = new RegExp(
53885 (ax._id.charAt(0) === 'x') ? ('^' + ax._id + 'y') : (ax._id + '$')
53886 );
53887 var subplotsWithAx = [];
53888
53889 for(var i = 0; i < subplots.length; i++) {
53890 var sp = subplots[i];
53891 if(axMatch.test(sp)) subplotsWithAx.push(sp);
53892 }
53893
53894 return subplotsWithAx;
53895};
53896
53897// makeClipPaths: prepare clipPaths for all single axes and all possible xy pairings
53898axes.makeClipPaths = function(gd) {
53899 var fullLayout = gd._fullLayout;
53900
53901 // for more info: https://github.com/plotly/plotly.js/issues/2595
53902 if(fullLayout._hasOnlyLargeSploms) return;
53903
53904 var fullWidth = {_offset: 0, _length: fullLayout.width, _id: ''};
53905 var fullHeight = {_offset: 0, _length: fullLayout.height, _id: ''};
53906 var xaList = axes.list(gd, 'x', true);
53907 var yaList = axes.list(gd, 'y', true);
53908 var clipList = [];
53909 var i, j;
53910
53911 for(i = 0; i < xaList.length; i++) {
53912 clipList.push({x: xaList[i], y: fullHeight});
53913 for(j = 0; j < yaList.length; j++) {
53914 if(i === 0) clipList.push({x: fullWidth, y: yaList[j]});
53915 clipList.push({x: xaList[i], y: yaList[j]});
53916 }
53917 }
53918
53919 // selectors don't work right with camelCase tags,
53920 // have to use class instead
53921 // https://groups.google.com/forum/#!topic/d3-js/6EpAzQ2gU9I
53922 var axClips = fullLayout._clips.selectAll('.axesclip')
53923 .data(clipList, function(d) { return d.x._id + d.y._id; });
53924
53925 axClips.enter().append('clipPath')
53926 .classed('axesclip', true)
53927 .attr('id', function(d) { return 'clip' + fullLayout._uid + d.x._id + d.y._id; })
53928 .append('rect');
53929
53930 axClips.exit().remove();
53931
53932 axClips.each(function(d) {
53933 d3.select(this).select('rect').attr({
53934 x: d.x._offset || 0,
53935 y: d.y._offset || 0,
53936 width: d.x._length || 1,
53937 height: d.y._length || 1
53938 });
53939 });
53940};
53941
53942/**
53943 * Main multi-axis drawing routine!
53944 *
53945 * @param {DOM element} gd : graph div
53946 * @param {string or array of strings} arg : polymorphic argument
53947 * @param {object} opts:
53948 * - @param {boolean} skipTitle : optional flag to skip axis title draw/update
53949 *
53950 * Signature 1: Axes.draw(gd, 'redraw')
53951 * use this to clear and redraw all axes on graph
53952 *
53953 * Signature 2: Axes.draw(gd, '')
53954 * use this to draw all axes on graph w/o the selectAll().remove()
53955 * of the 'redraw' signature
53956 *
53957 * Signature 3: Axes.draw(gd, [axId, axId2, ...])
53958 * where the items are axis id string,
53959 * use this to update multiple axes in one call
53960 *
53961 * N.B draw updates:
53962 * - ax._r (stored range for use by zoom/pan)
53963 * - ax._rl (stored linearized range for use by zoom/pan)
53964 */
53965axes.draw = function(gd, arg, opts) {
53966 var fullLayout = gd._fullLayout;
53967
53968 if(arg === 'redraw') {
53969 fullLayout._paper.selectAll('g.subplot').each(function(d) {
53970 var id = d[0];
53971 var plotinfo = fullLayout._plots[id];
53972 var xa = plotinfo.xaxis;
53973 var ya = plotinfo.yaxis;
53974
53975 plotinfo.xaxislayer.selectAll('.' + xa._id + 'tick').remove();
53976 plotinfo.yaxislayer.selectAll('.' + ya._id + 'tick').remove();
53977 plotinfo.xaxislayer.selectAll('.' + xa._id + 'tick2').remove();
53978 plotinfo.yaxislayer.selectAll('.' + ya._id + 'tick2').remove();
53979 plotinfo.xaxislayer.selectAll('.' + xa._id + 'divider').remove();
53980 plotinfo.yaxislayer.selectAll('.' + ya._id + 'divider').remove();
53981
53982 if(plotinfo.gridlayer) plotinfo.gridlayer.selectAll('path').remove();
53983 if(plotinfo.zerolinelayer) plotinfo.zerolinelayer.selectAll('path').remove();
53984
53985 fullLayout._infolayer.select('.g-' + xa._id + 'title').remove();
53986 fullLayout._infolayer.select('.g-' + ya._id + 'title').remove();
53987 });
53988 }
53989
53990 var axList = (!arg || arg === 'redraw') ? axes.listIds(gd) : arg;
53991
53992 return Lib.syncOrAsync(axList.map(function(axId) {
53993 return function() {
53994 if(!axId) return;
53995
53996 var ax = axes.getFromId(gd, axId);
53997 var axDone = axes.drawOne(gd, ax, opts);
53998
53999 ax._r = ax.range.slice();
54000 ax._rl = Lib.simpleMap(ax._r, ax.r2l);
54001
54002 return axDone;
54003 };
54004 }));
54005};
54006
54007/**
54008 * Draw one cartesian axis
54009 *
54010 * @param {DOM element} gd
54011 * @param {object} ax (full) axis object
54012 * @param {object} opts
54013 * - @param {boolean} skipTitle (set to true to skip axis title draw call)
54014 *
54015 * Depends on:
54016 * - ax._mainSubplot (from linkSubplots)
54017 * - ax._mainAxis
54018 * - ax._anchorAxis
54019 * - ax._subplotsWith
54020 * - ax._counterDomainMin, ax._counterDomainMax (optionally, from linkSubplots)
54021 * - ax._tickAngles (on redraw only, old value relinked during supplyDefaults)
54022 * - ax._mainLinePosition (from lsInner)
54023 * - ax._mainMirrorPosition
54024 * - ax._linepositions
54025 *
54026 * Fills in:
54027 * - ax._vals:
54028 * - ax._gridVals:
54029 * - ax._selections:
54030 * - ax._tickAngles:
54031 * - ax._depth (when required only):
54032 * - and calls ax.setScale
54033 */
54034axes.drawOne = function(gd, ax, opts) {
54035 opts = opts || {};
54036
54037 var i, sp, plotinfo;
54038
54039 ax.setScale();
54040
54041 var fullLayout = gd._fullLayout;
54042 var axId = ax._id;
54043 var axLetter = axId.charAt(0);
54044 var counterLetter = axes.counterLetter(axId);
54045 var mainPlotinfo = fullLayout._plots[ax._mainSubplot];
54046
54047 // this happens when updating matched group with 'missing' axes
54048 if(!mainPlotinfo) return;
54049
54050 var mainAxLayer = mainPlotinfo[axLetter + 'axislayer'];
54051 var mainLinePosition = ax._mainLinePosition;
54052 var mainMirrorPosition = ax._mainMirrorPosition;
54053
54054 var vals = ax._vals = axes.calcTicks(ax);
54055
54056 // Add a couple of axis properties that should cause us to recreate
54057 // elements. Used in d3 data function.
54058 var axInfo = [ax.mirror, mainLinePosition, mainMirrorPosition].join('_');
54059 for(i = 0; i < vals.length; i++) {
54060 vals[i].axInfo = axInfo;
54061 }
54062
54063 // stash selections to avoid DOM queries e.g.
54064 // - stash tickLabels selection, so that drawTitle can use it to scoot title
54065 ax._selections = {};
54066 // stash tick angle (including the computed 'auto' values) per tick-label class
54067 // linkup 'previous' tick angles on redraws
54068 if(ax._tickAngles) ax._prevTickAngles = ax._tickAngles;
54069 ax._tickAngles = {};
54070 // measure [in px] between axis position and outward-most part of bounding box
54071 // (touching either the tick label or ticks)
54072 // depth can be expansive to compute, so we only do so when required
54073 ax._depth = null;
54074
54075 // calcLabelLevelBbox can be expensive,
54076 // so make sure to not call it twice during the same Axes.drawOne call
54077 // by stashing label-level bounding boxes per tick-label class
54078 var llbboxes = {};
54079 function getLabelLevelBbox(suffix) {
54080 var cls = axId + (suffix || 'tick');
54081 if(!llbboxes[cls]) llbboxes[cls] = calcLabelLevelBbox(ax, cls);
54082 return llbboxes[cls];
54083 }
54084
54085 if(!ax.visible) return;
54086
54087 var transFn = axes.makeTransFn(ax);
54088 var tickVals;
54089 // We remove zero lines, grid lines, and inside ticks if they're within 1px of the end
54090 // The key case here is removing zero lines when the axis bound is zero
54091 var valsClipped;
54092
54093 if(ax.tickson === 'boundaries') {
54094 var boundaryVals = getBoundaryVals(ax, vals);
54095 valsClipped = axes.clipEnds(ax, boundaryVals);
54096 tickVals = ax.ticks === 'inside' ? valsClipped : boundaryVals;
54097 } else {
54098 valsClipped = axes.clipEnds(ax, vals);
54099 tickVals = ax.ticks === 'inside' ? valsClipped : vals;
54100 }
54101
54102 var gridVals = ax._gridVals = valsClipped;
54103 var dividerVals = getDividerVals(ax, vals);
54104
54105 if(!fullLayout._hasOnlyLargeSploms) {
54106 var subplotsWithAx = ax._subplotsWith;
54107
54108 // keep track of which subplots (by main counter axis) we've already
54109 // drawn grids for, so we don't overdraw overlaying subplots
54110 var finishedGrids = {};
54111
54112 for(i = 0; i < subplotsWithAx.length; i++) {
54113 sp = subplotsWithAx[i];
54114 plotinfo = fullLayout._plots[sp];
54115
54116 var counterAxis = plotinfo[counterLetter + 'axis'];
54117 var mainCounterID = counterAxis._mainAxis._id;
54118 if(finishedGrids[mainCounterID]) continue;
54119 finishedGrids[mainCounterID] = 1;
54120
54121 var gridPath = axLetter === 'x' ?
54122 'M0,' + counterAxis._offset + 'v' + counterAxis._length :
54123 'M' + counterAxis._offset + ',0h' + counterAxis._length;
54124
54125 axes.drawGrid(gd, ax, {
54126 vals: gridVals,
54127 counterAxis: counterAxis,
54128 layer: plotinfo.gridlayer.select('.' + axId),
54129 path: gridPath,
54130 transFn: transFn
54131 });
54132 axes.drawZeroLine(gd, ax, {
54133 counterAxis: counterAxis,
54134 layer: plotinfo.zerolinelayer,
54135 path: gridPath,
54136 transFn: transFn
54137 });
54138 }
54139 }
54140
54141 var tickSigns = axes.getTickSigns(ax);
54142 var tickSubplots = [];
54143
54144 if(ax.ticks) {
54145 var mainTickPath = axes.makeTickPath(ax, mainLinePosition, tickSigns[2]);
54146 var mirrorTickPath;
54147 var fullTickPath;
54148 if(ax._anchorAxis && ax.mirror && ax.mirror !== true) {
54149 mirrorTickPath = axes.makeTickPath(ax, mainMirrorPosition, tickSigns[3]);
54150 fullTickPath = mainTickPath + mirrorTickPath;
54151 } else {
54152 mirrorTickPath = '';
54153 fullTickPath = mainTickPath;
54154 }
54155
54156 var tickPath;
54157 if(ax.showdividers && ax.ticks === 'outside' && ax.tickson === 'boundaries') {
54158 var dividerLookup = {};
54159 for(i = 0; i < dividerVals.length; i++) {
54160 dividerLookup[dividerVals[i].x] = 1;
54161 }
54162 tickPath = function(d) {
54163 return dividerLookup[d.x] ? mirrorTickPath : fullTickPath;
54164 };
54165 } else {
54166 tickPath = fullTickPath;
54167 }
54168
54169 axes.drawTicks(gd, ax, {
54170 vals: tickVals,
54171 layer: mainAxLayer,
54172 path: tickPath,
54173 transFn: transFn
54174 });
54175
54176 if(ax.mirror === 'allticks') {
54177 tickSubplots = Object.keys(ax._linepositions || {});
54178 }
54179 }
54180
54181 for(i = 0; i < tickSubplots.length; i++) {
54182 sp = tickSubplots[i];
54183 plotinfo = fullLayout._plots[sp];
54184 // [bottom or left, top or right], free and main are handled above
54185 var linepositions = ax._linepositions[sp] || [];
54186 var spTickPath = axes.makeTickPath(ax, linepositions[0], tickSigns[0]) +
54187 axes.makeTickPath(ax, linepositions[1], tickSigns[1]);
54188
54189 axes.drawTicks(gd, ax, {
54190 vals: tickVals,
54191 layer: plotinfo[axLetter + 'axislayer'],
54192 path: spTickPath,
54193 transFn: transFn
54194 });
54195 }
54196
54197 var seq = [];
54198
54199 // tick labels - for now just the main labels.
54200 // TODO: mirror labels, esp for subplots
54201
54202 seq.push(function() {
54203 return axes.drawLabels(gd, ax, {
54204 vals: vals,
54205 layer: mainAxLayer,
54206 transFn: transFn,
54207 labelFns: axes.makeLabelFns(ax, mainLinePosition)
54208 });
54209 });
54210
54211 if(ax.type === 'multicategory') {
54212 var pad = {x: 2, y: 10}[axLetter];
54213
54214 seq.push(function() {
54215 var bboxKey = {x: 'height', y: 'width'}[axLetter];
54216 var standoff = getLabelLevelBbox()[bboxKey] + pad +
54217 (ax._tickAngles[axId + 'tick'] ? ax.tickfont.size * LINE_SPACING : 0);
54218
54219 return axes.drawLabels(gd, ax, {
54220 vals: getSecondaryLabelVals(ax, vals),
54221 layer: mainAxLayer,
54222 cls: axId + 'tick2',
54223 repositionOnUpdate: true,
54224 secondary: true,
54225 transFn: transFn,
54226 labelFns: axes.makeLabelFns(ax, mainLinePosition + standoff * tickSigns[4])
54227 });
54228 });
54229
54230 seq.push(function() {
54231 ax._depth = tickSigns[4] * (getLabelLevelBbox('tick2')[ax.side] - mainLinePosition);
54232
54233 return drawDividers(gd, ax, {
54234 vals: dividerVals,
54235 layer: mainAxLayer,
54236 path: axes.makeTickPath(ax, mainLinePosition, tickSigns[4], ax._depth),
54237 transFn: transFn
54238 });
54239 });
54240 } else if(ax.title.hasOwnProperty('standoff')) {
54241 seq.push(function() {
54242 ax._depth = tickSigns[4] * (getLabelLevelBbox()[ax.side] - mainLinePosition);
54243 });
54244 }
54245
54246 var hasRangeSlider = Registry.getComponentMethod('rangeslider', 'isVisible')(ax);
54247
54248 seq.push(function() {
54249 var s = ax.side.charAt(0);
54250 var sMirror = OPPOSITE_SIDE[ax.side].charAt(0);
54251 var pos = axes.getPxPosition(gd, ax);
54252 var outsideTickLen = ax.ticks === 'outside' ? ax.ticklen : 0;
54253 var llbbox;
54254
54255 var push;
54256 var mirrorPush;
54257 var rangeSliderPush;
54258
54259 if(ax.automargin || hasRangeSlider) {
54260 if(ax.type === 'multicategory') {
54261 llbbox = getLabelLevelBbox('tick2');
54262 } else {
54263 llbbox = getLabelLevelBbox();
54264 if(axLetter === 'x' && s === 'b') {
54265 ax._depth = Math.max(llbbox.width > 0 ? llbbox.bottom - pos : 0, outsideTickLen);
54266 }
54267 }
54268 }
54269
54270 if(ax.automargin) {
54271 push = {x: 0, y: 0, r: 0, l: 0, t: 0, b: 0};
54272 var domainIndices = [0, 1];
54273
54274 if(axLetter === 'x') {
54275 if(s === 'b') {
54276 push[s] = ax._depth;
54277 } else {
54278 push[s] = ax._depth = Math.max(llbbox.width > 0 ? pos - llbbox.top : 0, outsideTickLen);
54279 domainIndices.reverse();
54280 }
54281
54282 if(llbbox.width > 0) {
54283 var rExtra = llbbox.right - (ax._offset + ax._length);
54284 if(rExtra > 0) {
54285 push.xr = 1;
54286 push.r = rExtra;
54287 }
54288 var lExtra = ax._offset - llbbox.left;
54289 if(lExtra > 0) {
54290 push.xl = 0;
54291 push.l = lExtra;
54292 }
54293 }
54294 } else {
54295 if(s === 'l') {
54296 push[s] = ax._depth = Math.max(llbbox.height > 0 ? pos - llbbox.left : 0, outsideTickLen);
54297 } else {
54298 push[s] = ax._depth = Math.max(llbbox.height > 0 ? llbbox.right - pos : 0, outsideTickLen);
54299 domainIndices.reverse();
54300 }
54301
54302 if(llbbox.height > 0) {
54303 var bExtra = llbbox.bottom - (ax._offset + ax._length);
54304 if(bExtra > 0) {
54305 push.yb = 0;
54306 push.b = bExtra;
54307 }
54308 var tExtra = ax._offset - llbbox.top;
54309 if(tExtra > 0) {
54310 push.yt = 1;
54311 push.t = tExtra;
54312 }
54313 }
54314 }
54315
54316 push[counterLetter] = ax.anchor === 'free' ?
54317 ax.position :
54318 ax._anchorAxis.domain[domainIndices[0]];
54319
54320 if(ax.title.text !== fullLayout._dfltTitle[axLetter]) {
54321 push[s] += approxTitleDepth(ax) + (ax.title.standoff || 0);
54322 }
54323
54324 if(ax.mirror && ax.anchor !== 'free') {
54325 mirrorPush = {x: 0, y: 0, r: 0, l: 0, t: 0, b: 0};
54326
54327 mirrorPush[sMirror] = ax.linewidth;
54328 if(ax.mirror && ax.mirror !== true) mirrorPush[sMirror] += outsideTickLen;
54329
54330 if(ax.mirror === true || ax.mirror === 'ticks') {
54331 mirrorPush[counterLetter] = ax._anchorAxis.domain[domainIndices[1]];
54332 } else if(ax.mirror === 'all' || ax.mirror === 'allticks') {
54333 mirrorPush[counterLetter] = [ax._counterDomainMin, ax._counterDomainMax][domainIndices[1]];
54334 }
54335 }
54336 }
54337
54338 if(hasRangeSlider) {
54339 rangeSliderPush = Registry.getComponentMethod('rangeslider', 'autoMarginOpts')(gd, ax);
54340 }
54341
54342 Plots.autoMargin(gd, axAutoMarginID(ax), push);
54343 Plots.autoMargin(gd, axMirrorAutoMarginID(ax), mirrorPush);
54344 Plots.autoMargin(gd, rangeSliderAutoMarginID(ax), rangeSliderPush);
54345 });
54346
54347 if(!opts.skipTitle &&
54348 !(hasRangeSlider && ax.side === 'bottom')
54349 ) {
54350 seq.push(function() { return drawTitle(gd, ax); });
54351 }
54352
54353 return Lib.syncOrAsync(seq);
54354};
54355
54356function getBoundaryVals(ax, vals) {
54357 var out = [];
54358 var i;
54359
54360 // boundaryVals are never used for labels;
54361 // no need to worry about the other tickTextObj keys
54362 var _push = function(d, bndIndex) {
54363 var xb = d.xbnd[bndIndex];
54364 if(xb !== null) {
54365 out.push(Lib.extendFlat({}, d, {x: xb}));
54366 }
54367 };
54368
54369 if(vals.length) {
54370 for(i = 0; i < vals.length; i++) {
54371 _push(vals[i], 0);
54372 }
54373 _push(vals[i - 1], 1);
54374 }
54375
54376 return out;
54377}
54378
54379function getSecondaryLabelVals(ax, vals) {
54380 var out = [];
54381 var lookup = {};
54382
54383 for(var i = 0; i < vals.length; i++) {
54384 var d = vals[i];
54385 if(lookup[d.text2]) {
54386 lookup[d.text2].push(d.x);
54387 } else {
54388 lookup[d.text2] = [d.x];
54389 }
54390 }
54391
54392 for(var k in lookup) {
54393 out.push(tickTextObj(ax, Lib.interp(lookup[k], 0.5), k));
54394 }
54395
54396 return out;
54397}
54398
54399function getDividerVals(ax, vals) {
54400 var out = [];
54401 var i, current;
54402
54403 // never used for labels;
54404 // no need to worry about the other tickTextObj keys
54405 var _push = function(d, bndIndex) {
54406 var xb = d.xbnd[bndIndex];
54407 if(xb !== null) {
54408 out.push(Lib.extendFlat({}, d, {x: xb}));
54409 }
54410 };
54411
54412 if(ax.showdividers && vals.length) {
54413 for(i = 0; i < vals.length; i++) {
54414 var d = vals[i];
54415 if(d.text2 !== current) {
54416 _push(d, 0);
54417 }
54418 current = d.text2;
54419 }
54420 _push(vals[i - 1], 1);
54421 }
54422
54423 return out;
54424}
54425
54426function calcLabelLevelBbox(ax, cls) {
54427 var top, bottom;
54428 var left, right;
54429
54430 if(ax._selections[cls].size()) {
54431 top = Infinity;
54432 bottom = -Infinity;
54433 left = Infinity;
54434 right = -Infinity;
54435 ax._selections[cls].each(function() {
54436 var thisLabel = selectTickLabel(this);
54437 // Use parent node <g.(x|y)tick>, to make Drawing.bBox
54438 // retrieve a bbox computed with transform info
54439 //
54440 // To improve perf, it would be nice to use `thisLabel.node()`
54441 // (like in fixLabelOverlaps) instead and use Axes.getPxPosition
54442 // together with the makeLabelFns outputs and `tickangle`
54443 // to compute one bbox per (tick value x tick style)
54444 var bb = Drawing.bBox(thisLabel.node().parentNode);
54445 top = Math.min(top, bb.top);
54446 bottom = Math.max(bottom, bb.bottom);
54447 left = Math.min(left, bb.left);
54448 right = Math.max(right, bb.right);
54449 });
54450 } else {
54451 top = 0;
54452 bottom = 0;
54453 left = 0;
54454 right = 0;
54455 }
54456
54457 return {
54458 top: top,
54459 bottom: bottom,
54460 left: left,
54461 right: right,
54462 height: bottom - top,
54463 width: right - left
54464 };
54465}
54466
54467/**
54468 * Which direction do the 'ax.side' values, and free ticks go?
54469 *
54470 * @param {object} ax (full) axis object
54471 * - {string} _id (starting with 'x' or 'y')
54472 * - {string} side
54473 * - {string} ticks
54474 * @return {array} all entries are either -1 or 1
54475 * - [0]: sign for top/right ticks (i.e. negative SVG direction)
54476 * - [1]: sign for bottom/left ticks (i.e. positive SVG direction)
54477 * - [2]: sign for ticks corresponding to 'ax.side'
54478 * - [3]: sign for ticks mirroring 'ax.side'
54479 * - [4]: sign of arrow starting at axis pointing towards margin
54480 */
54481axes.getTickSigns = function(ax) {
54482 var axLetter = ax._id.charAt(0);
54483 var sideOpposite = {x: 'top', y: 'right'}[axLetter];
54484 var main = ax.side === sideOpposite ? 1 : -1;
54485 var out = [-1, 1, main, -main];
54486 // then we flip if outside XOR y axis
54487 if((ax.ticks !== 'inside') === (axLetter === 'x')) {
54488 out = out.map(function(v) { return -v; });
54489 }
54490 // independent of `ticks`; do not flip this one
54491 if(ax.side) {
54492 out.push({l: -1, t: -1, r: 1, b: 1}[ax.side.charAt(0)]);
54493 }
54494 return out;
54495};
54496
54497/**
54498 * Make axis translate transform function
54499 *
54500 * @param {object} ax (full) axis object
54501 * - {string} _id
54502 * - {number} _offset
54503 * - {fn} l2p
54504 * @return {fn} function of calcTicks items
54505 */
54506axes.makeTransFn = function(ax) {
54507 var axLetter = ax._id.charAt(0);
54508 var offset = ax._offset;
54509 return axLetter === 'x' ?
54510 function(d) { return 'translate(' + (offset + ax.l2p(d.x)) + ',0)'; } :
54511 function(d) { return 'translate(0,' + (offset + ax.l2p(d.x)) + ')'; };
54512};
54513
54514/**
54515 * Make axis tick path string
54516 *
54517 * @param {object} ax (full) axis object
54518 * - {string} _id
54519 * - {number} ticklen
54520 * - {number} linewidth
54521 * @param {number} shift along direction of ticklen
54522 * @param {1 or -1} sgn tick sign
54523 * @param {number (optional)} len tick length
54524 * @return {string}
54525 */
54526axes.makeTickPath = function(ax, shift, sgn, len) {
54527 len = len !== undefined ? len : ax.ticklen;
54528
54529 var axLetter = ax._id.charAt(0);
54530 var pad = (ax.linewidth || 1) / 2;
54531
54532 return axLetter === 'x' ?
54533 'M0,' + (shift + pad * sgn) + 'v' + (len * sgn) :
54534 'M' + (shift + pad * sgn) + ',0h' + (len * sgn);
54535};
54536
54537/**
54538 * Make axis tick label x, y and anchor functions
54539 *
54540 * @param {object} ax (full) axis object
54541 * - {string} _id
54542 * - {string} ticks
54543 * - {number} ticklen
54544 * - {string} side
54545 * - {number} linewidth
54546 * - {number} tickfont.size
54547 * - {boolean} showline
54548 * @param {number} shift
54549 * @param {number} angle [in degrees] ...
54550 * @return {object}
54551 * - {fn} xFn
54552 * - {fn} yFn
54553 * - {fn} anchorFn
54554 * - {fn} heightFn
54555 * - {number} labelStandoff (gap parallel to ticks)
54556 * - {number} labelShift (gap perpendicular to ticks)
54557 */
54558axes.makeLabelFns = function(ax, shift, angle) {
54559 var axLetter = ax._id.charAt(0);
54560 var ticksOnOutsideLabels = ax.tickson !== 'boundaries' && ax.ticks === 'outside';
54561
54562 var labelStandoff = 0;
54563 var labelShift = 0;
54564
54565 if(ticksOnOutsideLabels) {
54566 labelStandoff += ax.ticklen;
54567 }
54568 if(angle && ax.ticks === 'outside') {
54569 var rad = Lib.deg2rad(angle);
54570 labelStandoff = ax.ticklen * Math.cos(rad) + 1;
54571 labelShift = ax.ticklen * Math.sin(rad);
54572 }
54573 if(ax.showticklabels && (ticksOnOutsideLabels || ax.showline)) {
54574 labelStandoff += 0.2 * ax.tickfont.size;
54575 }
54576 labelStandoff += (ax.linewidth || 1) / 2;
54577
54578 var out = {
54579 labelStandoff: labelStandoff,
54580 labelShift: labelShift
54581 };
54582
54583 var x0, y0, ff, flipIt;
54584
54585 if(axLetter === 'x') {
54586 flipIt = ax.side === 'bottom' ? 1 : -1;
54587 x0 = labelShift * flipIt;
54588 y0 = shift + labelStandoff * flipIt;
54589 ff = ax.side === 'bottom' ? 1 : -0.2;
54590
54591 out.xFn = function(d) { return d.dx + x0; };
54592 out.yFn = function(d) { return d.dy + y0 + d.fontSize * ff; };
54593 out.anchorFn = function(d, a) {
54594 if(!isNumeric(a) || a === 0 || a === 180) {
54595 return 'middle';
54596 }
54597 return (a * flipIt < 0) ? 'end' : 'start';
54598 };
54599 out.heightFn = function(d, a, h) {
54600 return (a < -60 || a > 60) ? -0.5 * h :
54601 ax.side === 'top' ? -h :
54602 0;
54603 };
54604 } else if(axLetter === 'y') {
54605 flipIt = ax.side === 'right' ? 1 : -1;
54606 x0 = labelStandoff;
54607 y0 = -labelShift * flipIt;
54608 ff = Math.abs(ax.tickangle) === 90 ? 0.5 : 0;
54609
54610 out.xFn = function(d) { return d.dx + shift + (x0 + d.fontSize * ff) * flipIt; };
54611 out.yFn = function(d) { return d.dy + y0 + d.fontSize * MID_SHIFT; };
54612 out.anchorFn = function(d, a) {
54613 if(isNumeric(a) && Math.abs(a) === 90) {
54614 return 'middle';
54615 }
54616 return ax.side === 'right' ? 'start' : 'end';
54617 };
54618 out.heightFn = function(d, a, h) {
54619 a *= ax.side === 'left' ? 1 : -1;
54620 return a < -30 ? -h :
54621 a < 30 ? -0.5 * h :
54622 0;
54623 };
54624 }
54625
54626 return out;
54627};
54628
54629function tickDataFn(d) {
54630 return [d.text, d.x, d.axInfo, d.font, d.fontSize, d.fontColor].join('_');
54631}
54632
54633/**
54634 * Draw axis ticks
54635 *
54636 * @param {DOM element} gd
54637 * @param {object} ax (full) axis object
54638 * - {string} _id
54639 * - {string} ticks
54640 * - {number} linewidth
54641 * - {string} tickcolor
54642 * @param {object} opts
54643 * - {array of object} vals (calcTicks output-like)
54644 * - {d3 selection} layer
54645 * - {string or fn} path
54646 * - {fn} transFn
54647 * - {boolean} crisp (set to false to unset crisp-edge SVG rendering)
54648 */
54649axes.drawTicks = function(gd, ax, opts) {
54650 opts = opts || {};
54651
54652 var cls = ax._id + 'tick';
54653
54654 var ticks = opts.layer.selectAll('path.' + cls)
54655 .data(ax.ticks ? opts.vals : [], tickDataFn);
54656
54657 ticks.exit().remove();
54658
54659 ticks.enter().append('path')
54660 .classed(cls, 1)
54661 .classed('ticks', 1)
54662 .classed('crisp', opts.crisp !== false)
54663 .call(Color.stroke, ax.tickcolor)
54664 .style('stroke-width', Drawing.crispRound(gd, ax.tickwidth, 1) + 'px')
54665 .attr('d', opts.path);
54666
54667 ticks.attr('transform', opts.transFn);
54668};
54669
54670/**
54671 * Draw axis grid
54672 *
54673 * @param {DOM element} gd
54674 * @param {object} ax (full) axis object
54675 * - {string} _id
54676 * - {boolean} showgrid
54677 * - {string} gridcolor
54678 * - {string} gridwidth
54679 * - {boolean} zeroline
54680 * - {string} type
54681 * - {string} dtick
54682 * @param {object} opts
54683 * - {array of object} vals (calcTicks output-like)
54684 * - {d3 selection} layer
54685 * - {object} counterAxis (full axis object corresponding to counter axis)
54686 * optional - only required if this axis supports zero lines
54687 * - {string or fn} path
54688 * - {fn} transFn
54689 * - {boolean} crisp (set to false to unset crisp-edge SVG rendering)
54690 */
54691axes.drawGrid = function(gd, ax, opts) {
54692 opts = opts || {};
54693
54694 var cls = ax._id + 'grid';
54695 var vals = opts.vals;
54696 var counterAx = opts.counterAxis;
54697 if(ax.showgrid === false) {
54698 vals = [];
54699 } else if(counterAx && axes.shouldShowZeroLine(gd, ax, counterAx)) {
54700 var isArrayMode = ax.tickmode === 'array';
54701 for(var i = 0; i < vals.length; i++) {
54702 var xi = vals[i].x;
54703 if(isArrayMode ? !xi : (Math.abs(xi) < ax.dtick / 100)) {
54704 vals = vals.slice(0, i).concat(vals.slice(i + 1));
54705 // In array mode you can in principle have multiple
54706 // ticks at 0, so test them all. Otherwise once we found
54707 // one we can stop.
54708 if(isArrayMode) i--;
54709 else break;
54710 }
54711 }
54712 }
54713
54714 var grid = opts.layer.selectAll('path.' + cls)
54715 .data(vals, tickDataFn);
54716
54717 grid.exit().remove();
54718
54719 grid.enter().append('path')
54720 .classed(cls, 1)
54721 .classed('crisp', opts.crisp !== false);
54722
54723 ax._gw = Drawing.crispRound(gd, ax.gridwidth, 1);
54724
54725 grid.attr('transform', opts.transFn)
54726 .attr('d', opts.path)
54727 .call(Color.stroke, ax.gridcolor || '#ddd')
54728 .style('stroke-width', ax._gw + 'px');
54729
54730 if(typeof opts.path === 'function') grid.attr('d', opts.path);
54731};
54732
54733/**
54734 * Draw axis zero-line
54735 *
54736 * @param {DOM element} gd
54737 * @param {object} ax (full) axis object
54738 * - {string} _id
54739 * - {boolean} zeroline
54740 * - {number} zerolinewidth
54741 * - {string} zerolinecolor
54742 * - {number (optional)} _gridWidthCrispRound
54743 * @param {object} opts
54744 * - {d3 selection} layer
54745 * - {object} counterAxis (full axis object corresponding to counter axis)
54746 * - {string or fn} path
54747 * - {fn} transFn
54748 * - {boolean} crisp (set to false to unset crisp-edge SVG rendering)
54749 */
54750axes.drawZeroLine = function(gd, ax, opts) {
54751 opts = opts || opts;
54752
54753 var cls = ax._id + 'zl';
54754 var show = axes.shouldShowZeroLine(gd, ax, opts.counterAxis);
54755
54756 var zl = opts.layer.selectAll('path.' + cls)
54757 .data(show ? [{x: 0, id: ax._id}] : []);
54758
54759 zl.exit().remove();
54760
54761 zl.enter().append('path')
54762 .classed(cls, 1)
54763 .classed('zl', 1)
54764 .classed('crisp', opts.crisp !== false)
54765 .each(function() {
54766 // use the fact that only one element can enter to trigger a sort.
54767 // If several zerolines enter at the same time we will sort once per,
54768 // but generally this should be a minimal overhead.
54769 opts.layer.selectAll('path').sort(function(da, db) {
54770 return axisIds.idSort(da.id, db.id);
54771 });
54772 });
54773
54774 zl.attr('transform', opts.transFn)
54775 .attr('d', opts.path)
54776 .call(Color.stroke, ax.zerolinecolor || Color.defaultLine)
54777 .style('stroke-width', Drawing.crispRound(gd, ax.zerolinewidth, ax._gw || 1) + 'px');
54778};
54779
54780/**
54781 * Draw axis tick labels
54782 *
54783 * @param {DOM element} gd
54784 * @param {object} ax (full) axis object
54785 * - {string} _id
54786 * - {boolean} showticklabels
54787 * - {number} tickangle
54788 * - {object (optional)} _selections
54789 * - {object} (optional)} _tickAngles
54790 * - {object} (optional)} _prevTickAngles
54791 * @param {object} opts
54792 * - {array of object} vals (calcTicks output-like)
54793 * - {d3 selection} layer
54794 * - {string (optional)} cls (node className)
54795 * - {boolean} repositionOnUpdate (set to true to reposition update selection)
54796 * - {boolean} secondary
54797 * - {fn} transFn
54798 * - {object} labelFns
54799 * + {fn} xFn
54800 * + {fn} yFn
54801 * + {fn} anchorFn
54802 * + {fn} heightFn
54803 */
54804axes.drawLabels = function(gd, ax, opts) {
54805 opts = opts || {};
54806
54807 var fullLayout = gd._fullLayout;
54808 var axId = ax._id;
54809 var axLetter = axId.charAt(0);
54810 var cls = opts.cls || axId + 'tick';
54811 var vals = opts.vals;
54812 var labelFns = opts.labelFns;
54813 var tickAngle = opts.secondary ? 0 : ax.tickangle;
54814 var prevAngle = (ax._prevTickAngles || {})[cls];
54815
54816 var tickLabels = opts.layer.selectAll('g.' + cls)
54817 .data(ax.showticklabels ? vals : [], tickDataFn);
54818
54819 var labelsReady = [];
54820
54821 tickLabels.enter().append('g')
54822 .classed(cls, 1)
54823 .append('text')
54824 // only so tex has predictable alignment that we can
54825 // alter later
54826 .attr('text-anchor', 'middle')
54827 .each(function(d) {
54828 var thisLabel = d3.select(this);
54829 var newPromise = gd._promises.length;
54830
54831 thisLabel
54832 .call(svgTextUtils.positionText, labelFns.xFn(d), labelFns.yFn(d))
54833 .call(Drawing.font, d.font, d.fontSize, d.fontColor)
54834 .text(d.text)
54835 .call(svgTextUtils.convertToTspans, gd);
54836
54837 if(gd._promises[newPromise]) {
54838 // if we have an async label, we'll deal with that
54839 // all here so take it out of gd._promises and
54840 // instead position the label and promise this in
54841 // labelsReady
54842 labelsReady.push(gd._promises.pop().then(function() {
54843 positionLabels(thisLabel, tickAngle);
54844 }));
54845 } else {
54846 // sync label: just position it now.
54847 positionLabels(thisLabel, tickAngle);
54848 }
54849 });
54850
54851 tickLabels.exit().remove();
54852
54853 if(opts.repositionOnUpdate) {
54854 tickLabels.each(function(d) {
54855 d3.select(this).select('text')
54856 .call(svgTextUtils.positionText, labelFns.xFn(d), labelFns.yFn(d));
54857 });
54858 }
54859
54860 function positionLabels(s, angle) {
54861 s.each(function(d) {
54862 var thisLabel = d3.select(this);
54863 var mathjaxGroup = thisLabel.select('.text-math-group');
54864 var anchor = labelFns.anchorFn(d, angle);
54865
54866 var transform = opts.transFn.call(thisLabel.node(), d) +
54867 ((isNumeric(angle) && +angle !== 0) ?
54868 (' rotate(' + angle + ',' + labelFns.xFn(d) + ',' +
54869 (labelFns.yFn(d) - d.fontSize / 2) + ')') :
54870 '');
54871
54872 // how much to shift a multi-line label to center it vertically.
54873 var nLines = svgTextUtils.lineCount(thisLabel);
54874 var lineHeight = LINE_SPACING * d.fontSize;
54875 var anchorHeight = labelFns.heightFn(d, isNumeric(angle) ? +angle : 0, (nLines - 1) * lineHeight);
54876
54877 if(anchorHeight) {
54878 transform += ' translate(0, ' + anchorHeight + ')';
54879 }
54880
54881 if(mathjaxGroup.empty()) {
54882 thisLabel.select('text').attr({
54883 transform: transform,
54884 'text-anchor': anchor
54885 });
54886 } else {
54887 var mjWidth = Drawing.bBox(mathjaxGroup.node()).width;
54888 var mjShift = mjWidth * {end: -0.5, start: 0.5}[anchor];
54889 mathjaxGroup.attr('transform', transform + (mjShift ? 'translate(' + mjShift + ',0)' : ''));
54890 }
54891 });
54892 }
54893
54894 // make sure all labels are correctly positioned at their base angle
54895 // the positionLabels call above is only for newly drawn labels.
54896 // do this without waiting, using the last calculated angle to
54897 // minimize flicker, then do it again when we know all labels are
54898 // there, putting back the prescribed angle to check for overlaps.
54899 positionLabels(tickLabels, (prevAngle + 1) ? prevAngle : tickAngle);
54900
54901 function allLabelsReady() {
54902 return labelsReady.length && Promise.all(labelsReady);
54903 }
54904
54905 var autoangle = null;
54906
54907 function fixLabelOverlaps() {
54908 positionLabels(tickLabels, tickAngle);
54909
54910 // check for auto-angling if x labels overlap
54911 // don't auto-angle at all for log axes with
54912 // base and digit format
54913 if(vals.length && axLetter === 'x' && !isNumeric(tickAngle) &&
54914 (ax.type !== 'log' || String(ax.dtick).charAt(0) !== 'D')
54915 ) {
54916 autoangle = 0;
54917
54918 var maxFontSize = 0;
54919 var lbbArray = [];
54920 var i;
54921
54922 tickLabels.each(function(d) {
54923 maxFontSize = Math.max(maxFontSize, d.fontSize);
54924
54925 var x = ax.l2p(d.x);
54926 var thisLabel = selectTickLabel(this);
54927 var bb = Drawing.bBox(thisLabel.node());
54928
54929 lbbArray.push({
54930 // ignore about y, just deal with x overlaps
54931 top: 0,
54932 bottom: 10,
54933 height: 10,
54934 left: x - bb.width / 2,
54935 // impose a 2px gap
54936 right: x + bb.width / 2 + 2,
54937 width: bb.width + 2
54938 });
54939 });
54940
54941 if((ax.tickson === 'boundaries' || ax.showdividers) && !opts.secondary) {
54942 var gap = 2;
54943 if(ax.ticks) gap += ax.tickwidth / 2;
54944
54945 // TODO should secondary labels also fall into this fix-overlap regime?
54946
54947 for(i = 0; i < lbbArray.length; i++) {
54948 var xbnd = vals[i].xbnd;
54949 var lbb = lbbArray[i];
54950 if(
54951 (xbnd[0] !== null && (lbb.left - ax.l2p(xbnd[0])) < gap) ||
54952 (xbnd[1] !== null && (ax.l2p(xbnd[1]) - lbb.right) < gap)
54953 ) {
54954 autoangle = 90;
54955 break;
54956 }
54957 }
54958 } else {
54959 var vLen = vals.length;
54960 var tickSpacing = Math.abs((vals[vLen - 1].x - vals[0].x) * ax._m) / (vLen - 1);
54961 var rotate90 = (tickSpacing < maxFontSize * 2.5) || ax.type === 'multicategory';
54962
54963 // any overlap at all - set 30 degrees or 90 degrees
54964 for(i = 0; i < lbbArray.length - 1; i++) {
54965 if(Lib.bBoxIntersect(lbbArray[i], lbbArray[i + 1])) {
54966 autoangle = rotate90 ? 90 : 30;
54967 break;
54968 }
54969 }
54970 }
54971
54972 if(autoangle) {
54973 positionLabels(tickLabels, autoangle);
54974 }
54975 }
54976 }
54977
54978 if(ax._selections) {
54979 ax._selections[cls] = tickLabels;
54980 }
54981
54982 var seq = [allLabelsReady];
54983
54984 // N.B. during auto-margin redraws, if the axis fixed its label overlaps
54985 // by rotating 90 degrees, do not attempt to re-fix its label overlaps
54986 // as this can lead to infinite redraw loops!
54987 if(ax.automargin && fullLayout._redrawFromAutoMarginCount && prevAngle === 90) {
54988 autoangle = 90;
54989 seq.push(function() {
54990 positionLabels(tickLabels, prevAngle);
54991 });
54992 } else {
54993 seq.push(fixLabelOverlaps);
54994 }
54995
54996 // save current tick angle for future redraws
54997 if(ax._tickAngles) {
54998 seq.push(function() {
54999 ax._tickAngles[cls] = autoangle === null ?
55000 (isNumeric(tickAngle) ? tickAngle : 0) :
55001 autoangle;
55002 });
55003 }
55004
55005 var done = Lib.syncOrAsync(seq);
55006 if(done && done.then) gd._promises.push(done);
55007 return done;
55008};
55009
55010/**
55011 * Draw axis dividers
55012 *
55013 * @param {DOM element} gd
55014 * @param {object} ax (full) axis object
55015 * - {string} _id
55016 * - {string} showdividers
55017 * - {number} dividerwidth
55018 * - {string} dividercolor
55019 * @param {object} opts
55020 * - {array of object} vals (calcTicks output-like)
55021 * - {d3 selection} layer
55022 * - {fn} path
55023 * - {fn} transFn
55024 */
55025function drawDividers(gd, ax, opts) {
55026 var cls = ax._id + 'divider';
55027 var vals = opts.vals;
55028
55029 var dividers = opts.layer.selectAll('path.' + cls)
55030 .data(vals, tickDataFn);
55031
55032 dividers.exit().remove();
55033
55034 dividers.enter().insert('path', ':first-child')
55035 .classed(cls, 1)
55036 .classed('crisp', 1)
55037 .call(Color.stroke, ax.dividercolor)
55038 .style('stroke-width', Drawing.crispRound(gd, ax.dividerwidth, 1) + 'px');
55039
55040 dividers
55041 .attr('transform', opts.transFn)
55042 .attr('d', opts.path);
55043}
55044
55045/**
55046 * Get axis position in px, that is the distance for the graph's
55047 * top (left) edge for x (y) axes.
55048 *
55049 * @param {DOM element} gd
55050 * @param {object} ax (full) axis object
55051 * - {string} _id
55052 * - {string} side
55053 * if anchored:
55054 * - {object} _anchorAxis
55055 * Otherwise:
55056 * - {number} position
55057 * @return {number}
55058 */
55059axes.getPxPosition = function(gd, ax) {
55060 var gs = gd._fullLayout._size;
55061 var axLetter = ax._id.charAt(0);
55062 var side = ax.side;
55063 var anchorAxis;
55064
55065 if(ax.anchor !== 'free') {
55066 anchorAxis = ax._anchorAxis;
55067 } else if(axLetter === 'x') {
55068 anchorAxis = {
55069 _offset: gs.t + (1 - (ax.position || 0)) * gs.h,
55070 _length: 0
55071 };
55072 } else if(axLetter === 'y') {
55073 anchorAxis = {
55074 _offset: gs.l + (ax.position || 0) * gs.w,
55075 _length: 0
55076 };
55077 }
55078
55079 if(side === 'top' || side === 'left') {
55080 return anchorAxis._offset;
55081 } else if(side === 'bottom' || side === 'right') {
55082 return anchorAxis._offset + anchorAxis._length;
55083 }
55084};
55085
55086/**
55087 * Approximate axis title depth (w/o computing its bounding box)
55088 *
55089 * @param {object} ax (full) axis object
55090 * - {string} title.text
55091 * - {number} title.font.size
55092 * - {number} title.standoff
55093 * @return {number} (in px)
55094 */
55095function approxTitleDepth(ax) {
55096 var fontSize = ax.title.font.size;
55097 var extraLines = (ax.title.text.match(svgTextUtils.BR_TAG_ALL) || []).length;
55098 if(ax.title.hasOwnProperty('standoff')) {
55099 return extraLines ?
55100 fontSize * (CAP_SHIFT + (extraLines * LINE_SPACING)) :
55101 fontSize * CAP_SHIFT;
55102 } else {
55103 return extraLines ?
55104 fontSize * (extraLines + 1) * LINE_SPACING :
55105 fontSize;
55106 }
55107}
55108
55109/**
55110 * Draw axis title, compute default standoff if necessary
55111 *
55112 * @param {DOM element} gd
55113 * @param {object} ax (full) axis object
55114 * - {string} _id
55115 * - {string} _name
55116 * - {string} side
55117 * - {number} title.font.size
55118 * - {object} _selections
55119 *
55120 * - {number} _depth
55121 * - {number} title.standoff
55122 * OR
55123 * - {number} linewidth
55124 * - {boolean} showticklabels
55125 */
55126function drawTitle(gd, ax) {
55127 var fullLayout = gd._fullLayout;
55128 var axId = ax._id;
55129 var axLetter = axId.charAt(0);
55130 var fontSize = ax.title.font.size;
55131
55132 var titleStandoff;
55133
55134 if(ax.title.hasOwnProperty('standoff')) {
55135 titleStandoff = ax._depth + ax.title.standoff + approxTitleDepth(ax);
55136 } else {
55137 if(ax.type === 'multicategory') {
55138 titleStandoff = ax._depth;
55139 } else {
55140 var offsetBase = 1.5;
55141 titleStandoff = 10 + fontSize * offsetBase + (ax.linewidth ? ax.linewidth - 1 : 0);
55142 }
55143
55144 if(axLetter === 'x') {
55145 titleStandoff += ax.side === 'top' ?
55146 fontSize * (ax.showticklabels ? 1 : 0) :
55147 fontSize * (ax.showticklabels ? 1.5 : 0.5);
55148 } else {
55149 titleStandoff += ax.side === 'right' ?
55150 fontSize * (ax.showticklabels ? 1 : 0.5) :
55151 fontSize * (ax.showticklabels ? 0.5 : 0);
55152 }
55153 }
55154
55155 var pos = axes.getPxPosition(gd, ax);
55156 var transform, x, y;
55157
55158 if(axLetter === 'x') {
55159 x = ax._offset + ax._length / 2;
55160 y = (ax.side === 'top') ? pos - titleStandoff : pos + titleStandoff;
55161 } else {
55162 y = ax._offset + ax._length / 2;
55163 x = (ax.side === 'right') ? pos + titleStandoff : pos - titleStandoff;
55164 transform = {rotate: '-90', offset: 0};
55165 }
55166
55167 var avoid;
55168
55169 if(ax.type !== 'multicategory') {
55170 var tickLabels = ax._selections[ax._id + 'tick'];
55171
55172 avoid = {
55173 selection: tickLabels,
55174 side: ax.side
55175 };
55176
55177 if(tickLabels && tickLabels.node() && tickLabels.node().parentNode) {
55178 var translation = Drawing.getTranslate(tickLabels.node().parentNode);
55179 avoid.offsetLeft = translation.x;
55180 avoid.offsetTop = translation.y;
55181 }
55182
55183 if(ax.title.hasOwnProperty('standoff')) {
55184 avoid.pad = 0;
55185 }
55186 }
55187
55188 return Titles.draw(gd, axId + 'title', {
55189 propContainer: ax,
55190 propName: ax._name + '.title.text',
55191 placeholder: fullLayout._dfltTitle[axLetter],
55192 avoid: avoid,
55193 transform: transform,
55194 attributes: {x: x, y: y, 'text-anchor': 'middle'}
55195 });
55196}
55197
55198axes.shouldShowZeroLine = function(gd, ax, counterAxis) {
55199 var rng = Lib.simpleMap(ax.range, ax.r2l);
55200 return (
55201 (rng[0] * rng[1] <= 0) &&
55202 ax.zeroline &&
55203 (ax.type === 'linear' || ax.type === '-') &&
55204 !(ax.rangebreaks && ax.maskBreaks(0) === BADNUM) &&
55205 (
55206 clipEnds(ax, 0) ||
55207 !anyCounterAxLineAtZero(gd, ax, counterAxis, rng) ||
55208 hasBarsOrFill(gd, ax)
55209 )
55210 );
55211};
55212
55213axes.clipEnds = function(ax, vals) {
55214 return vals.filter(function(d) { return clipEnds(ax, d.x); });
55215};
55216
55217function clipEnds(ax, l) {
55218 var p = ax.l2p(l);
55219 return (p > 1 && p < ax._length - 1);
55220}
55221
55222function anyCounterAxLineAtZero(gd, ax, counterAxis, rng) {
55223 var mainCounterAxis = counterAxis._mainAxis;
55224 if(!mainCounterAxis) return;
55225
55226 var fullLayout = gd._fullLayout;
55227 var axLetter = ax._id.charAt(0);
55228 var counterLetter = axes.counterLetter(ax._id);
55229
55230 var zeroPosition = ax._offset + (
55231 ((Math.abs(rng[0]) < Math.abs(rng[1])) === (axLetter === 'x')) ?
55232 0 : ax._length
55233 );
55234
55235 function lineNearZero(ax2) {
55236 if(!ax2.showline || !ax2.linewidth) return false;
55237 var tolerance = Math.max((ax2.linewidth + ax.zerolinewidth) / 2, 1);
55238
55239 function closeEnough(pos2) {
55240 return typeof pos2 === 'number' && Math.abs(pos2 - zeroPosition) < tolerance;
55241 }
55242
55243 if(closeEnough(ax2._mainLinePosition) || closeEnough(ax2._mainMirrorPosition)) {
55244 return true;
55245 }
55246 var linePositions = ax2._linepositions || {};
55247 for(var k in linePositions) {
55248 if(closeEnough(linePositions[k][0]) || closeEnough(linePositions[k][1])) {
55249 return true;
55250 }
55251 }
55252 }
55253
55254 var plotinfo = fullLayout._plots[counterAxis._mainSubplot];
55255 if(!(plotinfo.mainplotinfo || plotinfo).overlays.length) {
55256 return lineNearZero(counterAxis, zeroPosition);
55257 }
55258
55259 var counterLetterAxes = axes.list(gd, counterLetter);
55260 for(var i = 0; i < counterLetterAxes.length; i++) {
55261 var counterAxis2 = counterLetterAxes[i];
55262 if(
55263 counterAxis2._mainAxis === mainCounterAxis &&
55264 lineNearZero(counterAxis2, zeroPosition)
55265 ) {
55266 return true;
55267 }
55268 }
55269}
55270
55271function hasBarsOrFill(gd, ax) {
55272 var fullData = gd._fullData;
55273 var subplot = ax._mainSubplot;
55274 var axLetter = ax._id.charAt(0);
55275
55276 for(var i = 0; i < fullData.length; i++) {
55277 var trace = fullData[i];
55278
55279 if(trace.visible === true && (trace.xaxis + trace.yaxis) === subplot) {
55280 if(
55281 Registry.traceIs(trace, 'bar-like') &&
55282 trace.orientation === {x: 'h', y: 'v'}[axLetter]
55283 ) return true;
55284
55285 if(
55286 trace.fill &&
55287 trace.fill.charAt(trace.fill.length - 1) === axLetter
55288 ) return true;
55289 }
55290 }
55291 return false;
55292}
55293
55294function selectTickLabel(gTick) {
55295 var s = d3.select(gTick);
55296 var mj = s.select('.text-math-group');
55297 return mj.empty() ? s.select('text') : mj;
55298}
55299
55300/**
55301 * Find all margin pushers for 2D axes and reserve them for later use
55302 * Both label and rangeslider automargin calculations happen later so
55303 * we need to explicitly allow their ids in order to not delete them.
55304 *
55305 * TODO: can we pull the actual automargin calls forward to avoid this hack?
55306 * We're probably also doing multiple redraws in this case, would be faster
55307 * if we can just do the whole calculation ahead of time and draw once.
55308 */
55309axes.allowAutoMargin = function(gd) {
55310 var axList = axes.list(gd, '', true);
55311 for(var i = 0; i < axList.length; i++) {
55312 var ax = axList[i];
55313 if(ax.automargin) {
55314 Plots.allowAutoMargin(gd, axAutoMarginID(ax));
55315 if(ax.mirror) {
55316 Plots.allowAutoMargin(gd, axMirrorAutoMarginID(ax));
55317 }
55318 }
55319 if(Registry.getComponentMethod('rangeslider', 'isVisible')(ax)) {
55320 Plots.allowAutoMargin(gd, rangeSliderAutoMarginID(ax));
55321 }
55322 }
55323};
55324
55325function axAutoMarginID(ax) { return ax._id + '.automargin'; }
55326function axMirrorAutoMarginID(ax) { return axAutoMarginID(ax) + '.mirror'; }
55327function rangeSliderAutoMarginID(ax) { return ax._id + '.rangeslider'; }
55328
55329// swap all the presentation attributes of the axes showing these traces
55330axes.swap = function(gd, traces) {
55331 var axGroups = makeAxisGroups(gd, traces);
55332
55333 for(var i = 0; i < axGroups.length; i++) {
55334 swapAxisGroup(gd, axGroups[i].x, axGroups[i].y);
55335 }
55336};
55337
55338function makeAxisGroups(gd, traces) {
55339 var groups = [];
55340 var i, j;
55341
55342 for(i = 0; i < traces.length; i++) {
55343 var groupsi = [];
55344 var xi = gd._fullData[traces[i]].xaxis;
55345 var yi = gd._fullData[traces[i]].yaxis;
55346 if(!xi || !yi) continue; // not a 2D cartesian trace?
55347
55348 for(j = 0; j < groups.length; j++) {
55349 if(groups[j].x.indexOf(xi) !== -1 || groups[j].y.indexOf(yi) !== -1) {
55350 groupsi.push(j);
55351 }
55352 }
55353
55354 if(!groupsi.length) {
55355 groups.push({x: [xi], y: [yi]});
55356 continue;
55357 }
55358
55359 var group0 = groups[groupsi[0]];
55360 var groupj;
55361
55362 if(groupsi.length > 1) {
55363 for(j = 1; j < groupsi.length; j++) {
55364 groupj = groups[groupsi[j]];
55365 mergeAxisGroups(group0.x, groupj.x);
55366 mergeAxisGroups(group0.y, groupj.y);
55367 }
55368 }
55369 mergeAxisGroups(group0.x, [xi]);
55370 mergeAxisGroups(group0.y, [yi]);
55371 }
55372
55373 return groups;
55374}
55375
55376function mergeAxisGroups(intoSet, fromSet) {
55377 for(var i = 0; i < fromSet.length; i++) {
55378 if(intoSet.indexOf(fromSet[i]) === -1) intoSet.push(fromSet[i]);
55379 }
55380}
55381
55382function swapAxisGroup(gd, xIds, yIds) {
55383 var xFullAxes = [];
55384 var yFullAxes = [];
55385 var layout = gd.layout;
55386 var i, j;
55387
55388 for(i = 0; i < xIds.length; i++) xFullAxes.push(axes.getFromId(gd, xIds[i]));
55389 for(i = 0; i < yIds.length; i++) yFullAxes.push(axes.getFromId(gd, yIds[i]));
55390
55391 var allAxKeys = Object.keys(axAttrs);
55392
55393 var noSwapAttrs = [
55394 'anchor', 'domain', 'overlaying', 'position', 'side', 'tickangle', 'editType'
55395 ];
55396 var numericTypes = ['linear', 'log'];
55397
55398 for(i = 0; i < allAxKeys.length; i++) {
55399 var keyi = allAxKeys[i];
55400 var xVal = xFullAxes[0][keyi];
55401 var yVal = yFullAxes[0][keyi];
55402 var allEqual = true;
55403 var coerceLinearX = false;
55404 var coerceLinearY = false;
55405 if(keyi.charAt(0) === '_' || typeof xVal === 'function' ||
55406 noSwapAttrs.indexOf(keyi) !== -1) {
55407 continue;
55408 }
55409 for(j = 1; j < xFullAxes.length && allEqual; j++) {
55410 var xVali = xFullAxes[j][keyi];
55411 if(keyi === 'type' && numericTypes.indexOf(xVal) !== -1 &&
55412 numericTypes.indexOf(xVali) !== -1 && xVal !== xVali) {
55413 // type is special - if we find a mixture of linear and log,
55414 // coerce them all to linear on flipping
55415 coerceLinearX = true;
55416 } else if(xVali !== xVal) allEqual = false;
55417 }
55418 for(j = 1; j < yFullAxes.length && allEqual; j++) {
55419 var yVali = yFullAxes[j][keyi];
55420 if(keyi === 'type' && numericTypes.indexOf(yVal) !== -1 &&
55421 numericTypes.indexOf(yVali) !== -1 && yVal !== yVali) {
55422 // type is special - if we find a mixture of linear and log,
55423 // coerce them all to linear on flipping
55424 coerceLinearY = true;
55425 } else if(yFullAxes[j][keyi] !== yVal) allEqual = false;
55426 }
55427 if(allEqual) {
55428 if(coerceLinearX) layout[xFullAxes[0]._name].type = 'linear';
55429 if(coerceLinearY) layout[yFullAxes[0]._name].type = 'linear';
55430 swapAxisAttrs(layout, keyi, xFullAxes, yFullAxes, gd._fullLayout._dfltTitle);
55431 }
55432 }
55433
55434 // now swap x&y for any annotations anchored to these x & y
55435 for(i = 0; i < gd._fullLayout.annotations.length; i++) {
55436 var ann = gd._fullLayout.annotations[i];
55437 if(xIds.indexOf(ann.xref) !== -1 &&
55438 yIds.indexOf(ann.yref) !== -1) {
55439 Lib.swapAttrs(layout.annotations[i], ['?']);
55440 }
55441 }
55442}
55443
55444function swapAxisAttrs(layout, key, xFullAxes, yFullAxes, dfltTitle) {
55445 // in case the value is the default for either axis,
55446 // look at the first axis in each list and see if
55447 // this key's value is undefined
55448 var np = Lib.nestedProperty;
55449 var xVal = np(layout[xFullAxes[0]._name], key).get();
55450 var yVal = np(layout[yFullAxes[0]._name], key).get();
55451 var i;
55452
55453 if(key === 'title') {
55454 // special handling of placeholder titles
55455 if(xVal && xVal.text === dfltTitle.x) {
55456 xVal.text = dfltTitle.y;
55457 }
55458 if(yVal && yVal.text === dfltTitle.y) {
55459 yVal.text = dfltTitle.x;
55460 }
55461 }
55462
55463 for(i = 0; i < xFullAxes.length; i++) {
55464 np(layout, xFullAxes[i]._name + '.' + key).set(yVal);
55465 }
55466 for(i = 0; i < yFullAxes.length; i++) {
55467 np(layout, yFullAxes[i]._name + '.' + key).set(xVal);
55468 }
55469}
55470
55471function isAngular(ax) {
55472 return ax._id === 'angularaxis';
55473}
55474
55475function moveOutsideBreak(v, ax) {
55476 var len = ax._rangebreaks.length;
55477 for(var k = 0; k < len; k++) {
55478 var brk = ax._rangebreaks[k];
55479 if(v >= brk.min && v < brk.max) {
55480 return brk.max;
55481 }
55482 }
55483 return v;
55484}
55485
55486},{"../../components/color":52,"../../components/drawing":74,"../../components/titles":147,"../../constants/alignment":154,"../../constants/numerical":158,"../../lib":178,"../../lib/svg_text_utils":199,"../../plots/plots":256,"../../registry":269,"./autorange":221,"./axis_autotype":223,"./axis_ids":225,"./clean_ticks":227,"./layout_attributes":236,"./set_convert":242,"d3":16,"fast-isnumeric":18}],223:[function(_dereq_,module,exports){
55487/**
55488* Copyright 2012-2020, Plotly, Inc.
55489* All rights reserved.
55490*
55491* This source code is licensed under the MIT license found in the
55492* LICENSE file in the root directory of this source tree.
55493*/
55494
55495
55496'use strict';
55497
55498var isNumeric = _dereq_('fast-isnumeric');
55499
55500var Lib = _dereq_('../../lib');
55501var BADNUM = _dereq_('../../constants/numerical').BADNUM;
55502
55503module.exports = function autoType(array, calendar, opts) {
55504 opts = opts || {};
55505
55506 if(!opts.noMultiCategory && multiCategory(array)) return 'multicategory';
55507 if(moreDates(array, calendar)) return 'date';
55508 if(category(array)) return 'category';
55509 if(linearOK(array)) return 'linear';
55510 else return '-';
55511};
55512
55513// is there at least one number in array? If not, we should leave
55514// ax.type empty so it can be autoset later
55515function linearOK(array) {
55516 if(!array) return false;
55517
55518 for(var i = 0; i < array.length; i++) {
55519 if(isNumeric(array[i])) return true;
55520 }
55521
55522 return false;
55523}
55524
55525// does the array a have mostly dates rather than numbers?
55526// note: some values can be neither (such as blanks, text)
55527// 2- or 4-digit integers can be both, so require twice as many
55528// dates as non-dates, to exclude cases with mostly 2 & 4 digit
55529// numbers and a few dates
55530// as with categories, consider DISTINCT values only.
55531function moreDates(a, calendar) {
55532 // test at most 1000 points, evenly spaced
55533 var inc = Math.max(1, (a.length - 1) / 1000);
55534 var dcnt = 0;
55535 var ncnt = 0;
55536 var seen = {};
55537
55538 for(var i = 0; i < a.length; i += inc) {
55539 var ai = a[Math.round(i)];
55540 var stri = String(ai);
55541 if(seen[stri]) continue;
55542 seen[stri] = 1;
55543
55544 if(Lib.isDateTime(ai, calendar)) dcnt += 1;
55545 if(isNumeric(ai)) ncnt += 1;
55546 }
55547
55548 return (dcnt > ncnt * 2);
55549}
55550
55551// are the (x,y)-values in gd.data mostly text?
55552// require twice as many DISTINCT categories as distinct numbers
55553function category(a) {
55554 // test at most 1000 points
55555 var inc = Math.max(1, (a.length - 1) / 1000);
55556 var curvenums = 0;
55557 var curvecats = 0;
55558 var seen = {};
55559
55560 for(var i = 0; i < a.length; i += inc) {
55561 var ai = a[Math.round(i)];
55562 var stri = String(ai);
55563 if(seen[stri]) continue;
55564 seen[stri] = 1;
55565
55566 if(typeof ai === 'boolean') curvecats++;
55567 else if(Lib.cleanNumber(ai) !== BADNUM) curvenums++;
55568 else if(typeof ai === 'string') curvecats++;
55569 }
55570
55571 return curvecats > curvenums * 2;
55572}
55573
55574// very-loose requirements for multicategory,
55575// trace modules that should never auto-type to multicategory
55576// should be declared with 'noMultiCategory'
55577function multiCategory(a) {
55578 return Lib.isArrayOrTypedArray(a[0]) && Lib.isArrayOrTypedArray(a[1]);
55579}
55580
55581},{"../../constants/numerical":158,"../../lib":178,"fast-isnumeric":18}],224:[function(_dereq_,module,exports){
55582/**
55583* Copyright 2012-2020, Plotly, Inc.
55584* All rights reserved.
55585*
55586* This source code is licensed under the MIT license found in the
55587* LICENSE file in the root directory of this source tree.
55588*/
55589
55590'use strict';
55591
55592var isNumeric = _dereq_('fast-isnumeric');
55593
55594var Registry = _dereq_('../../registry');
55595var Lib = _dereq_('../../lib');
55596
55597var handleArrayContainerDefaults = _dereq_('../array_container_defaults');
55598
55599var layoutAttributes = _dereq_('./layout_attributes');
55600var handleTickValueDefaults = _dereq_('./tick_value_defaults');
55601var handleTickMarkDefaults = _dereq_('./tick_mark_defaults');
55602var handleTickLabelDefaults = _dereq_('./tick_label_defaults');
55603var handleCategoryOrderDefaults = _dereq_('./category_order_defaults');
55604var handleLineGridDefaults = _dereq_('./line_grid_defaults');
55605var setConvert = _dereq_('./set_convert');
55606
55607var DAY_OF_WEEK = _dereq_('./constants').WEEKDAY_PATTERN;
55608var HOUR = _dereq_('./constants').HOUR_PATTERN;
55609
55610/**
55611 * options: object containing:
55612 *
55613 * letter: 'x' or 'y'
55614 * title: name of the axis (ie 'Colorbar') to go in default title
55615 * font: the default font to inherit
55616 * outerTicks: boolean, should ticks default to outside?
55617 * showGrid: boolean, should gridlines be shown by default?
55618 * noHover: boolean, this axis doesn't support hover effects?
55619 * noTickson: boolean, this axis doesn't support 'tickson'
55620 * data: the plot data, used to manage categories
55621 * bgColor: the plot background color, to calculate default gridline colors
55622 * calendar:
55623 * splomStash:
55624 * visibleDflt: boolean
55625 * reverseDflt: boolean
55626 * automargin: boolean
55627 */
55628module.exports = function handleAxisDefaults(containerIn, containerOut, coerce, options, layoutOut) {
55629 var letter = options.letter;
55630 var font = options.font || {};
55631 var splomStash = options.splomStash || {};
55632
55633 var visible = coerce('visible', !options.visibleDflt);
55634
55635 var axTemplate = containerOut._template || {};
55636 var axType = containerOut.type || axTemplate.type || '-';
55637
55638 if(axType === 'date') {
55639 var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleDefaults');
55640 handleCalendarDefaults(containerIn, containerOut, 'calendar', options.calendar);
55641 }
55642
55643 setConvert(containerOut, layoutOut);
55644
55645 var autorangeDflt = !containerOut.isValidRange(containerIn.range);
55646 if(autorangeDflt && options.reverseDflt) autorangeDflt = 'reversed';
55647 var autoRange = coerce('autorange', autorangeDflt);
55648 if(autoRange && (axType === 'linear' || axType === '-')) coerce('rangemode');
55649
55650 coerce('range');
55651 containerOut.cleanRange();
55652
55653 handleCategoryOrderDefaults(containerIn, containerOut, coerce, options);
55654
55655 if(axType !== 'category' && !options.noHover) coerce('hoverformat');
55656
55657 var dfltColor = coerce('color');
55658 // if axis.color was provided, use it for fonts too; otherwise,
55659 // inherit from global font color in case that was provided.
55660 // Compare to dflt rather than to containerIn, so we can provide color via
55661 // template too.
55662 var dfltFontColor = (dfltColor !== layoutAttributes.color.dflt) ? dfltColor : font.color;
55663 // try to get default title from splom trace, fallback to graph-wide value
55664 var dfltTitle = splomStash.label || layoutOut._dfltTitle[letter];
55665
55666 handleTickLabelDefaults(containerIn, containerOut, coerce, axType, options, {pass: 1});
55667 if(!visible) return containerOut;
55668
55669 coerce('title.text', dfltTitle);
55670 Lib.coerceFont(coerce, 'title.font', {
55671 family: font.family,
55672 size: Math.round(font.size * 1.2),
55673 color: dfltFontColor
55674 });
55675
55676 handleTickValueDefaults(containerIn, containerOut, coerce, axType);
55677 handleTickLabelDefaults(containerIn, containerOut, coerce, axType, options, {pass: 2});
55678 handleTickMarkDefaults(containerIn, containerOut, coerce, options);
55679 handleLineGridDefaults(containerIn, containerOut, coerce, {
55680 dfltColor: dfltColor,
55681 bgColor: options.bgColor,
55682 showGrid: options.showGrid,
55683 attributes: layoutAttributes
55684 });
55685
55686 if(containerOut.showline || containerOut.ticks) coerce('mirror');
55687
55688 if(options.automargin) coerce('automargin');
55689
55690 var isMultiCategory = axType === 'multicategory';
55691
55692 if(!options.noTickson &&
55693 (axType === 'category' || isMultiCategory) &&
55694 (containerOut.ticks || containerOut.showgrid)
55695 ) {
55696 var ticksonDflt;
55697 if(isMultiCategory) ticksonDflt = 'boundaries';
55698 coerce('tickson', ticksonDflt);
55699 }
55700
55701 if(isMultiCategory) {
55702 var showDividers = coerce('showdividers');
55703 if(showDividers) {
55704 coerce('dividercolor');
55705 coerce('dividerwidth');
55706 }
55707 }
55708
55709 if(axType === 'date') {
55710 handleArrayContainerDefaults(containerIn, containerOut, {
55711 name: 'rangebreaks',
55712 inclusionAttr: 'enabled',
55713 handleItemDefaults: rangebreaksDefaults
55714 });
55715
55716 if(!containerOut.rangebreaks.length) {
55717 delete containerOut.rangebreaks;
55718 } else {
55719 for(var k = 0; k < containerOut.rangebreaks.length; k++) {
55720 if(containerOut.rangebreaks[k].pattern === DAY_OF_WEEK) {
55721 containerOut._hasDayOfWeekBreaks = true;
55722 break;
55723 }
55724 }
55725
55726 setConvert(containerOut, layoutOut);
55727
55728 if(layoutOut._has('scattergl') || layoutOut._has('splom')) {
55729 for(var i = 0; i < options.data.length; i++) {
55730 var trace = options.data[i];
55731 if(trace.type === 'scattergl' || trace.type === 'splom') {
55732 trace.visible = false;
55733 Lib.warn(trace.type +
55734 ' traces do not work on axes with rangebreaks.' +
55735 ' Setting trace ' + trace.index + ' to `visible: false`.');
55736 }
55737 }
55738 }
55739 }
55740 }
55741
55742 return containerOut;
55743};
55744
55745function rangebreaksDefaults(itemIn, itemOut, containerOut) {
55746 function coerce(attr, dflt) {
55747 return Lib.coerce(itemIn, itemOut, layoutAttributes.rangebreaks, attr, dflt);
55748 }
55749
55750 var enabled = coerce('enabled');
55751
55752 if(enabled) {
55753 var bnds = coerce('bounds');
55754 if(bnds && bnds.length >= 2) {
55755 var dfltPattern = '';
55756 var i, q;
55757 if(bnds.length === 2) {
55758 for(i = 0; i < 2; i++) {
55759 q = indexOfDay(bnds[i]);
55760 if(q) {
55761 dfltPattern = DAY_OF_WEEK;
55762 break;
55763 }
55764 }
55765 }
55766 var pattern = coerce('pattern', dfltPattern);
55767 if(pattern === DAY_OF_WEEK) {
55768 for(i = 0; i < 2; i++) {
55769 q = indexOfDay(bnds[i]);
55770 if(q) {
55771 // convert to integers i.e 'Sunday' --> 0
55772 itemOut.bounds[i] = bnds[i] = q - 1;
55773 }
55774 }
55775 }
55776 if(pattern) {
55777 // ensure types and ranges
55778 for(i = 0; i < 2; i++) {
55779 q = bnds[i];
55780 switch(pattern) {
55781 case DAY_OF_WEEK :
55782 if(!isNumeric(q)) {
55783 itemOut.enabled = false;
55784 return;
55785 }
55786 q = +q;
55787
55788 if(
55789 q !== Math.floor(q) || // don't accept fractional days for mow
55790 q < 0 || q >= 7
55791 ) {
55792 itemOut.enabled = false;
55793 return;
55794 }
55795 // use number
55796 itemOut.bounds[i] = bnds[i] = q;
55797 break;
55798
55799 case HOUR :
55800 if(!isNumeric(q)) {
55801 itemOut.enabled = false;
55802 return;
55803 }
55804 q = +q;
55805
55806 if(q < 0 || q > 24) { // accept 24
55807 itemOut.enabled = false;
55808 return;
55809 }
55810 // use number
55811 itemOut.bounds[i] = bnds[i] = q;
55812 break;
55813 }
55814 }
55815 }
55816
55817 if(containerOut.autorange === false) {
55818 var rng = containerOut.range;
55819
55820 // if bounds are bigger than the (set) range, disable break
55821 if(rng[0] < rng[1]) {
55822 if(bnds[0] < rng[0] && bnds[1] > rng[1]) {
55823 itemOut.enabled = false;
55824 return;
55825 }
55826 } else if(bnds[0] > rng[0] && bnds[1] < rng[1]) {
55827 itemOut.enabled = false;
55828 return;
55829 }
55830 }
55831 } else {
55832 var values = coerce('values');
55833
55834 if(values && values.length) {
55835 coerce('dvalue');
55836 } else {
55837 itemOut.enabled = false;
55838 return;
55839 }
55840 }
55841 }
55842}
55843
55844// these numbers are one more than what bounds would be mapped to
55845var dayStrToNum = {
55846 sun: 1,
55847 mon: 2,
55848 tue: 3,
55849 wed: 4,
55850 thu: 5,
55851 fri: 6,
55852 sat: 7
55853};
55854
55855function indexOfDay(v) {
55856 if(typeof v !== 'string') return;
55857 return dayStrToNum[
55858 v.substr(0, 3).toLowerCase()
55859 ];
55860}
55861
55862},{"../../lib":178,"../../registry":269,"../array_container_defaults":218,"./category_order_defaults":226,"./constants":228,"./layout_attributes":236,"./line_grid_defaults":238,"./set_convert":242,"./tick_label_defaults":243,"./tick_mark_defaults":244,"./tick_value_defaults":245,"fast-isnumeric":18}],225:[function(_dereq_,module,exports){
55863/**
55864* Copyright 2012-2020, Plotly, Inc.
55865* All rights reserved.
55866*
55867* This source code is licensed under the MIT license found in the
55868* LICENSE file in the root directory of this source tree.
55869*/
55870
55871'use strict';
55872
55873var Registry = _dereq_('../../registry');
55874
55875var constants = _dereq_('./constants');
55876
55877
55878// convert between axis names (xaxis, xaxis2, etc, elements of gd.layout)
55879// and axis id's (x, x2, etc). Would probably have ditched 'xaxis'
55880// completely in favor of just 'x' if it weren't ingrained in the API etc.
55881exports.id2name = function id2name(id) {
55882 if(typeof id !== 'string' || !id.match(constants.AX_ID_PATTERN)) return;
55883 var axNum = id.substr(1);
55884 if(axNum === '1') axNum = '';
55885 return id.charAt(0) + 'axis' + axNum;
55886};
55887
55888exports.name2id = function name2id(name) {
55889 if(!name.match(constants.AX_NAME_PATTERN)) return;
55890 var axNum = name.substr(5);
55891 if(axNum === '1') axNum = '';
55892 return name.charAt(0) + axNum;
55893};
55894
55895exports.cleanId = function cleanId(id, axLetter) {
55896 if(typeof id !== 'string' || !id.match(constants.AX_ID_PATTERN)) return;
55897 if(axLetter && id.charAt(0) !== axLetter) return;
55898
55899 var axNum = id.substr(1).replace(/^0+/, '');
55900 if(axNum === '1') axNum = '';
55901 return id.charAt(0) + axNum;
55902};
55903
55904// get all axis objects, as restricted in listNames
55905exports.list = function(gd, axLetter, only2d) {
55906 var fullLayout = gd._fullLayout;
55907 if(!fullLayout) return [];
55908
55909 var idList = exports.listIds(gd, axLetter);
55910 var out = new Array(idList.length);
55911 var i;
55912
55913 for(i = 0; i < idList.length; i++) {
55914 var idi = idList[i];
55915 out[i] = fullLayout[idi.charAt(0) + 'axis' + idi.substr(1)];
55916 }
55917
55918 if(!only2d) {
55919 var sceneIds3D = fullLayout._subplots.gl3d || [];
55920
55921 for(i = 0; i < sceneIds3D.length; i++) {
55922 var scene = fullLayout[sceneIds3D[i]];
55923
55924 if(axLetter) out.push(scene[axLetter + 'axis']);
55925 else out.push(scene.xaxis, scene.yaxis, scene.zaxis);
55926 }
55927 }
55928
55929 return out;
55930};
55931
55932// get all axis ids, optionally restricted by letter
55933// this only makes sense for 2d axes
55934exports.listIds = function(gd, axLetter) {
55935 var fullLayout = gd._fullLayout;
55936 if(!fullLayout) return [];
55937
55938 var subplotLists = fullLayout._subplots;
55939 if(axLetter) return subplotLists[axLetter + 'axis'];
55940 return subplotLists.xaxis.concat(subplotLists.yaxis);
55941};
55942
55943// get an axis object from its id 'x','x2' etc
55944// optionally, id can be a subplot (ie 'x2y3') and type gets x or y from it
55945exports.getFromId = function(gd, id, type) {
55946 var fullLayout = gd._fullLayout;
55947
55948 if(type === 'x') id = id.replace(/y[0-9]*/, '');
55949 else if(type === 'y') id = id.replace(/x[0-9]*/, '');
55950
55951 return fullLayout[exports.id2name(id)];
55952};
55953
55954// get an axis object of specified type from the containing trace
55955exports.getFromTrace = function(gd, fullTrace, type) {
55956 var fullLayout = gd._fullLayout;
55957 var ax = null;
55958
55959 if(Registry.traceIs(fullTrace, 'gl3d')) {
55960 var scene = fullTrace.scene;
55961 if(scene.substr(0, 5) === 'scene') {
55962 ax = fullLayout[scene][type + 'axis'];
55963 }
55964 } else {
55965 ax = exports.getFromId(gd, fullTrace[type + 'axis'] || type);
55966 }
55967
55968 return ax;
55969};
55970
55971// sort x, x2, x10, y, y2, y10...
55972exports.idSort = function(id1, id2) {
55973 var letter1 = id1.charAt(0);
55974 var letter2 = id2.charAt(0);
55975 if(letter1 !== letter2) return letter1 > letter2 ? 1 : -1;
55976 return +(id1.substr(1) || 1) - +(id2.substr(1) || 1);
55977};
55978
55979exports.getAxisGroup = function getAxisGroup(fullLayout, axId) {
55980 var matchGroups = fullLayout._axisMatchGroups;
55981
55982 for(var i = 0; i < matchGroups.length; i++) {
55983 var group = matchGroups[i];
55984 if(group[axId]) return 'g' + i;
55985 }
55986 return axId;
55987};
55988
55989},{"../../registry":269,"./constants":228}],226:[function(_dereq_,module,exports){
55990/**
55991* Copyright 2012-2020, Plotly, Inc.
55992* All rights reserved.
55993*
55994* This source code is licensed under the MIT license found in the
55995* LICENSE file in the root directory of this source tree.
55996*/
55997
55998'use strict';
55999
56000function findCategories(ax, opts) {
56001 var dataAttr = opts.dataAttr || ax._id.charAt(0);
56002 var lookup = {};
56003 var axData;
56004 var i, j;
56005
56006 if(opts.axData) {
56007 // non-x/y case
56008 axData = opts.axData;
56009 } else {
56010 // x/y case
56011 axData = [];
56012 for(i = 0; i < opts.data.length; i++) {
56013 var trace = opts.data[i];
56014 if(trace[dataAttr + 'axis'] === ax._id) {
56015 axData.push(trace);
56016 }
56017 }
56018 }
56019
56020 for(i = 0; i < axData.length; i++) {
56021 var vals = axData[i][dataAttr];
56022 for(j = 0; j < vals.length; j++) {
56023 var v = vals[j];
56024 if(v !== null && v !== undefined) {
56025 lookup[v] = 1;
56026 }
56027 }
56028 }
56029
56030 return Object.keys(lookup);
56031}
56032
56033/**
56034 * Fills in category* default and initial categories.
56035 *
56036 * @param {object} containerIn : input axis object
56037 * @param {object} containerOut : full axis object
56038 * @param {function} coerce : Lib.coerce fn wrapper
56039 * @param {object} opts :
56040 * - data {array} : (full) data trace
56041 * OR
56042 * - axData {array} : (full) data associated with axis being coerced here
56043 * - dataAttr {string} : attribute name corresponding to coordinate array
56044 */
56045module.exports = function handleCategoryOrderDefaults(containerIn, containerOut, coerce, opts) {
56046 if(containerOut.type !== 'category') return;
56047
56048 var arrayIn = containerIn.categoryarray;
56049 var isValidArray = (Array.isArray(arrayIn) && arrayIn.length > 0);
56050
56051 // override default 'categoryorder' value when non-empty array is supplied
56052 var orderDefault;
56053 if(isValidArray) orderDefault = 'array';
56054
56055 var order = coerce('categoryorder', orderDefault);
56056 var array;
56057
56058 // coerce 'categoryarray' only in array order case
56059 if(order === 'array') {
56060 array = coerce('categoryarray');
56061 }
56062
56063 // cannot set 'categoryorder' to 'array' with an invalid 'categoryarray'
56064 if(!isValidArray && order === 'array') {
56065 order = containerOut.categoryorder = 'trace';
56066 }
56067
56068 // set up things for makeCalcdata
56069 if(order === 'trace') {
56070 containerOut._initialCategories = [];
56071 } else if(order === 'array') {
56072 containerOut._initialCategories = array.slice();
56073 } else {
56074 array = findCategories(containerOut, opts).sort();
56075 if(order === 'category ascending') {
56076 containerOut._initialCategories = array;
56077 } else if(order === 'category descending') {
56078 containerOut._initialCategories = array.reverse();
56079 }
56080 }
56081};
56082
56083},{}],227:[function(_dereq_,module,exports){
56084/**
56085* Copyright 2012-2020, Plotly, Inc.
56086* All rights reserved.
56087*
56088* This source code is licensed under the MIT license found in the
56089* LICENSE file in the root directory of this source tree.
56090*/
56091
56092'use strict';
56093
56094var isNumeric = _dereq_('fast-isnumeric');
56095var Lib = _dereq_('../../lib');
56096var ONEDAY = _dereq_('../../constants/numerical').ONEDAY;
56097
56098/**
56099 * Return a validated dtick value for this axis
56100 *
56101 * @param {any} dtick: the candidate dtick. valid values are numbers and strings,
56102 * and further constrained depending on the axis type.
56103 * @param {string} axType: the axis type
56104 */
56105exports.dtick = function(dtick, axType) {
56106 var isLog = axType === 'log';
56107 var isDate = axType === 'date';
56108 var isCat = axType === 'category';
56109 var dtickDflt = isDate ? ONEDAY : 1;
56110
56111 if(!dtick) return dtickDflt;
56112
56113 if(isNumeric(dtick)) {
56114 dtick = Number(dtick);
56115 if(dtick <= 0) return dtickDflt;
56116 if(isCat) {
56117 // category dtick must be positive integers
56118 return Math.max(1, Math.round(dtick));
56119 }
56120 if(isDate) {
56121 // date dtick must be at least 0.1ms (our current precision)
56122 return Math.max(0.1, dtick);
56123 }
56124 return dtick;
56125 }
56126
56127 if(typeof dtick !== 'string' || !(isDate || isLog)) {
56128 return dtickDflt;
56129 }
56130
56131 var prefix = dtick.charAt(0);
56132 var dtickNum = dtick.substr(1);
56133 dtickNum = isNumeric(dtickNum) ? Number(dtickNum) : 0;
56134
56135 if((dtickNum <= 0) || !(
56136 // "M<n>" gives ticks every (integer) n months
56137 (isDate && prefix === 'M' && dtickNum === Math.round(dtickNum)) ||
56138 // "L<f>" gives ticks linearly spaced in data (not in position) every (float) f
56139 (isLog && prefix === 'L') ||
56140 // "D1" gives powers of 10 with all small digits between, "D2" gives only 2 and 5
56141 (isLog && prefix === 'D' && (dtickNum === 1 || dtickNum === 2))
56142 )) {
56143 return dtickDflt;
56144 }
56145
56146 return dtick;
56147};
56148
56149/**
56150 * Return a validated tick0 for this axis
56151 *
56152 * @param {any} tick0: the candidate tick0. Valid values are numbers and strings,
56153 * further constrained depending on the axis type
56154 * @param {string} axType: the axis type
56155 * @param {string} calendar: for date axes, the calendar to validate/convert with
56156 * @param {any} dtick: an already valid dtick. Only used for D1 and D2 log dticks,
56157 * which do not support tick0 at all.
56158 */
56159exports.tick0 = function(tick0, axType, calendar, dtick) {
56160 if(axType === 'date') {
56161 return Lib.cleanDate(tick0, Lib.dateTick0(calendar));
56162 }
56163 if(dtick === 'D1' || dtick === 'D2') {
56164 // D1 and D2 modes ignore tick0 entirely
56165 return undefined;
56166 }
56167 // Aside from date axes, tick0 must be numeric
56168 return isNumeric(tick0) ? Number(tick0) : 0;
56169};
56170
56171},{"../../constants/numerical":158,"../../lib":178,"fast-isnumeric":18}],228:[function(_dereq_,module,exports){
56172/**
56173* Copyright 2012-2020, Plotly, Inc.
56174* All rights reserved.
56175*
56176* This source code is licensed under the MIT license found in the
56177* LICENSE file in the root directory of this source tree.
56178*/
56179
56180'use strict';
56181
56182var counterRegex = _dereq_('../../lib/regex').counter;
56183
56184module.exports = {
56185 idRegex: {
56186 x: counterRegex('x'),
56187 y: counterRegex('y')
56188 },
56189
56190 attrRegex: counterRegex('[xy]axis'),
56191
56192 // axis match regular expression
56193 xAxisMatch: counterRegex('xaxis'),
56194 yAxisMatch: counterRegex('yaxis'),
56195
56196 // pattern matching axis ids and names
56197 // note that this is more permissive than counterRegex, as
56198 // id2name, name2id, and cleanId accept "x1" etc
56199 AX_ID_PATTERN: /^[xyz][0-9]*$/,
56200 AX_NAME_PATTERN: /^[xyz]axis[0-9]*$/,
56201
56202 // and for 2D subplots
56203 SUBPLOT_PATTERN: /^x([0-9]*)y([0-9]*)$/,
56204
56205 HOUR_PATTERN: 'hour',
56206 WEEKDAY_PATTERN: 'day of week',
56207
56208 // pixels to move mouse before you stop clamping to starting point
56209 MINDRAG: 8,
56210
56211 // smallest dimension allowed for a select box
56212 MINSELECT: 12,
56213
56214 // smallest dimension allowed for a zoombox
56215 MINZOOM: 20,
56216
56217 // width of axis drag regions
56218 DRAGGERSIZE: 20,
56219
56220 // max pixels off straight before a lasso select line counts as bent
56221 BENDPX: 1.5,
56222
56223 // delay before a redraw (relayout) after smooth panning and zooming
56224 REDRAWDELAY: 50,
56225
56226 // throttling limit (ms) for selectPoints calls
56227 SELECTDELAY: 100,
56228
56229 // cache ID suffix for throttle
56230 SELECTID: '-select',
56231
56232 // last resort axis ranges for x and y axes if we have no data
56233 DFLTRANGEX: [-1, 6],
56234 DFLTRANGEY: [-1, 4],
56235
56236 // Layers to keep trace types in the right order
56237 // N.B. each 'unique' plot method must have its own layer
56238 traceLayerClasses: [
56239 'imagelayer',
56240 'heatmaplayer',
56241 'contourcarpetlayer', 'contourlayer',
56242 'funnellayer', 'waterfalllayer', 'barlayer',
56243 'carpetlayer',
56244 'violinlayer',
56245 'boxlayer',
56246 'ohlclayer',
56247 'scattercarpetlayer', 'scatterlayer'
56248 ],
56249
56250 clipOnAxisFalseQuery: [
56251 '.scatterlayer',
56252 '.barlayer',
56253 '.funnellayer',
56254 '.waterfalllayer'
56255 ],
56256
56257 layerValue2layerClass: {
56258 'above traces': 'above',
56259 'below traces': 'below'
56260 }
56261};
56262
56263},{"../../lib/regex":193}],229:[function(_dereq_,module,exports){
56264/**
56265* Copyright 2012-2020, Plotly, Inc.
56266* All rights reserved.
56267*
56268* This source code is licensed under the MIT license found in the
56269* LICENSE file in the root directory of this source tree.
56270*/
56271
56272'use strict';
56273
56274var Lib = _dereq_('../../lib');
56275var id2name = _dereq_('./axis_ids').id2name;
56276var scaleZoom = _dereq_('./scale_zoom');
56277var makePadFn = _dereq_('./autorange').makePadFn;
56278var concatExtremes = _dereq_('./autorange').concatExtremes;
56279
56280var ALMOST_EQUAL = _dereq_('../../constants/numerical').ALMOST_EQUAL;
56281var FROM_BL = _dereq_('../../constants/alignment').FROM_BL;
56282
56283exports.handleConstraintDefaults = function(containerIn, containerOut, coerce, opts) {
56284 var allAxisIds = opts.allAxisIds;
56285 var layoutOut = opts.layoutOut;
56286 var scaleanchorDflt = opts.scaleanchorDflt;
56287 var constrainDflt = opts.constrainDflt;
56288 var constraintGroups = layoutOut._axisConstraintGroups;
56289 var matchGroups = layoutOut._axisMatchGroups;
56290 var axId = containerOut._id;
56291 var axLetter = axId.charAt(0);
56292 var splomStash = ((layoutOut._splomAxes || {})[axLetter] || {})[axId] || {};
56293 var thisID = containerOut._id;
56294 var letter = thisID.charAt(0);
56295
56296 // coerce the constraint mechanics even if this axis has no scaleanchor
56297 // because it may be the anchor of another axis.
56298 var constrain = coerce('constrain', constrainDflt);
56299 Lib.coerce(containerIn, containerOut, {
56300 constraintoward: {
56301 valType: 'enumerated',
56302 values: letter === 'x' ? ['left', 'center', 'right'] : ['bottom', 'middle', 'top'],
56303 dflt: letter === 'x' ? 'center' : 'middle'
56304 }
56305 }, 'constraintoward');
56306
56307 var matches, matchOpts;
56308
56309 if((containerIn.matches || splomStash.matches) && !containerOut.fixedrange) {
56310 matchOpts = getConstraintOpts(matchGroups, thisID, allAxisIds, layoutOut);
56311 matches = Lib.coerce(containerIn, containerOut, {
56312 matches: {
56313 valType: 'enumerated',
56314 values: matchOpts.linkableAxes || [],
56315 dflt: splomStash.matches
56316 }
56317 }, 'matches');
56318 }
56319
56320 // 'matches' wins over 'scaleanchor' (for now)
56321 var scaleanchor, scaleOpts;
56322
56323 if(!matches &&
56324 !(containerOut.fixedrange && constrain !== 'domain') &&
56325 (containerIn.scaleanchor || scaleanchorDflt)
56326 ) {
56327 scaleOpts = getConstraintOpts(constraintGroups, thisID, allAxisIds, layoutOut, constrain);
56328 scaleanchor = Lib.coerce(containerIn, containerOut, {
56329 scaleanchor: {
56330 valType: 'enumerated',
56331 values: scaleOpts.linkableAxes || []
56332 }
56333 }, 'scaleanchor', scaleanchorDflt);
56334 }
56335
56336 if(matches) {
56337 delete containerOut.constrain;
56338 updateConstraintGroups(matchGroups, matchOpts.thisGroup, thisID, matches, 1);
56339 } else if(allAxisIds.indexOf(containerIn.matches) !== -1) {
56340 Lib.warn('ignored ' + containerOut._name + '.matches: "' +
56341 containerIn.matches + '" to avoid either an infinite loop ' +
56342 'or because the target axis has fixed range.');
56343 }
56344
56345 if(scaleanchor) {
56346 var scaleratio = coerce('scaleratio');
56347
56348 // TODO: I suppose I could do attribute.min: Number.MIN_VALUE to avoid zero,
56349 // but that seems hacky. Better way to say "must be a positive number"?
56350 // Of course if you use several super-tiny values you could eventually
56351 // force a product of these to zero and all hell would break loose...
56352 // Likewise with super-huge values.
56353 if(!scaleratio) scaleratio = containerOut.scaleratio = 1;
56354
56355 updateConstraintGroups(constraintGroups, scaleOpts.thisGroup, thisID, scaleanchor, scaleratio);
56356 } else if(allAxisIds.indexOf(containerIn.scaleanchor) !== -1) {
56357 Lib.warn('ignored ' + containerOut._name + '.scaleanchor: "' +
56358 containerIn.scaleanchor + '" to avoid either an infinite loop ' +
56359 'and possibly inconsistent scaleratios, or because the target ' +
56360 'axis has fixed range or this axis declares a *matches* constraint.');
56361 }
56362};
56363
56364// If this axis is already part of a constraint group, we can't
56365// scaleanchor any other axis in that group, or we'd make a loop.
56366// Filter allAxisIds to enforce this, also matching axis types.
56367function getConstraintOpts(groups, thisID, allAxisIds, layoutOut, constrain) {
56368 var doesNotConstrainRange = constrain !== 'range';
56369 var thisType = layoutOut[id2name(thisID)].type;
56370 var i, j, idj, axj;
56371
56372 var linkableAxes = [];
56373 for(j = 0; j < allAxisIds.length; j++) {
56374 idj = allAxisIds[j];
56375 if(idj === thisID) continue;
56376
56377 axj = layoutOut[id2name(idj)];
56378 if(axj.type === thisType) {
56379 if(!axj.fixedrange) {
56380 linkableAxes.push(idj);
56381 } else if(doesNotConstrainRange && axj.anchor) {
56382 // allow domain constraints on subplots where
56383 // BOTH axes have fixedrange:true and constrain:domain
56384 var counterAxj = layoutOut[id2name(axj.anchor)];
56385 if(counterAxj.fixedrange) {
56386 linkableAxes.push(idj);
56387 }
56388 }
56389 }
56390 }
56391
56392 for(i = 0; i < groups.length; i++) {
56393 if(groups[i][thisID]) {
56394 var thisGroup = groups[i];
56395
56396 var linkableAxesNoLoops = [];
56397 for(j = 0; j < linkableAxes.length; j++) {
56398 idj = linkableAxes[j];
56399 if(!thisGroup[idj]) linkableAxesNoLoops.push(idj);
56400 }
56401 return {linkableAxes: linkableAxesNoLoops, thisGroup: thisGroup};
56402 }
56403 }
56404
56405 return {linkableAxes: linkableAxes, thisGroup: null};
56406}
56407
56408/*
56409 * Add this axis to the axis constraint groups, which is the collection
56410 * of axes that are all constrained together on scale.
56411 *
56412 * constraintGroups: a list of objects. each object is
56413 * {axis_id: scale_within_group}, where scale_within_group is
56414 * only important relative to the rest of the group, and defines
56415 * the relative scales between all axes in the group
56416 *
56417 * thisGroup: the group the current axis is already in
56418 * thisID: the id if the current axis
56419 * scaleanchor: the id of the axis to scale it with
56420 * scaleratio: the ratio of this axis to the scaleanchor axis
56421 */
56422function updateConstraintGroups(constraintGroups, thisGroup, thisID, scaleanchor, scaleratio) {
56423 var i, j, groupi, keyj, thisGroupIndex;
56424
56425 if(thisGroup === null) {
56426 thisGroup = {};
56427 thisGroup[thisID] = 1;
56428 thisGroupIndex = constraintGroups.length;
56429 constraintGroups.push(thisGroup);
56430 } else {
56431 thisGroupIndex = constraintGroups.indexOf(thisGroup);
56432 }
56433
56434 var thisGroupKeys = Object.keys(thisGroup);
56435
56436 // we know that this axis isn't in any other groups, but we don't know
56437 // about the scaleanchor axis. If it is, we need to merge the groups.
56438 for(i = 0; i < constraintGroups.length; i++) {
56439 groupi = constraintGroups[i];
56440 if(i !== thisGroupIndex && groupi[scaleanchor]) {
56441 var baseScale = groupi[scaleanchor];
56442 for(j = 0; j < thisGroupKeys.length; j++) {
56443 keyj = thisGroupKeys[j];
56444 groupi[keyj] = baseScale * scaleratio * thisGroup[keyj];
56445 }
56446 constraintGroups.splice(thisGroupIndex, 1);
56447 return;
56448 }
56449 }
56450
56451 // otherwise, we insert the new scaleanchor axis as the base scale (1)
56452 // in its group, and scale the rest of the group to it
56453 if(scaleratio !== 1) {
56454 for(j = 0; j < thisGroupKeys.length; j++) {
56455 thisGroup[thisGroupKeys[j]] *= scaleratio;
56456 }
56457 }
56458 thisGroup[scaleanchor] = 1;
56459}
56460
56461exports.enforce = function enforce(gd) {
56462 var fullLayout = gd._fullLayout;
56463 var constraintGroups = fullLayout._axisConstraintGroups || [];
56464
56465 var i, j, axisID, ax, normScale, mode, factor;
56466
56467 for(i = 0; i < constraintGroups.length; i++) {
56468 var group = constraintGroups[i];
56469 var axisIDs = Object.keys(group);
56470
56471 var minScale = Infinity;
56472 var maxScale = 0;
56473 // mostly matchScale will be the same as minScale
56474 // ie we expand axis ranges to encompass *everything*
56475 // that's currently in any of their ranges, but during
56476 // autorange of a subset of axes we will ignore other
56477 // axes for this purpose.
56478 var matchScale = Infinity;
56479 var normScales = {};
56480 var axes = {};
56481 var hasAnyDomainConstraint = false;
56482
56483 // find the (normalized) scale of each axis in the group
56484 for(j = 0; j < axisIDs.length; j++) {
56485 axisID = axisIDs[j];
56486 axes[axisID] = ax = fullLayout[id2name(axisID)];
56487
56488 if(ax._inputDomain) ax.domain = ax._inputDomain.slice();
56489 else ax._inputDomain = ax.domain.slice();
56490
56491 if(!ax._inputRange) ax._inputRange = ax.range.slice();
56492
56493 // set axis scale here so we can use _m rather than
56494 // having to calculate it from length and range
56495 ax.setScale();
56496
56497 // abs: inverted scales still satisfy the constraint
56498 normScales[axisID] = normScale = Math.abs(ax._m) / group[axisID];
56499 minScale = Math.min(minScale, normScale);
56500 if(ax.constrain === 'domain' || !ax._constraintShrinkable) {
56501 matchScale = Math.min(matchScale, normScale);
56502 }
56503
56504 // this has served its purpose, so remove it
56505 delete ax._constraintShrinkable;
56506 maxScale = Math.max(maxScale, normScale);
56507
56508 if(ax.constrain === 'domain') hasAnyDomainConstraint = true;
56509 }
56510
56511 // Do we have a constraint mismatch? Give a small buffer for rounding errors
56512 if(minScale > ALMOST_EQUAL * maxScale && !hasAnyDomainConstraint) continue;
56513
56514 // now increase any ranges we need to until all normalized scales are equal
56515 for(j = 0; j < axisIDs.length; j++) {
56516 axisID = axisIDs[j];
56517 normScale = normScales[axisID];
56518 ax = axes[axisID];
56519 mode = ax.constrain;
56520
56521 // even if the scale didn't change, if we're shrinking domain
56522 // we need to recalculate in case `constraintoward` changed
56523 if(normScale !== matchScale || mode === 'domain') {
56524 factor = normScale / matchScale;
56525
56526 if(mode === 'range') {
56527 scaleZoom(ax, factor);
56528 } else {
56529 // mode === 'domain'
56530
56531 var inputDomain = ax._inputDomain;
56532 var domainShrunk = (ax.domain[1] - ax.domain[0]) /
56533 (inputDomain[1] - inputDomain[0]);
56534 var rangeShrunk = (ax.r2l(ax.range[1]) - ax.r2l(ax.range[0])) /
56535 (ax.r2l(ax._inputRange[1]) - ax.r2l(ax._inputRange[0]));
56536
56537 factor /= domainShrunk;
56538
56539 if(factor * rangeShrunk < 1) {
56540 // we've asked to magnify the axis more than we can just by
56541 // enlarging the domain - so we need to constrict range
56542 ax.domain = ax._input.domain = inputDomain.slice();
56543 scaleZoom(ax, factor);
56544 continue;
56545 }
56546
56547 if(rangeShrunk < 1) {
56548 // the range has previously been constricted by ^^, but we've
56549 // switched to the domain-constricted regime, so reset range
56550 ax.range = ax._input.range = ax._inputRange.slice();
56551 factor *= rangeShrunk;
56552 }
56553
56554 if(ax.autorange) {
56555 /*
56556 * range & factor may need to change because range was
56557 * calculated for the larger scaling, so some pixel
56558 * paddings may get cut off when we reduce the domain.
56559 *
56560 * This is easier than the regular autorange calculation
56561 * because we already know the scaling `m`, but we still
56562 * need to cut out impossible constraints (like
56563 * annotations with super-long arrows). That's what
56564 * outerMin/Max are for - if the expansion was going to
56565 * go beyond the original domain, it must be impossible
56566 */
56567 var rl0 = ax.r2l(ax.range[0]);
56568 var rl1 = ax.r2l(ax.range[1]);
56569 var rangeCenter = (rl0 + rl1) / 2;
56570 var rangeMin = rangeCenter;
56571 var rangeMax = rangeCenter;
56572 var halfRange = Math.abs(rl1 - rangeCenter);
56573 // extra tiny bit for rounding errors, in case we actually
56574 // *are* expanding to the full domain
56575 var outerMin = rangeCenter - halfRange * factor * 1.0001;
56576 var outerMax = rangeCenter + halfRange * factor * 1.0001;
56577 var getPad = makePadFn(ax);
56578
56579 updateDomain(ax, factor);
56580 var m = Math.abs(ax._m);
56581 var extremes = concatExtremes(gd, ax);
56582 var minArray = extremes.min;
56583 var maxArray = extremes.max;
56584 var newVal;
56585 var k;
56586
56587 for(k = 0; k < minArray.length; k++) {
56588 newVal = minArray[k].val - getPad(minArray[k]) / m;
56589 if(newVal > outerMin && newVal < rangeMin) {
56590 rangeMin = newVal;
56591 }
56592 }
56593
56594 for(k = 0; k < maxArray.length; k++) {
56595 newVal = maxArray[k].val + getPad(maxArray[k]) / m;
56596 if(newVal < outerMax && newVal > rangeMax) {
56597 rangeMax = newVal;
56598 }
56599 }
56600
56601 var domainExpand = (rangeMax - rangeMin) / (2 * halfRange);
56602 factor /= domainExpand;
56603
56604 rangeMin = ax.l2r(rangeMin);
56605 rangeMax = ax.l2r(rangeMax);
56606 ax.range = ax._input.range = (rl0 < rl1) ?
56607 [rangeMin, rangeMax] : [rangeMax, rangeMin];
56608 }
56609
56610 updateDomain(ax, factor);
56611 }
56612 }
56613 }
56614 }
56615};
56616
56617// For use before autoranging, check if this axis was previously constrained
56618// by domain but no longer is
56619exports.clean = function clean(gd, ax) {
56620 if(ax._inputDomain) {
56621 var isConstrained = false;
56622 var axId = ax._id;
56623 var constraintGroups = gd._fullLayout._axisConstraintGroups;
56624 for(var j = 0; j < constraintGroups.length; j++) {
56625 if(constraintGroups[j][axId]) {
56626 isConstrained = true;
56627 break;
56628 }
56629 }
56630 if(!isConstrained || ax.constrain !== 'domain') {
56631 ax._input.domain = ax.domain = ax._inputDomain;
56632 delete ax._inputDomain;
56633 }
56634 }
56635};
56636
56637function updateDomain(ax, factor) {
56638 var inputDomain = ax._inputDomain;
56639 var centerFraction = FROM_BL[ax.constraintoward];
56640 var center = inputDomain[0] + (inputDomain[1] - inputDomain[0]) * centerFraction;
56641
56642 ax.domain = ax._input.domain = [
56643 center + (inputDomain[0] - center) / factor,
56644 center + (inputDomain[1] - center) / factor
56645 ];
56646 ax.setScale();
56647}
56648
56649},{"../../constants/alignment":154,"../../constants/numerical":158,"../../lib":178,"./autorange":221,"./axis_ids":225,"./scale_zoom":240}],230:[function(_dereq_,module,exports){
56650/**
56651* Copyright 2012-2020, Plotly, Inc.
56652* All rights reserved.
56653*
56654* This source code is licensed under the MIT license found in the
56655* LICENSE file in the root directory of this source tree.
56656*/
56657
56658'use strict';
56659
56660var d3 = _dereq_('d3');
56661var tinycolor = _dereq_('tinycolor2');
56662var supportsPassive = _dereq_('has-passive-events');
56663
56664var Registry = _dereq_('../../registry');
56665var Lib = _dereq_('../../lib');
56666var svgTextUtils = _dereq_('../../lib/svg_text_utils');
56667var Color = _dereq_('../../components/color');
56668var Drawing = _dereq_('../../components/drawing');
56669var Fx = _dereq_('../../components/fx');
56670var Axes = _dereq_('./axes');
56671var setCursor = _dereq_('../../lib/setcursor');
56672var dragElement = _dereq_('../../components/dragelement');
56673var helpers = _dereq_('../../components/dragelement/helpers');
56674var selectingOrDrawing = helpers.selectingOrDrawing;
56675var freeMode = helpers.freeMode;
56676
56677var FROM_TL = _dereq_('../../constants/alignment').FROM_TL;
56678var clearGlCanvases = _dereq_('../../lib/clear_gl_canvases');
56679var redrawReglTraces = _dereq_('../../plot_api/subroutines').redrawReglTraces;
56680
56681var Plots = _dereq_('../plots');
56682
56683var getFromId = _dereq_('./axis_ids').getFromId;
56684var prepSelect = _dereq_('./select').prepSelect;
56685var clearSelect = _dereq_('./select').clearSelect;
56686var selectOnClick = _dereq_('./select').selectOnClick;
56687var scaleZoom = _dereq_('./scale_zoom');
56688
56689var constants = _dereq_('./constants');
56690var MINDRAG = constants.MINDRAG;
56691var MINZOOM = constants.MINZOOM;
56692
56693// flag for showing "doubleclick to zoom out" only at the beginning
56694var SHOWZOOMOUTTIP = true;
56695
56696// dragBox: create an element to drag one or more axis ends
56697// inputs:
56698// plotinfo - which subplot are we making dragboxes on?
56699// x,y,w,h - left, top, width, height of the box
56700// ns - how does this drag the vertical axis?
56701// 'n' - top only
56702// 's' - bottom only
56703// 'ns' - top and bottom together, difference unchanged
56704// ew - same for horizontal axis
56705function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) {
56706 // mouseDown stores ms of first mousedown event in the last
56707 // `gd._context.doubleClickDelay` ms on the drag bars
56708 // numClicks stores how many mousedowns have been seen
56709 // within `gd._context.doubleClickDelay` so we can check for click or doubleclick events
56710 // dragged stores whether a drag has occurred, so we don't have to
56711 // redraw unnecessarily, ie if no move bigger than MINDRAG or MINZOOM px
56712 var zoomlayer = gd._fullLayout._zoomlayer;
56713 var isMainDrag = (ns + ew === 'nsew');
56714 var singleEnd = (ns + ew).length === 1;
56715
56716 // main subplot x and y (i.e. found in plotinfo - the main ones)
56717 var xa0, ya0;
56718 // {ax._id: ax} hash objects
56719 var xaHash, yaHash;
56720 // xaHash/yaHash values (arrays)
56721 var xaxes, yaxes;
56722 // main axis offsets
56723 var xs, ys;
56724 // main axis lengths
56725 var pw, ph;
56726 // contains keys 'xaHash', 'yaHash', 'xaxes', and 'yaxes'
56727 // which are the x/y {ax._id: ax} hash objects and their values
56728 // for linked axis relative to this subplot
56729 var links;
56730 // similar to `links` but for matching axes
56731 var matches;
56732 // set to ew/ns val when active, set to '' when inactive
56733 var xActive, yActive;
56734 // are all axes in this subplot are fixed?
56735 var allFixedRanges;
56736 // do we need to edit x/y ranges?
56737 var editX, editY;
56738 // graph-wide optimization flags
56739 var hasScatterGl, hasSplom, hasSVG;
56740 // collected changes to be made to the plot by relayout at the end
56741 var updates;
56742
56743 function recomputeAxisLists() {
56744 xa0 = plotinfo.xaxis;
56745 ya0 = plotinfo.yaxis;
56746 pw = xa0._length;
56747 ph = ya0._length;
56748 xs = xa0._offset;
56749 ys = ya0._offset;
56750
56751 xaHash = {};
56752 xaHash[xa0._id] = xa0;
56753 yaHash = {};
56754 yaHash[ya0._id] = ya0;
56755
56756 // if we're dragging two axes at once, also drag overlays
56757 if(ns && ew) {
56758 var overlays = plotinfo.overlays;
56759 for(var i = 0; i < overlays.length; i++) {
56760 var xa = overlays[i].xaxis;
56761 xaHash[xa._id] = xa;
56762 var ya = overlays[i].yaxis;
56763 yaHash[ya._id] = ya;
56764 }
56765 }
56766
56767 xaxes = hashValues(xaHash);
56768 yaxes = hashValues(yaHash);
56769 xActive = isDirectionActive(xaxes, ew);
56770 yActive = isDirectionActive(yaxes, ns);
56771 allFixedRanges = !yActive && !xActive;
56772
56773 links = calcLinks(gd, gd._fullLayout._axisConstraintGroups, xaHash, yaHash);
56774 matches = calcLinks(gd, gd._fullLayout._axisMatchGroups, xaHash, yaHash);
56775 editX = ew || links.isSubplotConstrained || matches.isSubplotConstrained;
56776 editY = ns || links.isSubplotConstrained || matches.isSubplotConstrained;
56777
56778 var fullLayout = gd._fullLayout;
56779 hasScatterGl = fullLayout._has('scattergl');
56780 hasSplom = fullLayout._has('splom');
56781 hasSVG = fullLayout._has('svg');
56782 }
56783
56784 recomputeAxisLists();
56785
56786 var cursor = getDragCursor(yActive + xActive, gd._fullLayout.dragmode, isMainDrag);
56787 var dragger = makeRectDragger(plotinfo, ns + ew + 'drag', cursor, x, y, w, h);
56788
56789 // still need to make the element if the axes are disabled
56790 // but nuke its events (except for maindrag which needs them for hover)
56791 // and stop there
56792 if(allFixedRanges && !isMainDrag) {
56793 dragger.onmousedown = null;
56794 dragger.style.pointerEvents = 'none';
56795 return dragger;
56796 }
56797
56798 var dragOptions = {
56799 element: dragger,
56800 gd: gd,
56801 plotinfo: plotinfo
56802 };
56803
56804 dragOptions.prepFn = function(e, startX, startY) {
56805 var dragModePrev = dragOptions.dragmode;
56806 var dragModeNow = gd._fullLayout.dragmode;
56807 if(dragModeNow !== dragModePrev) {
56808 dragOptions.dragmode = dragModeNow;
56809 }
56810
56811 recomputeAxisLists();
56812
56813 if(!allFixedRanges) {
56814 if(isMainDrag) {
56815 // main dragger handles all drag modes, and changes
56816 // to pan (or to zoom if it already is pan) on shift
56817 if(e.shiftKey) {
56818 if(dragModeNow === 'pan') dragModeNow = 'zoom';
56819 else if(!selectingOrDrawing(dragModeNow)) dragModeNow = 'pan';
56820 } else if(e.ctrlKey) {
56821 dragModeNow = 'pan';
56822 }
56823 } else {
56824 // all other draggers just pan
56825 dragModeNow = 'pan';
56826 }
56827 }
56828
56829 if(freeMode(dragModeNow)) dragOptions.minDrag = 1;
56830 else dragOptions.minDrag = undefined;
56831
56832 if(selectingOrDrawing(dragModeNow)) {
56833 dragOptions.xaxes = xaxes;
56834 dragOptions.yaxes = yaxes;
56835 // this attaches moveFn, clickFn, doneFn on dragOptions
56836 prepSelect(e, startX, startY, dragOptions, dragModeNow);
56837 } else {
56838 dragOptions.clickFn = clickFn;
56839 if(selectingOrDrawing(dragModePrev)) {
56840 // TODO Fix potential bug
56841 // Note: clearing / resetting selection state only happens, when user
56842 // triggers at least one interaction in pan/zoom mode. Otherwise, the
56843 // select/lasso outlines are deleted (in plots.js.cleanPlot) but the selection
56844 // cache isn't cleared. So when the user switches back to select/lasso and
56845 // 'adds to a selection' with Shift, the "old", seemingly removed outlines
56846 // are redrawn again because the selection cache still holds their coordinates.
56847 // However, this isn't easily solved, since plots.js would need
56848 // to have a reference to the dragOptions object (which holds the
56849 // selection cache).
56850 clearAndResetSelect();
56851 }
56852
56853 if(!allFixedRanges) {
56854 if(dragModeNow === 'zoom') {
56855 dragOptions.moveFn = zoomMove;
56856 dragOptions.doneFn = zoomDone;
56857
56858 // zoomMove takes care of the threshold, but we need to
56859 // minimize this so that constrained zoom boxes will flip
56860 // orientation at the right place
56861 dragOptions.minDrag = 1;
56862
56863 zoomPrep(e, startX, startY);
56864 } else if(dragModeNow === 'pan') {
56865 dragOptions.moveFn = plotDrag;
56866 dragOptions.doneFn = dragTail;
56867 }
56868 }
56869 }
56870
56871 gd._fullLayout._redrag = function() {
56872 var dragDataNow = gd._dragdata;
56873
56874 if(dragDataNow && dragDataNow.element === dragger) {
56875 var dragModeNow = gd._fullLayout.dragmode;
56876
56877 if(!selectingOrDrawing(dragModeNow)) {
56878 recomputeAxisLists();
56879 updateSubplots([0, 0, pw, ph]);
56880 dragOptions.moveFn(dragDataNow.dx, dragDataNow.dy);
56881 }
56882
56883 // TODO should we try to "re-select" under select/lasso modes?
56884 // probably best to wait for https://github.com/plotly/plotly.js/issues/1851
56885 }
56886 };
56887 };
56888
56889 function clearAndResetSelect() {
56890 // clear selection polygon cache (if any)
56891 dragOptions.plotinfo.selection = false;
56892 // clear selection outlines
56893 clearSelect(gd);
56894 }
56895
56896 function clickFn(numClicks, evt) {
56897 var gd = dragOptions.gd;
56898 if(gd._fullLayout._activeShapeIndex >= 0) {
56899 gd._fullLayout._deactivateShape(gd);
56900 return;
56901 }
56902
56903 var clickmode = gd._fullLayout.clickmode;
56904
56905 removeZoombox(gd);
56906
56907 if(numClicks === 2 && !singleEnd) doubleClick();
56908
56909 if(isMainDrag) {
56910 if(clickmode.indexOf('select') > -1) {
56911 selectOnClick(evt, gd, xaxes, yaxes, plotinfo.id, dragOptions);
56912 }
56913
56914 if(clickmode.indexOf('event') > -1) {
56915 Fx.click(gd, evt, plotinfo.id);
56916 }
56917 } else if(numClicks === 1 && singleEnd) {
56918 var ax = ns ? ya0 : xa0;
56919 var end = (ns === 's' || ew === 'w') ? 0 : 1;
56920 var attrStr = ax._name + '.range[' + end + ']';
56921 var initialText = getEndText(ax, end);
56922 var hAlign = 'left';
56923 var vAlign = 'middle';
56924
56925 if(ax.fixedrange) return;
56926
56927 if(ns) {
56928 vAlign = (ns === 'n') ? 'top' : 'bottom';
56929 if(ax.side === 'right') hAlign = 'right';
56930 } else if(ew === 'e') hAlign = 'right';
56931
56932 if(gd._context.showAxisRangeEntryBoxes) {
56933 d3.select(dragger)
56934 .call(svgTextUtils.makeEditable, {
56935 gd: gd,
56936 immediate: true,
56937 background: gd._fullLayout.paper_bgcolor,
56938 text: String(initialText),
56939 fill: ax.tickfont ? ax.tickfont.color : '#444',
56940 horizontalAlign: hAlign,
56941 verticalAlign: vAlign
56942 })
56943 .on('edit', function(text) {
56944 var v = ax.d2r(text);
56945 if(v !== undefined) {
56946 Registry.call('_guiRelayout', gd, attrStr, v);
56947 }
56948 });
56949 }
56950 }
56951 }
56952
56953 dragElement.init(dragOptions);
56954
56955 // x/y px position at start of drag
56956 var x0, y0;
56957 // bbox object of the zoombox
56958 var box;
56959 // luminance of bg behind zoombox
56960 var lum;
56961 // zoombox path outline
56962 var path0;
56963 // is zoombox dimmed (during drag)
56964 var dimmed;
56965 // 'x'-only, 'y' or 'xy' zooming
56966 var zoomMode;
56967 // zoombox d3 selection
56968 var zb;
56969 // zoombox corner d3 selection
56970 var corners;
56971 // zoom takes over minDrag, so it also has to take over gd._dragged
56972 var zoomDragged;
56973
56974 function zoomPrep(e, startX, startY) {
56975 var dragBBox = dragger.getBoundingClientRect();
56976 x0 = startX - dragBBox.left;
56977 y0 = startY - dragBBox.top;
56978 box = {l: x0, r: x0, w: 0, t: y0, b: y0, h: 0};
56979 lum = gd._hmpixcount ?
56980 (gd._hmlumcount / gd._hmpixcount) :
56981 tinycolor(gd._fullLayout.plot_bgcolor).getLuminance();
56982 path0 = 'M0,0H' + pw + 'V' + ph + 'H0V0';
56983 dimmed = false;
56984 zoomMode = 'xy';
56985 zoomDragged = false;
56986 zb = makeZoombox(zoomlayer, lum, xs, ys, path0);
56987 corners = makeCorners(zoomlayer, xs, ys);
56988 }
56989
56990 function zoomMove(dx0, dy0) {
56991 if(gd._transitioningWithDuration) {
56992 return false;
56993 }
56994
56995 var x1 = Math.max(0, Math.min(pw, dx0 + x0));
56996 var y1 = Math.max(0, Math.min(ph, dy0 + y0));
56997 var dx = Math.abs(x1 - x0);
56998 var dy = Math.abs(y1 - y0);
56999
57000 box.l = Math.min(x0, x1);
57001 box.r = Math.max(x0, x1);
57002 box.t = Math.min(y0, y1);
57003 box.b = Math.max(y0, y1);
57004
57005 function noZoom() {
57006 zoomMode = '';
57007 box.r = box.l;
57008 box.t = box.b;
57009 corners.attr('d', 'M0,0Z');
57010 }
57011
57012 if(links.isSubplotConstrained) {
57013 if(dx > MINZOOM || dy > MINZOOM) {
57014 zoomMode = 'xy';
57015 if(dx / pw > dy / ph) {
57016 dy = dx * ph / pw;
57017 if(y0 > y1) box.t = y0 - dy;
57018 else box.b = y0 + dy;
57019 } else {
57020 dx = dy * pw / ph;
57021 if(x0 > x1) box.l = x0 - dx;
57022 else box.r = x0 + dx;
57023 }
57024 corners.attr('d', xyCorners(box));
57025 } else {
57026 noZoom();
57027 }
57028 } else if(matches.isSubplotConstrained) {
57029 if(dx > MINZOOM || dy > MINZOOM) {
57030 zoomMode = 'xy';
57031
57032 var r0 = Math.min(box.l / pw, (ph - box.b) / ph);
57033 var r1 = Math.max(box.r / pw, (ph - box.t) / ph);
57034
57035 box.l = r0 * pw;
57036 box.r = r1 * pw;
57037 box.b = (1 - r0) * ph;
57038 box.t = (1 - r1) * ph;
57039 corners.attr('d', xyCorners(box));
57040 } else {
57041 noZoom();
57042 }
57043 } else if(!yActive || dy < Math.min(Math.max(dx * 0.6, MINDRAG), MINZOOM)) {
57044 // look for small drags in one direction or the other,
57045 // and only drag the other axis
57046
57047 if(dx < MINDRAG || !xActive) {
57048 noZoom();
57049 } else {
57050 box.t = 0;
57051 box.b = ph;
57052 zoomMode = 'x';
57053 corners.attr('d', xCorners(box, y0));
57054 }
57055 } else if(!xActive || dx < Math.min(dy * 0.6, MINZOOM)) {
57056 box.l = 0;
57057 box.r = pw;
57058 zoomMode = 'y';
57059 corners.attr('d', yCorners(box, x0));
57060 } else {
57061 zoomMode = 'xy';
57062 corners.attr('d', xyCorners(box));
57063 }
57064 box.w = box.r - box.l;
57065 box.h = box.b - box.t;
57066
57067 if(zoomMode) zoomDragged = true;
57068 gd._dragged = zoomDragged;
57069
57070 updateZoombox(zb, corners, box, path0, dimmed, lum);
57071 computeZoomUpdates();
57072 gd.emit('plotly_relayouting', updates);
57073 dimmed = true;
57074 }
57075
57076 function computeZoomUpdates() {
57077 updates = {};
57078
57079 // TODO: edit linked axes in zoomAxRanges and in dragTail
57080 if(zoomMode === 'xy' || zoomMode === 'x') {
57081 zoomAxRanges(xaxes, box.l / pw, box.r / pw, updates, links.xaxes);
57082 updateMatchedAxRange('x', updates);
57083 }
57084 if(zoomMode === 'xy' || zoomMode === 'y') {
57085 zoomAxRanges(yaxes, (ph - box.b) / ph, (ph - box.t) / ph, updates, links.yaxes);
57086 updateMatchedAxRange('y', updates);
57087 }
57088 }
57089
57090 function zoomDone() {
57091 computeZoomUpdates();
57092 removeZoombox(gd);
57093 dragTail();
57094 showDoubleClickNotifier(gd);
57095 }
57096
57097 // scroll zoom, on all draggers except corners
57098 var scrollViewBox = [0, 0, pw, ph];
57099 // wait a little after scrolling before redrawing
57100 var redrawTimer = null;
57101 var REDRAWDELAY = constants.REDRAWDELAY;
57102 var mainplot = plotinfo.mainplot ? gd._fullLayout._plots[plotinfo.mainplot] : plotinfo;
57103
57104 function zoomWheel(e) {
57105 // deactivate mousewheel scrolling on embedded graphs
57106 // devs can override this with layout._enablescrollzoom,
57107 // but _ ensures this setting won't leave their page
57108 if(!gd._context._scrollZoom.cartesian && !gd._fullLayout._enablescrollzoom) {
57109 return;
57110 }
57111
57112 clearAndResetSelect();
57113
57114 // If a transition is in progress, then disable any behavior:
57115 if(gd._transitioningWithDuration) {
57116 e.preventDefault();
57117 e.stopPropagation();
57118 return;
57119 }
57120
57121 recomputeAxisLists();
57122
57123 clearTimeout(redrawTimer);
57124
57125 var wheelDelta = -e.deltaY;
57126 if(!isFinite(wheelDelta)) wheelDelta = e.wheelDelta / 10;
57127 if(!isFinite(wheelDelta)) {
57128 Lib.log('Did not find wheel motion attributes: ', e);
57129 return;
57130 }
57131
57132 var zoom = Math.exp(-Math.min(Math.max(wheelDelta, -20), 20) / 200);
57133 var gbb = mainplot.draglayer.select('.nsewdrag').node().getBoundingClientRect();
57134 var xfrac = (e.clientX - gbb.left) / gbb.width;
57135 var yfrac = (gbb.bottom - e.clientY) / gbb.height;
57136 var i;
57137
57138 function zoomWheelOneAxis(ax, centerFraction, zoom) {
57139 if(ax.fixedrange) return;
57140
57141 var axRange = Lib.simpleMap(ax.range, ax.r2l);
57142 var v0 = axRange[0] + (axRange[1] - axRange[0]) * centerFraction;
57143 function doZoom(v) { return ax.l2r(v0 + (v - v0) * zoom); }
57144 ax.range = axRange.map(doZoom);
57145 }
57146
57147 if(editX) {
57148 // if we're only zooming this axis because of constraints,
57149 // zoom it about the center
57150 if(!ew) xfrac = 0.5;
57151
57152 for(i = 0; i < xaxes.length; i++) {
57153 zoomWheelOneAxis(xaxes[i], xfrac, zoom);
57154 }
57155 updateMatchedAxRange('x');
57156
57157 scrollViewBox[2] *= zoom;
57158 scrollViewBox[0] += scrollViewBox[2] * xfrac * (1 / zoom - 1);
57159 }
57160 if(editY) {
57161 if(!ns) yfrac = 0.5;
57162
57163 for(i = 0; i < yaxes.length; i++) {
57164 zoomWheelOneAxis(yaxes[i], yfrac, zoom);
57165 }
57166 updateMatchedAxRange('y');
57167
57168 scrollViewBox[3] *= zoom;
57169 scrollViewBox[1] += scrollViewBox[3] * (1 - yfrac) * (1 / zoom - 1);
57170 }
57171
57172 // viewbox redraw at first
57173 updateSubplots(scrollViewBox);
57174 ticksAndAnnotations();
57175
57176 gd.emit('plotly_relayouting', updates);
57177
57178 // then replot after a delay to make sure
57179 // no more scrolling is coming
57180 redrawTimer = setTimeout(function() {
57181 scrollViewBox = [0, 0, pw, ph];
57182 dragTail();
57183 }, REDRAWDELAY);
57184
57185 e.preventDefault();
57186 return;
57187 }
57188
57189 // everything but the corners gets wheel zoom
57190 if(ns.length * ew.length !== 1) {
57191 attachWheelEventHandler(dragger, zoomWheel);
57192 }
57193
57194 // plotDrag: move the plot in response to a drag
57195 function plotDrag(dx, dy) {
57196 // If a transition is in progress, then disable any behavior:
57197 if(gd._transitioningWithDuration) {
57198 return;
57199 }
57200
57201 // prevent axis drawing from monkeying with margins until we're done
57202 gd._fullLayout._replotting = true;
57203
57204 if(xActive === 'ew' || yActive === 'ns') {
57205 if(xActive) {
57206 dragAxList(xaxes, dx);
57207 updateMatchedAxRange('x');
57208 }
57209 if(yActive) {
57210 dragAxList(yaxes, dy);
57211 updateMatchedAxRange('y');
57212 }
57213 updateSubplots([xActive ? -dx : 0, yActive ? -dy : 0, pw, ph]);
57214 ticksAndAnnotations();
57215 gd.emit('plotly_relayouting', updates);
57216 return;
57217 }
57218
57219 // dz: set a new value for one end (0 or 1) of an axis array axArray,
57220 // and return a pixel shift for that end for the viewbox
57221 // based on pixel drag distance d
57222 // TODO: this makes (generally non-fatal) errors when you get
57223 // near floating point limits
57224 function dz(axArray, end, d) {
57225 var otherEnd = 1 - end;
57226 var movedAx;
57227 var newLinearizedEnd;
57228 for(var i = 0; i < axArray.length; i++) {
57229 var axi = axArray[i];
57230 if(axi.fixedrange) continue;
57231 movedAx = axi;
57232 newLinearizedEnd = axi._rl[otherEnd] +
57233 (axi._rl[end] - axi._rl[otherEnd]) / dZoom(d / axi._length);
57234 var newEnd = axi.l2r(newLinearizedEnd);
57235
57236 // if l2r comes back false or undefined, it means we've dragged off
57237 // the end of valid ranges - so stop.
57238 if(newEnd !== false && newEnd !== undefined) axi.range[end] = newEnd;
57239 }
57240 return movedAx._length * (movedAx._rl[end] - newLinearizedEnd) /
57241 (movedAx._rl[end] - movedAx._rl[otherEnd]);
57242 }
57243
57244 if(links.isSubplotConstrained && xActive && yActive) {
57245 // dragging a corner of a constrained subplot:
57246 // respect the fixed corner, but harmonize dx and dy
57247 var dxySign = ((xActive === 'w') === (yActive === 'n')) ? 1 : -1;
57248 var dxyFraction = (dx / pw + dxySign * dy / ph) / 2;
57249 dx = dxyFraction * pw;
57250 dy = dxySign * dxyFraction * ph;
57251 }
57252
57253 if(xActive === 'w') dx = dz(xaxes, 0, dx);
57254 else if(xActive === 'e') dx = dz(xaxes, 1, -dx);
57255 else if(!xActive) dx = 0;
57256
57257 if(yActive === 'n') dy = dz(yaxes, 1, dy);
57258 else if(yActive === 's') dy = dz(yaxes, 0, -dy);
57259 else if(!yActive) dy = 0;
57260
57261 var xStart = (xActive === 'w') ? dx : 0;
57262 var yStart = (yActive === 'n') ? dy : 0;
57263
57264 if(links.isSubplotConstrained) {
57265 var i;
57266 if(!xActive && yActive.length === 1) {
57267 // dragging one end of the y axis of a constrained subplot
57268 // scale the other axis the same about its middle
57269 for(i = 0; i < xaxes.length; i++) {
57270 xaxes[i].range = xaxes[i]._r.slice();
57271 scaleZoom(xaxes[i], 1 - dy / ph);
57272 }
57273 dx = dy * pw / ph;
57274 xStart = dx / 2;
57275 }
57276 if(!yActive && xActive.length === 1) {
57277 for(i = 0; i < yaxes.length; i++) {
57278 yaxes[i].range = yaxes[i]._r.slice();
57279 scaleZoom(yaxes[i], 1 - dx / pw);
57280 }
57281 dy = dx * ph / pw;
57282 yStart = dy / 2;
57283 }
57284 }
57285
57286 updateMatchedAxRange('x');
57287 updateMatchedAxRange('y');
57288 updateSubplots([xStart, yStart, pw - dx, ph - dy]);
57289 ticksAndAnnotations();
57290 gd.emit('plotly_relayouting', updates);
57291 }
57292
57293 function updateMatchedAxRange(axLetter, out) {
57294 var matchedAxes = matches.isSubplotConstrained ?
57295 {x: yaxes, y: xaxes}[axLetter] :
57296 matches[axLetter + 'axes'];
57297
57298 var constrainedAxes = matches.isSubplotConstrained ?
57299 {x: xaxes, y: yaxes}[axLetter] :
57300 [];
57301
57302 for(var i = 0; i < matchedAxes.length; i++) {
57303 var ax = matchedAxes[i];
57304 var axId = ax._id;
57305 var axId2 = matches.xLinks[axId] || matches.yLinks[axId];
57306 var ax2 = constrainedAxes[0] || xaHash[axId2] || yaHash[axId2];
57307
57308 if(ax2) {
57309 if(out) {
57310 // zoombox case - don't mutate 'range', just add keys in 'updates'
57311 out[ax._name + '.range[0]'] = out[ax2._name + '.range[0]'];
57312 out[ax._name + '.range[1]'] = out[ax2._name + '.range[1]'];
57313 } else {
57314 ax.range = ax2.range.slice();
57315 }
57316 }
57317 }
57318 }
57319
57320 // Draw ticks and annotations (and other components) when ranges change.
57321 // Also records the ranges that have changed for use by update at the end.
57322 function ticksAndAnnotations() {
57323 var activeAxIds = [];
57324 var i;
57325
57326 function pushActiveAxIds(axList) {
57327 for(i = 0; i < axList.length; i++) {
57328 if(!axList[i].fixedrange) activeAxIds.push(axList[i]._id);
57329 }
57330 }
57331
57332 if(editX) {
57333 pushActiveAxIds(xaxes);
57334 pushActiveAxIds(links.xaxes);
57335 pushActiveAxIds(matches.xaxes);
57336 }
57337 if(editY) {
57338 pushActiveAxIds(yaxes);
57339 pushActiveAxIds(links.yaxes);
57340 pushActiveAxIds(matches.yaxes);
57341 }
57342
57343 updates = {};
57344 for(i = 0; i < activeAxIds.length; i++) {
57345 var axId = activeAxIds[i];
57346 var ax = getFromId(gd, axId);
57347 Axes.drawOne(gd, ax, {skipTitle: true});
57348 updates[ax._name + '.range[0]'] = ax.range[0];
57349 updates[ax._name + '.range[1]'] = ax.range[1];
57350 }
57351
57352 Axes.redrawComponents(gd, activeAxIds);
57353 }
57354
57355 function doubleClick() {
57356 if(gd._transitioningWithDuration) return;
57357
57358 var doubleClickConfig = gd._context.doubleClick;
57359
57360 var axList = [];
57361 if(xActive) axList = axList.concat(xaxes);
57362 if(yActive) axList = axList.concat(yaxes);
57363 if(matches.xaxes) axList = axList.concat(matches.xaxes);
57364 if(matches.yaxes) axList = axList.concat(matches.yaxes);
57365
57366 var attrs = {};
57367 var ax, i, rangeInitial;
57368
57369 // For reset+autosize mode:
57370 // If *any* of the main axes is not at its initial range
57371 // (or autoranged, if we have no initial range, to match the logic in
57372 // doubleClickConfig === 'reset' below), we reset.
57373 // If they are *all* at their initial ranges, then we autosize.
57374 if(doubleClickConfig === 'reset+autosize') {
57375 doubleClickConfig = 'autosize';
57376
57377 for(i = 0; i < axList.length; i++) {
57378 ax = axList[i];
57379 if((ax._rangeInitial && (
57380 ax.range[0] !== ax._rangeInitial[0] ||
57381 ax.range[1] !== ax._rangeInitial[1]
57382 )) ||
57383 (!ax._rangeInitial && !ax.autorange)
57384 ) {
57385 doubleClickConfig = 'reset';
57386 break;
57387 }
57388 }
57389 }
57390
57391 if(doubleClickConfig === 'autosize') {
57392 // don't set the linked axes here, so relayout marks them as shrinkable
57393 // and we autosize just to the requested axis/axes
57394 for(i = 0; i < axList.length; i++) {
57395 ax = axList[i];
57396 if(!ax.fixedrange) attrs[ax._name + '.autorange'] = true;
57397 }
57398 } else if(doubleClickConfig === 'reset') {
57399 // when we're resetting, reset all linked axes too, so we get back
57400 // to the fully-auto-with-constraints situation
57401 if(xActive || links.isSubplotConstrained) axList = axList.concat(links.xaxes);
57402 if(yActive && !links.isSubplotConstrained) axList = axList.concat(links.yaxes);
57403
57404 if(links.isSubplotConstrained) {
57405 if(!xActive) axList = axList.concat(xaxes);
57406 else if(!yActive) axList = axList.concat(yaxes);
57407 }
57408
57409 for(i = 0; i < axList.length; i++) {
57410 ax = axList[i];
57411
57412 if(!ax.fixedrange) {
57413 if(!ax._rangeInitial) {
57414 attrs[ax._name + '.autorange'] = true;
57415 } else {
57416 rangeInitial = ax._rangeInitial;
57417 attrs[ax._name + '.range[0]'] = rangeInitial[0];
57418 attrs[ax._name + '.range[1]'] = rangeInitial[1];
57419 }
57420 }
57421 }
57422 }
57423
57424 gd.emit('plotly_doubleclick', null);
57425 Registry.call('_guiRelayout', gd, attrs);
57426 }
57427
57428 // dragTail - finish a drag event with a redraw
57429 function dragTail() {
57430 // put the subplot viewboxes back to default (Because we're going to)
57431 // be repositioning the data in the relayout. But DON'T call
57432 // ticksAndAnnotations again - it's unnecessary and would overwrite `updates`
57433 updateSubplots([0, 0, pw, ph]);
57434
57435 // since we may have been redrawing some things during the drag, we may have
57436 // accumulated MathJax promises - wait for them before we relayout.
57437 Lib.syncOrAsync([
57438 Plots.previousPromises,
57439 function() {
57440 gd._fullLayout._replotting = false;
57441 Registry.call('_guiRelayout', gd, updates);
57442 }
57443 ], gd);
57444 }
57445
57446 // updateSubplots - find all plot viewboxes that should be
57447 // affected by this drag, and update them. look for all plots
57448 // sharing an affected axis (including the one being dragged),
57449 // includes also scattergl and splom logic.
57450 function updateSubplots(viewBox) {
57451 var fullLayout = gd._fullLayout;
57452 var plotinfos = fullLayout._plots;
57453 var subplots = fullLayout._subplots.cartesian;
57454 var i, sp, xa, ya;
57455
57456 if(hasSplom) {
57457 Registry.subplotsRegistry.splom.drag(gd);
57458 }
57459
57460 if(hasScatterGl) {
57461 for(i = 0; i < subplots.length; i++) {
57462 sp = plotinfos[subplots[i]];
57463 xa = sp.xaxis;
57464 ya = sp.yaxis;
57465
57466 if(sp._scene) {
57467 var xrng = Lib.simpleMap(xa.range, xa.r2l);
57468 var yrng = Lib.simpleMap(ya.range, ya.r2l);
57469 sp._scene.update({range: [xrng[0], yrng[0], xrng[1], yrng[1]]});
57470 }
57471 }
57472 }
57473
57474 if(hasSplom || hasScatterGl) {
57475 clearGlCanvases(gd);
57476 redrawReglTraces(gd);
57477 }
57478
57479 if(hasSVG) {
57480 var xScaleFactor = viewBox[2] / xa0._length;
57481 var yScaleFactor = viewBox[3] / ya0._length;
57482
57483 for(i = 0; i < subplots.length; i++) {
57484 sp = plotinfos[subplots[i]];
57485 xa = sp.xaxis;
57486 ya = sp.yaxis;
57487
57488 var editX2 = editX && !xa.fixedrange && xaHash[xa._id];
57489 var editY2 = editY && !ya.fixedrange && yaHash[ya._id];
57490
57491 var xScaleFactor2, yScaleFactor2;
57492 var clipDx, clipDy;
57493
57494 if(editX2) {
57495 xScaleFactor2 = xScaleFactor;
57496 clipDx = ew ? viewBox[0] : getShift(xa, xScaleFactor2);
57497 } else if(matches.xaHash[xa._id]) {
57498 xScaleFactor2 = xScaleFactor;
57499 clipDx = viewBox[0] * xa._length / xa0._length;
57500 } else if(matches.yaHash[xa._id]) {
57501 xScaleFactor2 = yScaleFactor;
57502 clipDx = yActive === 'ns' ?
57503 -viewBox[1] * xa._length / ya0._length :
57504 getShift(xa, xScaleFactor2, {n: 'top', s: 'bottom'}[yActive]);
57505 } else {
57506 xScaleFactor2 = getLinkedScaleFactor(xa, xScaleFactor, yScaleFactor);
57507 clipDx = scaleAndGetShift(xa, xScaleFactor2);
57508 }
57509
57510 if(editY2) {
57511 yScaleFactor2 = yScaleFactor;
57512 clipDy = ns ? viewBox[1] : getShift(ya, yScaleFactor2);
57513 } else if(matches.yaHash[ya._id]) {
57514 yScaleFactor2 = yScaleFactor;
57515 clipDy = viewBox[1] * ya._length / ya0._length;
57516 } else if(matches.xaHash[ya._id]) {
57517 yScaleFactor2 = xScaleFactor;
57518 clipDy = xActive === 'ew' ?
57519 -viewBox[0] * ya._length / xa0._length :
57520 getShift(ya, yScaleFactor2, {e: 'right', w: 'left'}[xActive]);
57521 } else {
57522 yScaleFactor2 = getLinkedScaleFactor(ya, xScaleFactor, yScaleFactor);
57523 clipDy = scaleAndGetShift(ya, yScaleFactor2);
57524 }
57525
57526 // don't scale at all if neither axis is scalable here
57527 if(!xScaleFactor2 && !yScaleFactor2) {
57528 continue;
57529 }
57530
57531 // but if only one is, reset the other axis scaling
57532 if(!xScaleFactor2) xScaleFactor2 = 1;
57533 if(!yScaleFactor2) yScaleFactor2 = 1;
57534
57535 var plotDx = xa._offset - clipDx / xScaleFactor2;
57536 var plotDy = ya._offset - clipDy / yScaleFactor2;
57537
57538 // TODO could be more efficient here:
57539 // setTranslate and setScale do a lot of extra work
57540 // when working independently, should perhaps combine
57541 // them into a single routine.
57542 sp.clipRect
57543 .call(Drawing.setTranslate, clipDx, clipDy)
57544 .call(Drawing.setScale, xScaleFactor2, yScaleFactor2);
57545
57546 sp.plot
57547 .call(Drawing.setTranslate, plotDx, plotDy)
57548 .call(Drawing.setScale, 1 / xScaleFactor2, 1 / yScaleFactor2);
57549
57550 // apply an inverse scale to individual points to counteract
57551 // the scale of the trace group.
57552 // apply only when scale changes, as adjusting the scale of
57553 // all the points can be expansive.
57554 if(xScaleFactor2 !== sp.xScaleFactor || yScaleFactor2 !== sp.yScaleFactor) {
57555 Drawing.setPointGroupScale(sp.zoomScalePts, xScaleFactor2, yScaleFactor2);
57556 Drawing.setTextPointsScale(sp.zoomScaleTxt, xScaleFactor2, yScaleFactor2);
57557 }
57558
57559 Drawing.hideOutsideRangePoints(sp.clipOnAxisFalseTraces, sp);
57560
57561 // update x/y scaleFactor stash
57562 sp.xScaleFactor = xScaleFactor2;
57563 sp.yScaleFactor = yScaleFactor2;
57564 }
57565 }
57566 }
57567
57568 // Find the appropriate scaling for this axis, if it's linked to the
57569 // dragged axes by constraints. 0 is special, it means this axis shouldn't
57570 // ever be scaled (will be converted to 1 if the other axis is scaled)
57571 function getLinkedScaleFactor(ax, xScaleFactor, yScaleFactor) {
57572 if(ax.fixedrange) return 0;
57573
57574 if(editX && links.xaHash[ax._id]) {
57575 return xScaleFactor;
57576 }
57577 if(editY && (links.isSubplotConstrained ? links.xaHash : links.yaHash)[ax._id]) {
57578 return yScaleFactor;
57579 }
57580 return 0;
57581 }
57582
57583 function scaleAndGetShift(ax, scaleFactor) {
57584 if(scaleFactor) {
57585 ax.range = ax._r.slice();
57586 scaleZoom(ax, scaleFactor);
57587 return getShift(ax, scaleFactor);
57588 }
57589 return 0;
57590 }
57591
57592 function getShift(ax, scaleFactor, from) {
57593 return ax._length * (1 - scaleFactor) * FROM_TL[from || ax.constraintoward || 'middle'];
57594 }
57595
57596 return dragger;
57597}
57598
57599function makeDragger(plotinfo, nodeName, dragClass, cursor) {
57600 var dragger3 = Lib.ensureSingle(plotinfo.draglayer, nodeName, dragClass, function(s) {
57601 s.classed('drag', true)
57602 .style({fill: 'transparent', 'stroke-width': 0})
57603 .attr('data-subplot', plotinfo.id);
57604 });
57605
57606 dragger3.call(setCursor, cursor);
57607
57608 return dragger3.node();
57609}
57610
57611function makeRectDragger(plotinfo, dragClass, cursor, x, y, w, h) {
57612 var dragger = makeDragger(plotinfo, 'rect', dragClass, cursor);
57613 d3.select(dragger).call(Drawing.setRect, x, y, w, h);
57614 return dragger;
57615}
57616
57617function isDirectionActive(axList, activeVal) {
57618 for(var i = 0; i < axList.length; i++) {
57619 if(!axList[i].fixedrange) return activeVal;
57620 }
57621 return '';
57622}
57623
57624function getEndText(ax, end) {
57625 var initialVal = ax.range[end];
57626 var diff = Math.abs(initialVal - ax.range[1 - end]);
57627 var dig;
57628
57629 // TODO: this should basically be ax.r2d but we're doing extra
57630 // rounding here... can we clean up at all?
57631 if(ax.type === 'date') {
57632 return initialVal;
57633 } else if(ax.type === 'log') {
57634 dig = Math.ceil(Math.max(0, -Math.log(diff) / Math.LN10)) + 3;
57635 return d3.format('.' + dig + 'g')(Math.pow(10, initialVal));
57636 } else { // linear numeric (or category... but just show numbers here)
57637 dig = Math.floor(Math.log(Math.abs(initialVal)) / Math.LN10) -
57638 Math.floor(Math.log(diff) / Math.LN10) + 4;
57639 return d3.format('.' + String(dig) + 'g')(initialVal);
57640 }
57641}
57642
57643function zoomAxRanges(axList, r0Fraction, r1Fraction, updates, linkedAxes) {
57644 for(var i = 0; i < axList.length; i++) {
57645 var axi = axList[i];
57646 if(axi.fixedrange) continue;
57647
57648 if(axi.rangebreaks) {
57649 var isY = axi._id.charAt(0) === 'y';
57650 var r0F = isY ? (1 - r0Fraction) : r0Fraction;
57651 var r1F = isY ? (1 - r1Fraction) : r1Fraction;
57652
57653 updates[axi._name + '.range[0]'] = axi.l2r(axi.p2l(r0F * axi._length));
57654 updates[axi._name + '.range[1]'] = axi.l2r(axi.p2l(r1F * axi._length));
57655 } else {
57656 var axRangeLinear0 = axi._rl[0];
57657 var axRangeLinearSpan = axi._rl[1] - axRangeLinear0;
57658 updates[axi._name + '.range[0]'] = axi.l2r(axRangeLinear0 + axRangeLinearSpan * r0Fraction);
57659 updates[axi._name + '.range[1]'] = axi.l2r(axRangeLinear0 + axRangeLinearSpan * r1Fraction);
57660 }
57661 }
57662
57663 // zoom linked axes about their centers
57664 if(linkedAxes && linkedAxes.length) {
57665 var linkedR0Fraction = (r0Fraction + (1 - r1Fraction)) / 2;
57666 zoomAxRanges(linkedAxes, linkedR0Fraction, 1 - linkedR0Fraction, updates, []);
57667 }
57668}
57669
57670function dragAxList(axList, pix) {
57671 for(var i = 0; i < axList.length; i++) {
57672 var axi = axList[i];
57673 if(!axi.fixedrange) {
57674 if(axi.rangebreaks) {
57675 var p0 = 0;
57676 var p1 = axi._length;
57677 var d0 = axi.p2l(p0 + pix) - axi.p2l(p0);
57678 var d1 = axi.p2l(p1 + pix) - axi.p2l(p1);
57679 var delta = (d0 + d1) / 2;
57680
57681 axi.range = [
57682 axi.l2r(axi._rl[0] - delta),
57683 axi.l2r(axi._rl[1] - delta)
57684 ];
57685 } else {
57686 axi.range = [
57687 axi.l2r(axi._rl[0] - pix / axi._m),
57688 axi.l2r(axi._rl[1] - pix / axi._m)
57689 ];
57690 }
57691 }
57692 }
57693}
57694
57695// common transform for dragging one end of an axis
57696// d>0 is compressing scale (cursor is over the plot,
57697// the axis end should move with the cursor)
57698// d<0 is expanding (cursor is off the plot, axis end moves
57699// nonlinearly so you can expand far)
57700function dZoom(d) {
57701 return 1 - ((d >= 0) ? Math.min(d, 0.9) :
57702 1 / (1 / Math.max(d, -0.3) + 3.222));
57703}
57704
57705function getDragCursor(nsew, dragmode, isMainDrag) {
57706 if(!nsew) return 'pointer';
57707 if(nsew === 'nsew') {
57708 // in this case here, clear cursor and
57709 // use the cursor style set on <g .draglayer>
57710 if(isMainDrag) return '';
57711 if(dragmode === 'pan') return 'move';
57712 return 'crosshair';
57713 }
57714 return nsew.toLowerCase() + '-resize';
57715}
57716
57717function makeZoombox(zoomlayer, lum, xs, ys, path0) {
57718 return zoomlayer.append('path')
57719 .attr('class', 'zoombox')
57720 .style({
57721 'fill': lum > 0.2 ? 'rgba(0,0,0,0)' : 'rgba(255,255,255,0)',
57722 'stroke-width': 0
57723 })
57724 .attr('transform', 'translate(' + xs + ', ' + ys + ')')
57725 .attr('d', path0 + 'Z');
57726}
57727
57728function makeCorners(zoomlayer, xs, ys) {
57729 return zoomlayer.append('path')
57730 .attr('class', 'zoombox-corners')
57731 .style({
57732 fill: Color.background,
57733 stroke: Color.defaultLine,
57734 'stroke-width': 1,
57735 opacity: 0
57736 })
57737 .attr('transform', 'translate(' + xs + ', ' + ys + ')')
57738 .attr('d', 'M0,0Z');
57739}
57740
57741function updateZoombox(zb, corners, box, path0, dimmed, lum) {
57742 zb.attr('d',
57743 path0 + 'M' + (box.l) + ',' + (box.t) + 'v' + (box.h) +
57744 'h' + (box.w) + 'v-' + (box.h) + 'h-' + (box.w) + 'Z');
57745 transitionZoombox(zb, corners, dimmed, lum);
57746}
57747
57748function transitionZoombox(zb, corners, dimmed, lum) {
57749 if(!dimmed) {
57750 zb.transition()
57751 .style('fill', lum > 0.2 ? 'rgba(0,0,0,0.4)' :
57752 'rgba(255,255,255,0.3)')
57753 .duration(200);
57754 corners.transition()
57755 .style('opacity', 1)
57756 .duration(200);
57757 }
57758}
57759
57760function removeZoombox(gd) {
57761 d3.select(gd)
57762 .selectAll('.zoombox,.js-zoombox-backdrop,.js-zoombox-menu,.zoombox-corners')
57763 .remove();
57764}
57765
57766function showDoubleClickNotifier(gd) {
57767 if(SHOWZOOMOUTTIP && gd.data && gd._context.showTips) {
57768 Lib.notifier(Lib._(gd, 'Double-click to zoom back out'), 'long');
57769 SHOWZOOMOUTTIP = false;
57770 }
57771}
57772
57773function xCorners(box, y0) {
57774 return 'M' +
57775 (box.l - 0.5) + ',' + (y0 - MINZOOM - 0.5) +
57776 'h-3v' + (2 * MINZOOM + 1) + 'h3ZM' +
57777 (box.r + 0.5) + ',' + (y0 - MINZOOM - 0.5) +
57778 'h3v' + (2 * MINZOOM + 1) + 'h-3Z';
57779}
57780
57781function yCorners(box, x0) {
57782 return 'M' +
57783 (x0 - MINZOOM - 0.5) + ',' + (box.t - 0.5) +
57784 'v-3h' + (2 * MINZOOM + 1) + 'v3ZM' +
57785 (x0 - MINZOOM - 0.5) + ',' + (box.b + 0.5) +
57786 'v3h' + (2 * MINZOOM + 1) + 'v-3Z';
57787}
57788
57789function xyCorners(box) {
57790 var clen = Math.floor(Math.min(box.b - box.t, box.r - box.l, MINZOOM) / 2);
57791 return 'M' +
57792 (box.l - 3.5) + ',' + (box.t - 0.5 + clen) + 'h3v' + (-clen) +
57793 'h' + clen + 'v-3h-' + (clen + 3) + 'ZM' +
57794 (box.r + 3.5) + ',' + (box.t - 0.5 + clen) + 'h-3v' + (-clen) +
57795 'h' + (-clen) + 'v-3h' + (clen + 3) + 'ZM' +
57796 (box.r + 3.5) + ',' + (box.b + 0.5 - clen) + 'h-3v' + clen +
57797 'h' + (-clen) + 'v3h' + (clen + 3) + 'ZM' +
57798 (box.l - 3.5) + ',' + (box.b + 0.5 - clen) + 'h3v' + clen +
57799 'h' + clen + 'v3h-' + (clen + 3) + 'Z';
57800}
57801
57802function calcLinks(gd, groups, xaHash, yaHash) {
57803 var isSubplotConstrained = false;
57804 var xLinks = {};
57805 var yLinks = {};
57806 var xID, yID, xLinkID, yLinkID;
57807
57808 for(var i = 0; i < groups.length; i++) {
57809 var group = groups[i];
57810 // check if any of the x axes we're dragging is in this constraint group
57811 for(xID in xaHash) {
57812 if(group[xID]) {
57813 // put the rest of these axes into xLinks, if we're not already
57814 // dragging them, so we know to scale these axes automatically too
57815 // to match the changes in the dragged x axes
57816 for(xLinkID in group) {
57817 if(!(xLinkID.charAt(0) === 'x' ? xaHash : yaHash)[xLinkID]) {
57818 xLinks[xLinkID] = xID;
57819 }
57820 }
57821
57822 // check if the x and y axes of THIS drag are linked
57823 for(yID in yaHash) {
57824 if(group[yID]) isSubplotConstrained = true;
57825 }
57826 }
57827 }
57828
57829 // now check if any of the y axes we're dragging is in this constraint group
57830 // only look for outside links, as we've already checked for links within the dragger
57831 for(yID in yaHash) {
57832 if(group[yID]) {
57833 for(yLinkID in group) {
57834 if(!(yLinkID.charAt(0) === 'x' ? xaHash : yaHash)[yLinkID]) {
57835 yLinks[yLinkID] = yID;
57836 }
57837 }
57838 }
57839 }
57840 }
57841
57842 if(isSubplotConstrained) {
57843 // merge xLinks and yLinks if the subplot is constrained,
57844 // since we'll always apply both anyway and the two will contain
57845 // duplicates
57846 Lib.extendFlat(xLinks, yLinks);
57847 yLinks = {};
57848 }
57849
57850 var xaHashLinked = {};
57851 var xaxesLinked = [];
57852 for(xLinkID in xLinks) {
57853 var xa = getFromId(gd, xLinkID);
57854 xaxesLinked.push(xa);
57855 xaHashLinked[xa._id] = xa;
57856 }
57857
57858 var yaHashLinked = {};
57859 var yaxesLinked = [];
57860 for(yLinkID in yLinks) {
57861 var ya = getFromId(gd, yLinkID);
57862 yaxesLinked.push(ya);
57863 yaHashLinked[ya._id] = ya;
57864 }
57865
57866 return {
57867 xaHash: xaHashLinked,
57868 yaHash: yaHashLinked,
57869 xaxes: xaxesLinked,
57870 yaxes: yaxesLinked,
57871 xLinks: xLinks,
57872 yLinks: yLinks,
57873 isSubplotConstrained: isSubplotConstrained
57874 };
57875}
57876
57877// still seems to be some confusion about onwheel vs onmousewheel...
57878function attachWheelEventHandler(element, handler) {
57879 if(!supportsPassive) {
57880 if(element.onwheel !== undefined) element.onwheel = handler;
57881 else if(element.onmousewheel !== undefined) element.onmousewheel = handler;
57882 else if(!element.isAddedWheelEvent) {
57883 element.isAddedWheelEvent = true;
57884 element.addEventListener('wheel', handler, {passive: false});
57885 }
57886 } else {
57887 var wheelEventName = element.onwheel !== undefined ? 'wheel' : 'mousewheel';
57888
57889 if(element._onwheel) {
57890 element.removeEventListener(wheelEventName, element._onwheel);
57891 }
57892 element._onwheel = handler;
57893
57894 element.addEventListener(wheelEventName, handler, {passive: false});
57895 }
57896}
57897
57898function hashValues(hash) {
57899 var out = [];
57900 for(var k in hash) out.push(hash[k]);
57901 return out;
57902}
57903
57904module.exports = {
57905 makeDragBox: makeDragBox,
57906
57907 makeDragger: makeDragger,
57908 makeRectDragger: makeRectDragger,
57909 makeZoombox: makeZoombox,
57910 makeCorners: makeCorners,
57911
57912 updateZoombox: updateZoombox,
57913 xyCorners: xyCorners,
57914 transitionZoombox: transitionZoombox,
57915 removeZoombox: removeZoombox,
57916 showDoubleClickNotifier: showDoubleClickNotifier,
57917
57918 attachWheelEventHandler: attachWheelEventHandler
57919};
57920
57921},{"../../components/color":52,"../../components/dragelement":71,"../../components/dragelement/helpers":70,"../../components/drawing":74,"../../components/fx":92,"../../constants/alignment":154,"../../lib":178,"../../lib/clear_gl_canvases":167,"../../lib/setcursor":197,"../../lib/svg_text_utils":199,"../../plot_api/subroutines":213,"../../registry":269,"../plots":256,"./axes":222,"./axis_ids":225,"./constants":228,"./scale_zoom":240,"./select":241,"d3":16,"has-passive-events":21,"tinycolor2":35}],231:[function(_dereq_,module,exports){
57922/**
57923* Copyright 2012-2020, Plotly, Inc.
57924* All rights reserved.
57925*
57926* This source code is licensed under the MIT license found in the
57927* LICENSE file in the root directory of this source tree.
57928*/
57929
57930
57931'use strict';
57932
57933var d3 = _dereq_('d3');
57934
57935var Fx = _dereq_('../../components/fx');
57936var dragElement = _dereq_('../../components/dragelement');
57937var setCursor = _dereq_('../../lib/setcursor');
57938
57939var makeDragBox = _dereq_('./dragbox').makeDragBox;
57940var DRAGGERSIZE = _dereq_('./constants').DRAGGERSIZE;
57941
57942exports.initInteractions = function initInteractions(gd) {
57943 var fullLayout = gd._fullLayout;
57944
57945 if(gd._context.staticPlot) {
57946 // this sweeps up more than just cartesian drag elements...
57947 d3.select(gd).selectAll('.drag').remove();
57948 return;
57949 }
57950
57951 if(!fullLayout._has('cartesian') && !fullLayout._has('splom')) return;
57952
57953 var subplots = Object.keys(fullLayout._plots || {}).sort(function(a, b) {
57954 // sort overlays last, then by x axis number, then y axis number
57955 if((fullLayout._plots[a].mainplot && true) ===
57956 (fullLayout._plots[b].mainplot && true)) {
57957 var aParts = a.split('y');
57958 var bParts = b.split('y');
57959 return (aParts[0] === bParts[0]) ?
57960 (Number(aParts[1] || 1) - Number(bParts[1] || 1)) :
57961 (Number(aParts[0] || 1) - Number(bParts[0] || 1));
57962 }
57963 return fullLayout._plots[a].mainplot ? 1 : -1;
57964 });
57965
57966 subplots.forEach(function(subplot) {
57967 var plotinfo = fullLayout._plots[subplot];
57968 var xa = plotinfo.xaxis;
57969 var ya = plotinfo.yaxis;
57970
57971 // main and corner draggers need not be repeated for
57972 // overlaid subplots - these draggers drag them all
57973 if(!plotinfo.mainplot) {
57974 // main dragger goes over the grids and data, so we use its
57975 // mousemove events for all data hover effects
57976 var maindrag = makeDragBox(gd, plotinfo, xa._offset, ya._offset,
57977 xa._length, ya._length, 'ns', 'ew');
57978
57979 maindrag.onmousemove = function(evt) {
57980 // This is on `gd._fullLayout`, *not* fullLayout because the reference
57981 // changes by the time this is called again.
57982 gd._fullLayout._rehover = function() {
57983 if((gd._fullLayout._hoversubplot === subplot) && gd._fullLayout._plots[subplot]) {
57984 Fx.hover(gd, evt, subplot);
57985 }
57986 };
57987
57988 Fx.hover(gd, evt, subplot);
57989
57990 // Note that we have *not* used the cached fullLayout variable here
57991 // since that may be outdated when this is called as a callback later on
57992 gd._fullLayout._lasthover = maindrag;
57993 gd._fullLayout._hoversubplot = subplot;
57994 };
57995
57996 /*
57997 * IMPORTANT:
57998 * We must check for the presence of the drag cover here.
57999 * If we don't, a 'mouseout' event is triggered on the
58000 * maindrag before each 'click' event, which has the effect
58001 * of clearing the hoverdata; thus, cancelling the click event.
58002 */
58003 maindrag.onmouseout = function(evt) {
58004 if(gd._dragging) return;
58005
58006 // When the mouse leaves this maindrag, unset the hovered subplot.
58007 // This may cause problems if it leaves the subplot directly *onto*
58008 // another subplot, but that's a tiny corner case at the moment.
58009 gd._fullLayout._hoversubplot = null;
58010
58011 dragElement.unhover(gd, evt);
58012 };
58013
58014 // corner draggers
58015 if(gd._context.showAxisDragHandles) {
58016 makeDragBox(gd, plotinfo, xa._offset - DRAGGERSIZE, ya._offset - DRAGGERSIZE,
58017 DRAGGERSIZE, DRAGGERSIZE, 'n', 'w');
58018 makeDragBox(gd, plotinfo, xa._offset + xa._length, ya._offset - DRAGGERSIZE,
58019 DRAGGERSIZE, DRAGGERSIZE, 'n', 'e');
58020 makeDragBox(gd, plotinfo, xa._offset - DRAGGERSIZE, ya._offset + ya._length,
58021 DRAGGERSIZE, DRAGGERSIZE, 's', 'w');
58022 makeDragBox(gd, plotinfo, xa._offset + xa._length, ya._offset + ya._length,
58023 DRAGGERSIZE, DRAGGERSIZE, 's', 'e');
58024 }
58025 }
58026 if(gd._context.showAxisDragHandles) {
58027 // x axis draggers - if you have overlaid plots,
58028 // these drag each axis separately
58029 if(subplot === xa._mainSubplot) {
58030 // the y position of the main x axis line
58031 var y0 = xa._mainLinePosition;
58032 if(xa.side === 'top') y0 -= DRAGGERSIZE;
58033 makeDragBox(gd, plotinfo, xa._offset + xa._length * 0.1, y0,
58034 xa._length * 0.8, DRAGGERSIZE, '', 'ew');
58035 makeDragBox(gd, plotinfo, xa._offset, y0,
58036 xa._length * 0.1, DRAGGERSIZE, '', 'w');
58037 makeDragBox(gd, plotinfo, xa._offset + xa._length * 0.9, y0,
58038 xa._length * 0.1, DRAGGERSIZE, '', 'e');
58039 }
58040 // y axis draggers
58041 if(subplot === ya._mainSubplot) {
58042 // the x position of the main y axis line
58043 var x0 = ya._mainLinePosition;
58044 if(ya.side !== 'right') x0 -= DRAGGERSIZE;
58045 makeDragBox(gd, plotinfo, x0, ya._offset + ya._length * 0.1,
58046 DRAGGERSIZE, ya._length * 0.8, 'ns', '');
58047 makeDragBox(gd, plotinfo, x0, ya._offset + ya._length * 0.9,
58048 DRAGGERSIZE, ya._length * 0.1, 's', '');
58049 makeDragBox(gd, plotinfo, x0, ya._offset,
58050 DRAGGERSIZE, ya._length * 0.1, 'n', '');
58051 }
58052 }
58053 });
58054
58055 // In case you mousemove over some hovertext, send it to Fx.hover too
58056 // we do this so that we can put the hover text in front of everything,
58057 // but still be able to interact with everything as if it isn't there
58058 var hoverLayer = fullLayout._hoverlayer.node();
58059
58060 hoverLayer.onmousemove = function(evt) {
58061 evt.target = gd._fullLayout._lasthover;
58062 Fx.hover(gd, evt, fullLayout._hoversubplot);
58063 };
58064
58065 hoverLayer.onclick = function(evt) {
58066 evt.target = gd._fullLayout._lasthover;
58067 Fx.click(gd, evt);
58068 };
58069
58070 // also delegate mousedowns... TODO: does this actually work?
58071 hoverLayer.onmousedown = function(evt) {
58072 gd._fullLayout._lasthover.onmousedown(evt);
58073 };
58074
58075 exports.updateFx(gd);
58076};
58077
58078// Minimal set of update needed on 'modebar' edits.
58079// We only need to update the <g .draglayer> cursor style.
58080//
58081// Note that changing the axis configuration and/or the fixedrange attribute
58082// should trigger a full initInteractions.
58083exports.updateFx = function(gd) {
58084 var fullLayout = gd._fullLayout;
58085 var cursor = fullLayout.dragmode === 'pan' ? 'move' : 'crosshair';
58086 setCursor(fullLayout._draggers, cursor);
58087};
58088
58089},{"../../components/dragelement":71,"../../components/fx":92,"../../lib/setcursor":197,"./constants":228,"./dragbox":230,"d3":16}],232:[function(_dereq_,module,exports){
58090/**
58091* Copyright 2012-2020, Plotly, Inc.
58092* All rights reserved.
58093*
58094* This source code is licensed under the MIT license found in the
58095* LICENSE file in the root directory of this source tree.
58096*/
58097
58098
58099'use strict';
58100
58101function clearOutlineControllers(gd) {
58102 var zoomLayer = gd._fullLayout._zoomlayer;
58103 if(zoomLayer) {
58104 zoomLayer.selectAll('.outline-controllers').remove();
58105 }
58106}
58107
58108function clearSelect(gd) {
58109 var zoomLayer = gd._fullLayout._zoomlayer;
58110 if(zoomLayer) {
58111 // until we get around to persistent selections, remove the outline
58112 // here. The selection itself will be removed when the plot redraws
58113 // at the end.
58114 zoomLayer.selectAll('.select-outline').remove();
58115 }
58116
58117 gd._fullLayout._drawing = false;
58118}
58119
58120module.exports = {
58121 clearOutlineControllers: clearOutlineControllers,
58122 clearSelect: clearSelect
58123};
58124
58125},{}],233:[function(_dereq_,module,exports){
58126/**
58127* Copyright 2012-2020, Plotly, Inc.
58128* All rights reserved.
58129*
58130* This source code is licensed under the MIT license found in the
58131* LICENSE file in the root directory of this source tree.
58132*/
58133
58134
58135'use strict';
58136
58137// in v2 (once log ranges are fixed),
58138// we'll be able to p2r here for all axis types
58139function p2r(ax, v) {
58140 switch(ax.type) {
58141 case 'log':
58142 return ax.p2d(v);
58143 case 'date':
58144 return ax.p2r(v, 0, ax.calendar);
58145 default:
58146 return ax.p2r(v);
58147 }
58148}
58149
58150function r2p(ax, v) {
58151 switch(ax.type) {
58152 case 'log':
58153 return ax.d2p(v);
58154 case 'date':
58155 return ax.r2p(v, 0, ax.calendar);
58156 default:
58157 return ax.r2p(v);
58158 }
58159}
58160
58161function axValue(ax) {
58162 var index = (ax._id.charAt(0) === 'y') ? 1 : 0;
58163 return function(v) { return p2r(ax, v[index]); };
58164}
58165
58166function getTransform(plotinfo) {
58167 return 'translate(' +
58168 plotinfo.xaxis._offset + ',' +
58169 plotinfo.yaxis._offset + ')';
58170}
58171
58172module.exports = {
58173 p2r: p2r,
58174 r2p: r2p,
58175 axValue: axValue,
58176 getTransform: getTransform
58177};
58178
58179},{}],234:[function(_dereq_,module,exports){
58180/**
58181* Copyright 2012-2020, Plotly, Inc.
58182* All rights reserved.
58183*
58184* This source code is licensed under the MIT license found in the
58185* LICENSE file in the root directory of this source tree.
58186*/
58187
58188
58189'use strict';
58190
58191var Registry = _dereq_('../../registry');
58192var Lib = _dereq_('../../lib');
58193
58194/**
58195 * Factory function for checking component arrays for subplot references.
58196 *
58197 * @param {string} containerArrayName: the top-level array in gd.layout to check
58198 * If an item in this container is found that references a cartesian x and/or y axis,
58199 * ensure cartesian is marked as a base plot module and record the axes (and subplot
58200 * if both refs are axes) in gd._fullLayout
58201 *
58202 * @return {function}: with args layoutIn (gd.layout) and layoutOut (gd._fullLayout)
58203 * as expected of a component includeBasePlot method
58204 */
58205module.exports = function makeIncludeComponents(containerArrayName) {
58206 return function includeComponents(layoutIn, layoutOut) {
58207 var array = layoutIn[containerArrayName];
58208 if(!Array.isArray(array)) return;
58209
58210 var Cartesian = Registry.subplotsRegistry.cartesian;
58211 var idRegex = Cartesian.idRegex;
58212 var subplots = layoutOut._subplots;
58213 var xaList = subplots.xaxis;
58214 var yaList = subplots.yaxis;
58215 var cartesianList = subplots.cartesian;
58216 var hasCartesianOrGL2D = layoutOut._has('cartesian') || layoutOut._has('gl2d');
58217
58218 for(var i = 0; i < array.length; i++) {
58219 var itemi = array[i];
58220 if(!Lib.isPlainObject(itemi)) continue;
58221
58222 var xref = itemi.xref;
58223 var yref = itemi.yref;
58224
58225 var hasXref = idRegex.x.test(xref);
58226 var hasYref = idRegex.y.test(yref);
58227 if(hasXref || hasYref) {
58228 if(!hasCartesianOrGL2D) Lib.pushUnique(layoutOut._basePlotModules, Cartesian);
58229
58230 var newAxis = false;
58231 if(hasXref && xaList.indexOf(xref) === -1) {
58232 xaList.push(xref);
58233 newAxis = true;
58234 }
58235 if(hasYref && yaList.indexOf(yref) === -1) {
58236 yaList.push(yref);
58237 newAxis = true;
58238 }
58239
58240 /*
58241 * Notice the logic here: only add a subplot for a component if
58242 * it's referencing both x and y axes AND it's creating a new axis
58243 * so for example if your plot already has xy and x2y2, an annotation
58244 * on x2y or xy2 will not create a new subplot.
58245 */
58246 if(newAxis && hasXref && hasYref) {
58247 cartesianList.push(xref + yref);
58248 }
58249 }
58250 }
58251 };
58252};
58253
58254},{"../../lib":178,"../../registry":269}],235:[function(_dereq_,module,exports){
58255/**
58256* Copyright 2012-2020, Plotly, Inc.
58257* All rights reserved.
58258*
58259* This source code is licensed under the MIT license found in the
58260* LICENSE file in the root directory of this source tree.
58261*/
58262
58263
58264'use strict';
58265
58266var d3 = _dereq_('d3');
58267
58268var Registry = _dereq_('../../registry');
58269var Lib = _dereq_('../../lib');
58270var Plots = _dereq_('../plots');
58271var Drawing = _dereq_('../../components/drawing');
58272
58273var getModuleCalcData = _dereq_('../get_data').getModuleCalcData;
58274var axisIds = _dereq_('./axis_ids');
58275var constants = _dereq_('./constants');
58276var xmlnsNamespaces = _dereq_('../../constants/xmlns_namespaces');
58277
58278var ensureSingle = Lib.ensureSingle;
58279
58280function ensureSingleAndAddDatum(parent, nodeType, className) {
58281 return Lib.ensureSingle(parent, nodeType, className, function(s) {
58282 s.datum(className);
58283 });
58284}
58285
58286exports.name = 'cartesian';
58287
58288exports.attr = ['xaxis', 'yaxis'];
58289
58290exports.idRoot = ['x', 'y'];
58291
58292exports.idRegex = constants.idRegex;
58293
58294exports.attrRegex = constants.attrRegex;
58295
58296exports.attributes = _dereq_('./attributes');
58297
58298exports.layoutAttributes = _dereq_('./layout_attributes');
58299
58300exports.supplyLayoutDefaults = _dereq_('./layout_defaults');
58301
58302exports.transitionAxes = _dereq_('./transition_axes');
58303
58304exports.finalizeSubplots = function(layoutIn, layoutOut) {
58305 var subplots = layoutOut._subplots;
58306 var xList = subplots.xaxis;
58307 var yList = subplots.yaxis;
58308 var spSVG = subplots.cartesian;
58309 var spAll = spSVG.concat(subplots.gl2d || []);
58310 var allX = {};
58311 var allY = {};
58312 var i, xi, yi;
58313
58314 for(i = 0; i < spAll.length; i++) {
58315 var parts = spAll[i].split('y');
58316 allX[parts[0]] = 1;
58317 allY['y' + parts[1]] = 1;
58318 }
58319
58320 // check for x axes with no subplot, and make one from the anchor of that x axis
58321 for(i = 0; i < xList.length; i++) {
58322 xi = xList[i];
58323 if(!allX[xi]) {
58324 yi = (layoutIn[axisIds.id2name(xi)] || {}).anchor;
58325 if(!constants.idRegex.y.test(yi)) yi = 'y';
58326 spSVG.push(xi + yi);
58327 spAll.push(xi + yi);
58328
58329 if(!allY[yi]) {
58330 allY[yi] = 1;
58331 Lib.pushUnique(yList, yi);
58332 }
58333 }
58334 }
58335
58336 // same for y axes with no subplot
58337 for(i = 0; i < yList.length; i++) {
58338 yi = yList[i];
58339 if(!allY[yi]) {
58340 xi = (layoutIn[axisIds.id2name(yi)] || {}).anchor;
58341 if(!constants.idRegex.x.test(xi)) xi = 'x';
58342 spSVG.push(xi + yi);
58343 spAll.push(xi + yi);
58344
58345 if(!allX[xi]) {
58346 allX[xi] = 1;
58347 Lib.pushUnique(xList, xi);
58348 }
58349 }
58350 }
58351
58352 // finally, if we've gotten here we're supposed to show cartesian...
58353 // so if there are NO subplots at all, make one from the first
58354 // x & y axes in the input layout
58355 if(!spAll.length) {
58356 xi = '';
58357 yi = '';
58358 for(var ki in layoutIn) {
58359 if(constants.attrRegex.test(ki)) {
58360 var axLetter = ki.charAt(0);
58361 if(axLetter === 'x') {
58362 if(!xi || (+ki.substr(5) < +xi.substr(5))) {
58363 xi = ki;
58364 }
58365 } else if(!yi || (+ki.substr(5) < +yi.substr(5))) {
58366 yi = ki;
58367 }
58368 }
58369 }
58370 xi = xi ? axisIds.name2id(xi) : 'x';
58371 yi = yi ? axisIds.name2id(yi) : 'y';
58372 xList.push(xi);
58373 yList.push(yi);
58374 spSVG.push(xi + yi);
58375 }
58376};
58377
58378/**
58379 * Cartesian.plot
58380 *
58381 * @param {DOM div | object} gd
58382 * @param {array (optional)} traces
58383 * array of traces indices to plot
58384 * if undefined, plots all cartesian traces,
58385 * @param {object} (optional) transitionOpts
58386 * transition option object
58387 * @param {function} (optional) makeOnCompleteCallback
58388 * transition make callback function from Plots.transition
58389 */
58390exports.plot = function(gd, traces, transitionOpts, makeOnCompleteCallback) {
58391 var fullLayout = gd._fullLayout;
58392 var subplots = fullLayout._subplots.cartesian;
58393 var calcdata = gd.calcdata;
58394 var i;
58395
58396 if(!Array.isArray(traces)) {
58397 // If traces is not provided, then it's a complete replot and missing
58398 // traces are removed
58399 traces = [];
58400 for(i = 0; i < calcdata.length; i++) traces.push(i);
58401 }
58402
58403 for(i = 0; i < subplots.length; i++) {
58404 var subplot = subplots[i];
58405 var subplotInfo = fullLayout._plots[subplot];
58406
58407 // Get all calcdata for this subplot:
58408 var cdSubplot = [];
58409 var pcd;
58410
58411 for(var j = 0; j < calcdata.length; j++) {
58412 var cd = calcdata[j];
58413 var trace = cd[0].trace;
58414
58415 // Skip trace if whitelist provided and it's not whitelisted:
58416 // if (Array.isArray(traces) && traces.indexOf(i) === -1) continue;
58417 if(trace.xaxis + trace.yaxis === subplot) {
58418 // XXX: Should trace carpet dependencies. Only replot all carpet plots if the carpet
58419 // axis has actually changed:
58420 //
58421 // If this trace is specifically requested, add it to the list:
58422 if(traces.indexOf(trace.index) !== -1 || trace.carpet) {
58423 // Okay, so example: traces 0, 1, and 2 have fill = tonext. You animate
58424 // traces 0 and 2. Trace 1 also needs to be updated, otherwise its fill
58425 // is outdated. So this retroactively adds the previous trace if the
58426 // traces are interdependent.
58427 if(
58428 pcd &&
58429 pcd[0].trace.xaxis + pcd[0].trace.yaxis === subplot &&
58430 ['tonextx', 'tonexty', 'tonext'].indexOf(trace.fill) !== -1 &&
58431 cdSubplot.indexOf(pcd) === -1
58432 ) {
58433 cdSubplot.push(pcd);
58434 }
58435
58436 cdSubplot.push(cd);
58437 }
58438
58439 // Track the previous trace on this subplot for the retroactive-add step
58440 // above:
58441 pcd = cd;
58442 }
58443 }
58444
58445 plotOne(gd, subplotInfo, cdSubplot, transitionOpts, makeOnCompleteCallback);
58446 }
58447};
58448
58449function plotOne(gd, plotinfo, cdSubplot, transitionOpts, makeOnCompleteCallback) {
58450 var traceLayerClasses = constants.traceLayerClasses;
58451 var fullLayout = gd._fullLayout;
58452 var modules = fullLayout._modules;
58453 var _module, cdModuleAndOthers, cdModule;
58454
58455 var layerData = [];
58456 var zoomScaleQueryParts = [];
58457
58458 for(var i = 0; i < modules.length; i++) {
58459 _module = modules[i];
58460 var name = _module.name;
58461 var categories = Registry.modules[name].categories;
58462
58463 if(categories.svg) {
58464 var className = (_module.layerName || name + 'layer');
58465 var plotMethod = _module.plot;
58466
58467 // plot all visible traces of this type on this subplot at once
58468 cdModuleAndOthers = getModuleCalcData(cdSubplot, plotMethod);
58469 cdModule = cdModuleAndOthers[0];
58470 // don't need to search the found traces again - in fact we need to NOT
58471 // so that if two modules share the same plotter we don't double-plot
58472 cdSubplot = cdModuleAndOthers[1];
58473
58474 if(cdModule.length) {
58475 layerData.push({
58476 i: traceLayerClasses.indexOf(className),
58477 className: className,
58478 plotMethod: plotMethod,
58479 cdModule: cdModule
58480 });
58481 }
58482
58483 if(categories.zoomScale) {
58484 zoomScaleQueryParts.push('.' + className);
58485 }
58486 }
58487 }
58488
58489 layerData.sort(function(a, b) { return a.i - b.i; });
58490
58491 var layers = plotinfo.plot.selectAll('g.mlayer')
58492 .data(layerData, function(d) { return d.className; });
58493
58494 layers.enter().append('g')
58495 .attr('class', function(d) { return d.className; })
58496 .classed('mlayer', true)
58497 .classed('rangeplot', plotinfo.isRangePlot);
58498
58499 layers.exit().remove();
58500
58501 layers.order();
58502
58503 layers.each(function(d) {
58504 var sel = d3.select(this);
58505 var className = d.className;
58506
58507 d.plotMethod(
58508 gd, plotinfo, d.cdModule, sel,
58509 transitionOpts, makeOnCompleteCallback
58510 );
58511
58512 // layers that allow `cliponaxis: false`
58513 if(constants.clipOnAxisFalseQuery.indexOf('.' + className) === -1) {
58514 Drawing.setClipUrl(sel, plotinfo.layerClipId, gd);
58515 }
58516 });
58517
58518 // call Scattergl.plot separately
58519 if(fullLayout._has('scattergl')) {
58520 _module = Registry.getModule('scattergl');
58521 cdModule = getModuleCalcData(cdSubplot, _module)[0];
58522 _module.plot(gd, plotinfo, cdModule);
58523 }
58524
58525 // stash "hot" selections for faster interaction on drag and scroll
58526 if(!gd._context.staticPlot) {
58527 if(plotinfo._hasClipOnAxisFalse) {
58528 plotinfo.clipOnAxisFalseTraces = plotinfo.plot
58529 .selectAll(constants.clipOnAxisFalseQuery.join(','))
58530 .selectAll('.trace');
58531 }
58532
58533 if(zoomScaleQueryParts.length) {
58534 var traces = plotinfo.plot
58535 .selectAll(zoomScaleQueryParts.join(','))
58536 .selectAll('.trace');
58537
58538 plotinfo.zoomScalePts = traces.selectAll('path.point');
58539 plotinfo.zoomScaleTxt = traces.selectAll('.textpoint');
58540 }
58541 }
58542}
58543
58544exports.clean = function(newFullData, newFullLayout, oldFullData, oldFullLayout) {
58545 var oldPlots = oldFullLayout._plots || {};
58546 var newPlots = newFullLayout._plots || {};
58547 var oldSubplotList = oldFullLayout._subplots || {};
58548 var plotinfo;
58549 var i, k;
58550
58551 // when going from a large splom graph to something else,
58552 // we need to clear <g subplot> so that the new cartesian subplot
58553 // can have the correct layer ordering
58554 if(oldFullLayout._hasOnlyLargeSploms && !newFullLayout._hasOnlyLargeSploms) {
58555 for(k in oldPlots) {
58556 plotinfo = oldPlots[k];
58557 if(plotinfo.plotgroup) plotinfo.plotgroup.remove();
58558 }
58559 }
58560
58561 var hadGl = (oldFullLayout._has && oldFullLayout._has('gl'));
58562 var hasGl = (newFullLayout._has && newFullLayout._has('gl'));
58563
58564 if(hadGl && !hasGl) {
58565 for(k in oldPlots) {
58566 plotinfo = oldPlots[k];
58567 if(plotinfo._scene) plotinfo._scene.destroy();
58568 }
58569 }
58570
58571 // delete any titles we don't need anymore
58572 // check if axis list has changed, and if so clear old titles
58573 if(oldSubplotList.xaxis && oldSubplotList.yaxis) {
58574 var oldAxIDs = axisIds.listIds({_fullLayout: oldFullLayout});
58575 for(i = 0; i < oldAxIDs.length; i++) {
58576 var oldAxId = oldAxIDs[i];
58577 if(!newFullLayout[axisIds.id2name(oldAxId)]) {
58578 oldFullLayout._infolayer.selectAll('.g-' + oldAxId + 'title').remove();
58579 }
58580 }
58581 }
58582
58583 var hadCartesian = (oldFullLayout._has && oldFullLayout._has('cartesian'));
58584 var hasCartesian = (newFullLayout._has && newFullLayout._has('cartesian'));
58585
58586 if(hadCartesian && !hasCartesian) {
58587 // if we've gotten rid of all cartesian traces, remove all the subplot svg items
58588
58589 purgeSubplotLayers(oldFullLayout._cartesianlayer.selectAll('.subplot'), oldFullLayout);
58590 oldFullLayout._defs.selectAll('.axesclip').remove();
58591 delete oldFullLayout._axisConstraintGroups;
58592 } else if(oldSubplotList.cartesian) {
58593 // otherwise look for subplots we need to remove
58594
58595 for(i = 0; i < oldSubplotList.cartesian.length; i++) {
58596 var oldSubplotId = oldSubplotList.cartesian[i];
58597 if(!newPlots[oldSubplotId]) {
58598 var selector = '.' + oldSubplotId + ',.' + oldSubplotId + '-x,.' + oldSubplotId + '-y';
58599 oldFullLayout._cartesianlayer.selectAll(selector).remove();
58600 removeSubplotExtras(oldSubplotId, oldFullLayout);
58601 }
58602 }
58603 }
58604};
58605
58606exports.drawFramework = function(gd) {
58607 var fullLayout = gd._fullLayout;
58608 var subplotData = makeSubplotData(gd);
58609
58610 var subplotLayers = fullLayout._cartesianlayer.selectAll('.subplot')
58611 .data(subplotData, String);
58612
58613 subplotLayers.enter().append('g')
58614 .attr('class', function(d) { return 'subplot ' + d[0]; });
58615
58616 subplotLayers.order();
58617
58618 subplotLayers.exit()
58619 .call(purgeSubplotLayers, fullLayout);
58620
58621 subplotLayers.each(function(d) {
58622 var id = d[0];
58623 var plotinfo = fullLayout._plots[id];
58624
58625 plotinfo.plotgroup = d3.select(this);
58626 makeSubplotLayer(gd, plotinfo);
58627
58628 // make separate drag layers for each subplot,
58629 // but append them to paper rather than the plot groups,
58630 // so they end up on top of the rest
58631 plotinfo.draglayer = ensureSingle(fullLayout._draggers, 'g', id);
58632 });
58633};
58634
58635exports.rangePlot = function(gd, plotinfo, cdSubplot) {
58636 makeSubplotLayer(gd, plotinfo);
58637 plotOne(gd, plotinfo, cdSubplot);
58638 Plots.style(gd);
58639};
58640
58641function makeSubplotData(gd) {
58642 var fullLayout = gd._fullLayout;
58643 var ids = fullLayout._subplots.cartesian;
58644 var len = ids.length;
58645 var i, j, id, plotinfo, xa, ya;
58646
58647 // split 'regular' and 'overlaying' subplots
58648 var regulars = [];
58649 var overlays = [];
58650
58651 for(i = 0; i < len; i++) {
58652 id = ids[i];
58653 plotinfo = fullLayout._plots[id];
58654 xa = plotinfo.xaxis;
58655 ya = plotinfo.yaxis;
58656
58657 var xa2 = xa._mainAxis;
58658 var ya2 = ya._mainAxis;
58659 var mainplot = xa2._id + ya2._id;
58660 var mainplotinfo = fullLayout._plots[mainplot];
58661 plotinfo.overlays = [];
58662
58663 if(mainplot !== id && mainplotinfo) {
58664 plotinfo.mainplot = mainplot;
58665 plotinfo.mainplotinfo = mainplotinfo;
58666 overlays.push(id);
58667 } else {
58668 plotinfo.mainplot = undefined;
58669 plotinfo.mainPlotinfo = undefined;
58670 regulars.push(id);
58671 }
58672 }
58673
58674 // fill in list of overlaying subplots in 'main plot'
58675 for(i = 0; i < overlays.length; i++) {
58676 id = overlays[i];
58677 plotinfo = fullLayout._plots[id];
58678 plotinfo.mainplotinfo.overlays.push(plotinfo);
58679 }
58680
58681 // put 'regular' subplot data before 'overlaying'
58682 var subplotIds = regulars.concat(overlays);
58683 var subplotData = new Array(len);
58684
58685 for(i = 0; i < len; i++) {
58686 id = subplotIds[i];
58687 plotinfo = fullLayout._plots[id];
58688 xa = plotinfo.xaxis;
58689 ya = plotinfo.yaxis;
58690
58691 // use info about axis layer and overlaying pattern
58692 // to clean what need to be cleaned up in exit selection
58693 var d = [id, xa.layer, ya.layer, xa.overlaying || '', ya.overlaying || ''];
58694 for(j = 0; j < plotinfo.overlays.length; j++) {
58695 d.push(plotinfo.overlays[j].id);
58696 }
58697 subplotData[i] = d;
58698 }
58699
58700 return subplotData;
58701}
58702
58703function makeSubplotLayer(gd, plotinfo) {
58704 var plotgroup = plotinfo.plotgroup;
58705 var id = plotinfo.id;
58706 var xLayer = constants.layerValue2layerClass[plotinfo.xaxis.layer];
58707 var yLayer = constants.layerValue2layerClass[plotinfo.yaxis.layer];
58708 var hasOnlyLargeSploms = gd._fullLayout._hasOnlyLargeSploms;
58709
58710 if(!plotinfo.mainplot) {
58711 if(hasOnlyLargeSploms) {
58712 // TODO could do even better
58713 // - we don't need plot (but we would have to mock it in lsInner
58714 // and other places
58715 // - we don't (x|y)lines and (x|y)axislayer for most subplots
58716 // usually just the bottom x and left y axes.
58717 plotinfo.xlines = ensureSingle(plotgroup, 'path', 'xlines-above');
58718 plotinfo.ylines = ensureSingle(plotgroup, 'path', 'ylines-above');
58719 plotinfo.xaxislayer = ensureSingle(plotgroup, 'g', 'xaxislayer-above');
58720 plotinfo.yaxislayer = ensureSingle(plotgroup, 'g', 'yaxislayer-above');
58721 } else {
58722 var backLayer = ensureSingle(plotgroup, 'g', 'layer-subplot');
58723 plotinfo.shapelayer = ensureSingle(backLayer, 'g', 'shapelayer');
58724 plotinfo.imagelayer = ensureSingle(backLayer, 'g', 'imagelayer');
58725
58726 plotinfo.gridlayer = ensureSingle(plotgroup, 'g', 'gridlayer');
58727 plotinfo.zerolinelayer = ensureSingle(plotgroup, 'g', 'zerolinelayer');
58728
58729 ensureSingle(plotgroup, 'path', 'xlines-below');
58730 ensureSingle(plotgroup, 'path', 'ylines-below');
58731 plotinfo.overlinesBelow = ensureSingle(plotgroup, 'g', 'overlines-below');
58732
58733 ensureSingle(plotgroup, 'g', 'xaxislayer-below');
58734 ensureSingle(plotgroup, 'g', 'yaxislayer-below');
58735 plotinfo.overaxesBelow = ensureSingle(plotgroup, 'g', 'overaxes-below');
58736
58737 plotinfo.plot = ensureSingle(plotgroup, 'g', 'plot');
58738 plotinfo.overplot = ensureSingle(plotgroup, 'g', 'overplot');
58739
58740 plotinfo.xlines = ensureSingle(plotgroup, 'path', 'xlines-above');
58741 plotinfo.ylines = ensureSingle(plotgroup, 'path', 'ylines-above');
58742 plotinfo.overlinesAbove = ensureSingle(plotgroup, 'g', 'overlines-above');
58743
58744 ensureSingle(plotgroup, 'g', 'xaxislayer-above');
58745 ensureSingle(plotgroup, 'g', 'yaxislayer-above');
58746 plotinfo.overaxesAbove = ensureSingle(plotgroup, 'g', 'overaxes-above');
58747
58748 // set refs to correct layers as determined by 'axis.layer'
58749 plotinfo.xlines = plotgroup.select('.xlines-' + xLayer);
58750 plotinfo.ylines = plotgroup.select('.ylines-' + yLayer);
58751 plotinfo.xaxislayer = plotgroup.select('.xaxislayer-' + xLayer);
58752 plotinfo.yaxislayer = plotgroup.select('.yaxislayer-' + yLayer);
58753 }
58754 } else {
58755 var mainplotinfo = plotinfo.mainplotinfo;
58756 var mainplotgroup = mainplotinfo.plotgroup;
58757 var xId = id + '-x';
58758 var yId = id + '-y';
58759
58760 // now make the components of overlaid subplots
58761 // overlays don't have backgrounds, and append all
58762 // their other components to the corresponding
58763 // extra groups of their main plots.
58764
58765 plotinfo.gridlayer = mainplotinfo.gridlayer;
58766 plotinfo.zerolinelayer = mainplotinfo.zerolinelayer;
58767
58768 ensureSingle(mainplotinfo.overlinesBelow, 'path', xId);
58769 ensureSingle(mainplotinfo.overlinesBelow, 'path', yId);
58770 ensureSingle(mainplotinfo.overaxesBelow, 'g', xId);
58771 ensureSingle(mainplotinfo.overaxesBelow, 'g', yId);
58772
58773 plotinfo.plot = ensureSingle(mainplotinfo.overplot, 'g', id);
58774
58775 ensureSingle(mainplotinfo.overlinesAbove, 'path', xId);
58776 ensureSingle(mainplotinfo.overlinesAbove, 'path', yId);
58777 ensureSingle(mainplotinfo.overaxesAbove, 'g', xId);
58778 ensureSingle(mainplotinfo.overaxesAbove, 'g', yId);
58779
58780 // set refs to correct layers as determined by 'abovetraces'
58781 plotinfo.xlines = mainplotgroup.select('.overlines-' + xLayer).select('.' + xId);
58782 plotinfo.ylines = mainplotgroup.select('.overlines-' + yLayer).select('.' + yId);
58783 plotinfo.xaxislayer = mainplotgroup.select('.overaxes-' + xLayer).select('.' + xId);
58784 plotinfo.yaxislayer = mainplotgroup.select('.overaxes-' + yLayer).select('.' + yId);
58785 }
58786
58787 // common attributes for all subplots, overlays or not
58788
58789 if(!hasOnlyLargeSploms) {
58790 ensureSingleAndAddDatum(plotinfo.gridlayer, 'g', plotinfo.xaxis._id);
58791 ensureSingleAndAddDatum(plotinfo.gridlayer, 'g', plotinfo.yaxis._id);
58792 plotinfo.gridlayer.selectAll('g')
58793 .map(function(d) { return d[0]; })
58794 .sort(axisIds.idSort);
58795 }
58796
58797 plotinfo.xlines
58798 .style('fill', 'none')
58799 .classed('crisp', true);
58800
58801 plotinfo.ylines
58802 .style('fill', 'none')
58803 .classed('crisp', true);
58804}
58805
58806function purgeSubplotLayers(layers, fullLayout) {
58807 if(!layers) return;
58808
58809 var overlayIdsToRemove = {};
58810
58811 layers.each(function(d) {
58812 var id = d[0];
58813 var plotgroup = d3.select(this);
58814
58815 plotgroup.remove();
58816 removeSubplotExtras(id, fullLayout);
58817 overlayIdsToRemove[id] = true;
58818
58819 // do not remove individual axis <clipPath>s here
58820 // as other subplots may need them
58821 });
58822
58823 // must remove overlaid subplot trace layers 'manually'
58824
58825 for(var k in fullLayout._plots) {
58826 var subplotInfo = fullLayout._plots[k];
58827 var overlays = subplotInfo.overlays || [];
58828
58829 for(var j = 0; j < overlays.length; j++) {
58830 var overlayInfo = overlays[j];
58831
58832 if(overlayIdsToRemove[overlayInfo.id]) {
58833 overlayInfo.plot.selectAll('.trace').remove();
58834 }
58835 }
58836 }
58837}
58838
58839function removeSubplotExtras(subplotId, fullLayout) {
58840 fullLayout._draggers.selectAll('g.' + subplotId).remove();
58841 fullLayout._defs.select('#clip' + fullLayout._uid + subplotId + 'plot').remove();
58842}
58843
58844exports.toSVG = function(gd) {
58845 var imageRoot = gd._fullLayout._glimages;
58846 var root = d3.select(gd).selectAll('.svg-container');
58847 var canvases = root.filter(function(d, i) {return i === root.size() - 1;})
58848 .selectAll('.gl-canvas-context, .gl-canvas-focus');
58849
58850 function canvasToImage() {
58851 var canvas = this;
58852 var imageData = canvas.toDataURL('image/png');
58853 var image = imageRoot.append('svg:image');
58854
58855 image.attr({
58856 xmlns: xmlnsNamespaces.svg,
58857 'xlink:href': imageData,
58858 preserveAspectRatio: 'none',
58859 x: 0,
58860 y: 0,
58861 width: canvas.width,
58862 height: canvas.height
58863 });
58864 }
58865
58866 canvases.each(canvasToImage);
58867};
58868
58869exports.updateFx = _dereq_('./graph_interact').updateFx;
58870
58871},{"../../components/drawing":74,"../../constants/xmlns_namespaces":159,"../../lib":178,"../../registry":269,"../get_data":252,"../plots":256,"./attributes":220,"./axis_ids":225,"./constants":228,"./graph_interact":231,"./layout_attributes":236,"./layout_defaults":237,"./transition_axes":246,"d3":16}],236:[function(_dereq_,module,exports){
58872/**
58873* Copyright 2012-2020, Plotly, Inc.
58874* All rights reserved.
58875*
58876* This source code is licensed under the MIT license found in the
58877* LICENSE file in the root directory of this source tree.
58878*/
58879
58880'use strict';
58881
58882var fontAttrs = _dereq_('../font_attributes');
58883var colorAttrs = _dereq_('../../components/color/attributes');
58884var dash = _dereq_('../../components/drawing/attributes').dash;
58885var extendFlat = _dereq_('../../lib/extend').extendFlat;
58886var templatedArray = _dereq_('../../plot_api/plot_template').templatedArray;
58887
58888var FORMAT_LINK = _dereq_('../../constants/docs').FORMAT_LINK;
58889var DATE_FORMAT_LINK = _dereq_('../../constants/docs').DATE_FORMAT_LINK;
58890var ONEDAY = _dereq_('../../constants/numerical').ONEDAY;
58891var constants = _dereq_('./constants');
58892var HOUR = constants.HOUR_PATTERN;
58893var DAY_OF_WEEK = constants.WEEKDAY_PATTERN;
58894
58895module.exports = {
58896 visible: {
58897 valType: 'boolean',
58898
58899 editType: 'plot',
58900
58901 },
58902 color: {
58903 valType: 'color',
58904 dflt: colorAttrs.defaultLine,
58905
58906 editType: 'ticks',
58907
58908 },
58909 title: {
58910 text: {
58911 valType: 'string',
58912
58913 editType: 'ticks',
58914
58915 },
58916 font: fontAttrs({
58917 editType: 'ticks',
58918
58919 }),
58920 standoff: {
58921 valType: 'number',
58922
58923 min: 0,
58924 editType: 'ticks',
58925
58926 },
58927 editType: 'ticks'
58928 },
58929 type: {
58930 valType: 'enumerated',
58931 // '-' means we haven't yet run autotype or couldn't find any data
58932 // it gets turned into linear in gd._fullLayout but not copied back
58933 // to gd.data like the others are.
58934 values: ['-', 'linear', 'log', 'date', 'category', 'multicategory'],
58935 dflt: '-',
58936
58937 editType: 'calc',
58938 // we forget when an axis has been autotyped, just writing the auto
58939 // value back to the input - so it doesn't make sense to template this.
58940 // Note: we do NOT prohibit this in `coerce`, so if someone enters a
58941 // type in the template explicitly it will be honored as the default.
58942 _noTemplating: true,
58943
58944 },
58945 autorange: {
58946 valType: 'enumerated',
58947 values: [true, false, 'reversed'],
58948 dflt: true,
58949
58950 editType: 'axrange',
58951 impliedEdits: {'range[0]': undefined, 'range[1]': undefined},
58952
58953 },
58954 rangemode: {
58955 valType: 'enumerated',
58956 values: ['normal', 'tozero', 'nonnegative'],
58957 dflt: 'normal',
58958
58959 editType: 'plot',
58960
58961 },
58962 range: {
58963 valType: 'info_array',
58964
58965 items: [
58966 {valType: 'any', editType: 'axrange', impliedEdits: {'^autorange': false}, anim: true},
58967 {valType: 'any', editType: 'axrange', impliedEdits: {'^autorange': false}, anim: true}
58968 ],
58969 editType: 'axrange',
58970 impliedEdits: {'autorange': false},
58971 anim: true,
58972
58973 },
58974 fixedrange: {
58975 valType: 'boolean',
58976 dflt: false,
58977
58978 editType: 'calc',
58979
58980 },
58981 // scaleanchor: not used directly, just put here for reference
58982 // values are any opposite-letter axis id
58983 scaleanchor: {
58984 valType: 'enumerated',
58985 values: [
58986 constants.idRegex.x.toString(),
58987 constants.idRegex.y.toString()
58988 ],
58989
58990 editType: 'plot',
58991
58992 },
58993 scaleratio: {
58994 valType: 'number',
58995 min: 0,
58996 dflt: 1,
58997
58998 editType: 'plot',
58999
59000 },
59001 constrain: {
59002 valType: 'enumerated',
59003 values: ['range', 'domain'],
59004 dflt: 'range',
59005
59006 editType: 'plot',
59007
59008 },
59009 // constraintoward: not used directly, just put here for reference
59010 constraintoward: {
59011 valType: 'enumerated',
59012 values: ['left', 'center', 'right', 'top', 'middle', 'bottom'],
59013
59014 editType: 'plot',
59015
59016 },
59017 matches: {
59018 valType: 'enumerated',
59019 values: [
59020 constants.idRegex.x.toString(),
59021 constants.idRegex.y.toString()
59022 ],
59023
59024 editType: 'calc',
59025
59026 },
59027
59028 rangebreaks: templatedArray('rangebreak', {
59029 enabled: {
59030 valType: 'boolean',
59031
59032 dflt: true,
59033 editType: 'calc',
59034
59035 },
59036
59037 bounds: {
59038 valType: 'info_array',
59039
59040 items: [
59041 {valType: 'any', editType: 'calc'},
59042 {valType: 'any', editType: 'calc'}
59043 ],
59044 editType: 'calc',
59045
59046 },
59047
59048 pattern: {
59049 valType: 'enumerated',
59050 values: [DAY_OF_WEEK, HOUR, ''],
59051
59052 editType: 'calc',
59053
59054 },
59055
59056 values: {
59057 valType: 'info_array',
59058 freeLength: true,
59059
59060 editType: 'calc',
59061 items: {
59062 valType: 'any',
59063 editType: 'calc'
59064 },
59065
59066 },
59067 dvalue: {
59068 // TODO could become 'any' to add support for 'months', 'years'
59069 valType: 'number',
59070
59071 editType: 'calc',
59072 min: 0,
59073 dflt: ONEDAY,
59074
59075 },
59076
59077 /*
59078 gap: {
59079 valType: 'number',
59080 min: 0,
59081 dflt: 0, // for *date* axes, maybe something else for *linear*
59082 editType: 'calc',
59083
59084
59085 },
59086 gapmode: {
59087 valType: 'enumerated',
59088 values: ['pixels', 'fraction'],
59089 dflt: 'pixels',
59090 editType: 'calc',
59091
59092
59093 },
59094 */
59095
59096 // To complete https://github.com/plotly/plotly.js/issues/4210
59097 // we additionally need `gap` and make this work on *linear*, and
59098 // possibly all other cartesian axis types. We possibly would also need
59099 // some style attributes controlling the zig-zag on the corresponding
59100 // axis.
59101
59102 editType: 'calc'
59103 }),
59104
59105 // ticks
59106 tickmode: {
59107 valType: 'enumerated',
59108 values: ['auto', 'linear', 'array'],
59109
59110 editType: 'ticks',
59111 impliedEdits: {tick0: undefined, dtick: undefined},
59112
59113 },
59114 nticks: {
59115 valType: 'integer',
59116 min: 0,
59117 dflt: 0,
59118
59119 editType: 'ticks',
59120
59121 },
59122 tick0: {
59123 valType: 'any',
59124
59125 editType: 'ticks',
59126 impliedEdits: {tickmode: 'linear'},
59127
59128 },
59129 dtick: {
59130 valType: 'any',
59131
59132 editType: 'ticks',
59133 impliedEdits: {tickmode: 'linear'},
59134
59135 },
59136 tickvals: {
59137 valType: 'data_array',
59138 editType: 'ticks',
59139
59140 },
59141 ticktext: {
59142 valType: 'data_array',
59143 editType: 'ticks',
59144
59145 },
59146 ticks: {
59147 valType: 'enumerated',
59148 values: ['outside', 'inside', ''],
59149
59150 editType: 'ticks',
59151
59152 },
59153 tickson: {
59154 valType: 'enumerated',
59155 values: ['labels', 'boundaries'],
59156
59157 dflt: 'labels',
59158 editType: 'ticks',
59159
59160 },
59161 mirror: {
59162 valType: 'enumerated',
59163 values: [true, 'ticks', false, 'all', 'allticks'],
59164 dflt: false,
59165
59166 editType: 'ticks+layoutstyle',
59167
59168 },
59169 ticklen: {
59170 valType: 'number',
59171 min: 0,
59172 dflt: 5,
59173
59174 editType: 'ticks',
59175
59176 },
59177 tickwidth: {
59178 valType: 'number',
59179 min: 0,
59180 dflt: 1,
59181
59182 editType: 'ticks',
59183
59184 },
59185 tickcolor: {
59186 valType: 'color',
59187 dflt: colorAttrs.defaultLine,
59188
59189 editType: 'ticks',
59190
59191 },
59192 showticklabels: {
59193 valType: 'boolean',
59194 dflt: true,
59195
59196 editType: 'ticks',
59197
59198 },
59199 automargin: {
59200 valType: 'boolean',
59201 dflt: false,
59202
59203 editType: 'ticks',
59204
59205 },
59206 showspikes: {
59207 valType: 'boolean',
59208 dflt: false,
59209
59210 editType: 'modebar',
59211
59212 },
59213 spikecolor: {
59214 valType: 'color',
59215 dflt: null,
59216
59217 editType: 'none',
59218
59219 },
59220 spikethickness: {
59221 valType: 'number',
59222 dflt: 3,
59223
59224 editType: 'none',
59225
59226 },
59227 spikedash: extendFlat({}, dash, {dflt: 'dash', editType: 'none'}),
59228 spikemode: {
59229 valType: 'flaglist',
59230 flags: ['toaxis', 'across', 'marker'],
59231
59232 dflt: 'toaxis',
59233 editType: 'none',
59234
59235 },
59236 spikesnap: {
59237 valType: 'enumerated',
59238 values: ['data', 'cursor', 'hovered data'],
59239 dflt: 'data',
59240
59241 editType: 'none',
59242
59243 },
59244 tickfont: fontAttrs({
59245 editType: 'ticks',
59246
59247 }),
59248 tickangle: {
59249 valType: 'angle',
59250 dflt: 'auto',
59251
59252 editType: 'ticks',
59253
59254 },
59255 tickprefix: {
59256 valType: 'string',
59257 dflt: '',
59258
59259 editType: 'ticks',
59260
59261 },
59262 showtickprefix: {
59263 valType: 'enumerated',
59264 values: ['all', 'first', 'last', 'none'],
59265 dflt: 'all',
59266
59267 editType: 'ticks',
59268
59269 },
59270 ticksuffix: {
59271 valType: 'string',
59272 dflt: '',
59273
59274 editType: 'ticks',
59275
59276 },
59277 showticksuffix: {
59278 valType: 'enumerated',
59279 values: ['all', 'first', 'last', 'none'],
59280 dflt: 'all',
59281
59282 editType: 'ticks',
59283
59284 },
59285 showexponent: {
59286 valType: 'enumerated',
59287 values: ['all', 'first', 'last', 'none'],
59288 dflt: 'all',
59289
59290 editType: 'ticks',
59291
59292 },
59293 exponentformat: {
59294 valType: 'enumerated',
59295 values: ['none', 'e', 'E', 'power', 'SI', 'B'],
59296 dflt: 'B',
59297
59298 editType: 'ticks',
59299
59300 },
59301 separatethousands: {
59302 valType: 'boolean',
59303 dflt: false,
59304
59305 editType: 'ticks',
59306
59307 },
59308 tickformat: {
59309 valType: 'string',
59310 dflt: '',
59311
59312 editType: 'ticks',
59313
59314 },
59315 tickformatstops: templatedArray('tickformatstop', {
59316 enabled: {
59317 valType: 'boolean',
59318
59319 dflt: true,
59320 editType: 'ticks',
59321
59322 },
59323 dtickrange: {
59324 valType: 'info_array',
59325
59326 items: [
59327 {valType: 'any', editType: 'ticks'},
59328 {valType: 'any', editType: 'ticks'}
59329 ],
59330 editType: 'ticks',
59331
59332 },
59333 value: {
59334 valType: 'string',
59335 dflt: '',
59336
59337 editType: 'ticks',
59338
59339 },
59340 editType: 'ticks'
59341 }),
59342 hoverformat: {
59343 valType: 'string',
59344 dflt: '',
59345
59346 editType: 'none',
59347
59348 },
59349 // lines and grids
59350 showline: {
59351 valType: 'boolean',
59352 dflt: false,
59353
59354 editType: 'ticks+layoutstyle',
59355
59356 },
59357 linecolor: {
59358 valType: 'color',
59359 dflt: colorAttrs.defaultLine,
59360
59361 editType: 'layoutstyle',
59362
59363 },
59364 linewidth: {
59365 valType: 'number',
59366 min: 0,
59367 dflt: 1,
59368
59369 editType: 'ticks+layoutstyle',
59370
59371 },
59372 showgrid: {
59373 valType: 'boolean',
59374
59375 editType: 'ticks',
59376
59377 },
59378 gridcolor: {
59379 valType: 'color',
59380 dflt: colorAttrs.lightLine,
59381
59382 editType: 'ticks',
59383
59384 },
59385 gridwidth: {
59386 valType: 'number',
59387 min: 0,
59388 dflt: 1,
59389
59390 editType: 'ticks',
59391
59392 },
59393 zeroline: {
59394 valType: 'boolean',
59395
59396 editType: 'ticks',
59397
59398 },
59399 zerolinecolor: {
59400 valType: 'color',
59401 dflt: colorAttrs.defaultLine,
59402
59403 editType: 'ticks',
59404
59405 },
59406 zerolinewidth: {
59407 valType: 'number',
59408 dflt: 1,
59409
59410 editType: 'ticks',
59411
59412 },
59413
59414 showdividers: {
59415 valType: 'boolean',
59416 dflt: true,
59417
59418 editType: 'ticks',
59419
59420 },
59421 dividercolor: {
59422 valType: 'color',
59423 dflt: colorAttrs.defaultLine,
59424
59425 editType: 'ticks',
59426
59427 },
59428 dividerwidth: {
59429 valType: 'number',
59430 dflt: 1,
59431
59432 editType: 'ticks',
59433
59434 },
59435 // TODO dividerlen: that would override "to label base" length?
59436
59437 // positioning attributes
59438 // anchor: not used directly, just put here for reference
59439 // values are any opposite-letter axis id
59440 anchor: {
59441 valType: 'enumerated',
59442 values: [
59443 'free',
59444 constants.idRegex.x.toString(),
59445 constants.idRegex.y.toString()
59446 ],
59447
59448 editType: 'plot',
59449
59450 },
59451 // side: not used directly, as values depend on direction
59452 // values are top, bottom for x axes, and left, right for y
59453 side: {
59454 valType: 'enumerated',
59455 values: ['top', 'bottom', 'left', 'right'],
59456
59457 editType: 'plot',
59458
59459 },
59460 // overlaying: not used directly, just put here for reference
59461 // values are false and any other same-letter axis id that's not
59462 // itself overlaying anything
59463 overlaying: {
59464 valType: 'enumerated',
59465 values: [
59466 'free',
59467 constants.idRegex.x.toString(),
59468 constants.idRegex.y.toString()
59469 ],
59470
59471 editType: 'plot',
59472
59473 },
59474 layer: {
59475 valType: 'enumerated',
59476 values: ['above traces', 'below traces'],
59477 dflt: 'above traces',
59478
59479 editType: 'plot',
59480
59481 },
59482 domain: {
59483 valType: 'info_array',
59484
59485 items: [
59486 {valType: 'number', min: 0, max: 1, editType: 'plot'},
59487 {valType: 'number', min: 0, max: 1, editType: 'plot'}
59488 ],
59489 dflt: [0, 1],
59490 editType: 'plot',
59491
59492 },
59493 position: {
59494 valType: 'number',
59495 min: 0,
59496 max: 1,
59497 dflt: 0,
59498
59499 editType: 'plot',
59500
59501 },
59502 categoryorder: {
59503 valType: 'enumerated',
59504 values: [
59505 'trace', 'category ascending', 'category descending', 'array',
59506 'total ascending', 'total descending',
59507 'min ascending', 'min descending',
59508 'max ascending', 'max descending',
59509 'sum ascending', 'sum descending',
59510 'mean ascending', 'mean descending',
59511 'median ascending', 'median descending'
59512 ],
59513 dflt: 'trace',
59514
59515 editType: 'calc',
59516
59517 },
59518 categoryarray: {
59519 valType: 'data_array',
59520
59521 editType: 'calc',
59522
59523 },
59524 uirevision: {
59525 valType: 'any',
59526
59527 editType: 'none',
59528
59529 },
59530 editType: 'calc',
59531
59532 _deprecated: {
59533 autotick: {
59534 valType: 'boolean',
59535
59536 editType: 'ticks',
59537
59538 },
59539 title: {
59540 valType: 'string',
59541
59542 editType: 'ticks',
59543
59544 },
59545 titlefont: fontAttrs({
59546 editType: 'ticks',
59547
59548 })
59549 }
59550};
59551
59552},{"../../components/color/attributes":51,"../../components/drawing/attributes":73,"../../constants/docs":155,"../../constants/numerical":158,"../../lib/extend":173,"../../plot_api/plot_template":212,"../font_attributes":250,"./constants":228}],237:[function(_dereq_,module,exports){
59553/**
59554* Copyright 2012-2020, Plotly, Inc.
59555* All rights reserved.
59556*
59557* This source code is licensed under the MIT license found in the
59558* LICENSE file in the root directory of this source tree.
59559*/
59560
59561
59562'use strict';
59563
59564var Lib = _dereq_('../../lib');
59565var Color = _dereq_('../../components/color');
59566var isUnifiedHover = _dereq_('../../components/fx/helpers').isUnifiedHover;
59567var handleHoverModeDefaults = _dereq_('../../components/fx/hovermode_defaults');
59568var Template = _dereq_('../../plot_api/plot_template');
59569var basePlotLayoutAttributes = _dereq_('../layout_attributes');
59570
59571var layoutAttributes = _dereq_('./layout_attributes');
59572var handleTypeDefaults = _dereq_('./type_defaults');
59573var handleAxisDefaults = _dereq_('./axis_defaults');
59574var handleConstraintDefaults = _dereq_('./constraints').handleConstraintDefaults;
59575var handlePositionDefaults = _dereq_('./position_defaults');
59576
59577var axisIds = _dereq_('./axis_ids');
59578var id2name = axisIds.id2name;
59579var name2id = axisIds.name2id;
59580
59581var AX_ID_PATTERN = _dereq_('./constants').AX_ID_PATTERN;
59582
59583var Registry = _dereq_('../../registry');
59584var traceIs = Registry.traceIs;
59585var getComponentMethod = Registry.getComponentMethod;
59586
59587function appendList(cont, k, item) {
59588 if(Array.isArray(cont[k])) cont[k].push(item);
59589 else cont[k] = [item];
59590}
59591
59592module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) {
59593 var ax2traces = {};
59594 var xaMayHide = {};
59595 var yaMayHide = {};
59596 var xaMustDisplay = {};
59597 var yaMustDisplay = {};
59598 var yaMustNotReverse = {};
59599 var yaMayReverse = {};
59600 var axHasImage = {};
59601 var outerTicks = {};
59602 var noGrids = {};
59603 var i, j;
59604
59605 // look for axes in the data
59606 for(i = 0; i < fullData.length; i++) {
59607 var trace = fullData[i];
59608 if(!traceIs(trace, 'cartesian') && !traceIs(trace, 'gl2d')) continue;
59609
59610 var xaName;
59611 if(trace.xaxis) {
59612 xaName = id2name(trace.xaxis);
59613 appendList(ax2traces, xaName, trace);
59614 } else if(trace.xaxes) {
59615 for(j = 0; j < trace.xaxes.length; j++) {
59616 appendList(ax2traces, id2name(trace.xaxes[j]), trace);
59617 }
59618 }
59619
59620 var yaName;
59621 if(trace.yaxis) {
59622 yaName = id2name(trace.yaxis);
59623 appendList(ax2traces, yaName, trace);
59624 } else if(trace.yaxes) {
59625 for(j = 0; j < trace.yaxes.length; j++) {
59626 appendList(ax2traces, id2name(trace.yaxes[j]), trace);
59627 }
59628 }
59629
59630 // logic for funnels
59631 if(trace.type === 'funnel') {
59632 if(trace.orientation === 'h') {
59633 if(xaName) xaMayHide[xaName] = true;
59634 if(yaName) yaMayReverse[yaName] = true;
59635 } else {
59636 if(yaName) yaMayHide[yaName] = true;
59637 }
59638 } else if(trace.type === 'image') {
59639 if(yaName) axHasImage[yaName] = true;
59640 if(xaName) axHasImage[xaName] = true;
59641 } else {
59642 if(yaName) {
59643 yaMustDisplay[yaName] = true;
59644 yaMustNotReverse[yaName] = true;
59645 }
59646
59647 if(!traceIs(trace, 'carpet') || (trace.type === 'carpet' && !trace._cheater)) {
59648 if(xaName) xaMustDisplay[xaName] = true;
59649 }
59650 }
59651
59652 // Two things trigger axis visibility:
59653 // 1. is not carpet
59654 // 2. carpet that's not cheater
59655
59656 // The above check for definitely-not-cheater is not adequate. This
59657 // second list tracks which axes *could* be a cheater so that the
59658 // full condition triggering hiding is:
59659 // *could* be a cheater and *is not definitely visible*
59660 if(trace.type === 'carpet' && trace._cheater) {
59661 if(xaName) xaMayHide[xaName] = true;
59662 }
59663
59664 // check for default formatting tweaks
59665 if(traceIs(trace, '2dMap')) {
59666 outerTicks[xaName] = true;
59667 outerTicks[yaName] = true;
59668 }
59669
59670 if(traceIs(trace, 'oriented')) {
59671 var positionAxis = trace.orientation === 'h' ? yaName : xaName;
59672 noGrids[positionAxis] = true;
59673 }
59674 }
59675
59676 var subplots = layoutOut._subplots;
59677 var xIds = subplots.xaxis;
59678 var yIds = subplots.yaxis;
59679 var xNames = Lib.simpleMap(xIds, id2name);
59680 var yNames = Lib.simpleMap(yIds, id2name);
59681 var axNames = xNames.concat(yNames);
59682
59683 // plot_bgcolor only makes sense if there's a (2D) plot!
59684 // TODO: bgcolor for each subplot, to inherit from the main one
59685 var plotBgColor = Color.background;
59686 if(xIds.length && yIds.length) {
59687 plotBgColor = Lib.coerce(layoutIn, layoutOut, basePlotLayoutAttributes, 'plot_bgcolor');
59688 }
59689
59690 var bgColor = Color.combine(plotBgColor, layoutOut.paper_bgcolor);
59691
59692 // name of single axis (e.g. 'xaxis', 'yaxis2')
59693 var axName;
59694 // id of single axis (e.g. 'y', 'x5')
59695 var axId;
59696 // 'x' or 'y'
59697 var axLetter;
59698 // input layout axis container
59699 var axLayoutIn;
59700 // full layout axis container
59701 var axLayoutOut;
59702
59703 function newAxLayoutOut() {
59704 var traces = ax2traces[axName] || [];
59705 axLayoutOut._traceIndices = traces.map(function(t) { return t._expandedIndex; });
59706 axLayoutOut._annIndices = [];
59707 axLayoutOut._shapeIndices = [];
59708 axLayoutOut._imgIndices = [];
59709 axLayoutOut._subplotsWith = [];
59710 axLayoutOut._counterAxes = [];
59711 axLayoutOut._name = axLayoutOut._attr = axName;
59712 axLayoutOut._id = axId;
59713 }
59714
59715 function coerce(attr, dflt) {
59716 return Lib.coerce(axLayoutIn, axLayoutOut, layoutAttributes, attr, dflt);
59717 }
59718
59719 function coerce2(attr, dflt) {
59720 return Lib.coerce2(axLayoutIn, axLayoutOut, layoutAttributes, attr, dflt);
59721 }
59722
59723 function getCounterAxes(axLetter) {
59724 return (axLetter === 'x') ? yIds : xIds;
59725 }
59726
59727 function getOverlayableAxes(axLetter, axName) {
59728 var list = (axLetter === 'x') ? xNames : yNames;
59729 var out = [];
59730
59731 for(var j = 0; j < list.length; j++) {
59732 var axName2 = list[j];
59733
59734 if(axName2 !== axName && !(layoutIn[axName2] || {}).overlaying) {
59735 out.push(name2id(axName2));
59736 }
59737 }
59738
59739 return out;
59740 }
59741
59742 // list of available counter axis names
59743 var counterAxes = {x: getCounterAxes('x'), y: getCounterAxes('y')};
59744 // list of all x AND y axis ids
59745 var allAxisIds = counterAxes.x.concat(counterAxes.y);
59746 // lookup and list of axis ids that axes in axNames have a reference to,
59747 // even though they are missing from allAxisIds
59748 var missingMatchedAxisIdsLookup = {};
59749 var missingMatchedAxisIds = [];
59750
59751 // fill in 'missing' axis lookup when an axis is set to match an axis
59752 // not part of the allAxisIds list, save axis type so that we can propagate
59753 // it to the missing axes
59754 function addMissingMatchedAxis() {
59755 var matchesIn = axLayoutIn.matches;
59756 if(AX_ID_PATTERN.test(matchesIn) && allAxisIds.indexOf(matchesIn) === -1) {
59757 missingMatchedAxisIdsLookup[matchesIn] = axLayoutIn.type;
59758 missingMatchedAxisIds = Object.keys(missingMatchedAxisIdsLookup);
59759 }
59760 }
59761
59762 var hovermode = handleHoverModeDefaults(layoutIn, layoutOut, fullData);
59763 var unifiedHover = isUnifiedHover(hovermode);
59764
59765 // first pass creates the containers, determines types, and handles most of the settings
59766 for(i = 0; i < axNames.length; i++) {
59767 axName = axNames[i];
59768 axId = name2id(axName);
59769 axLetter = axName.charAt(0);
59770
59771 if(!Lib.isPlainObject(layoutIn[axName])) {
59772 layoutIn[axName] = {};
59773 }
59774
59775 axLayoutIn = layoutIn[axName];
59776 axLayoutOut = Template.newContainer(layoutOut, axName, axLetter + 'axis');
59777 newAxLayoutOut();
59778
59779 var visibleDflt =
59780 (axLetter === 'x' && !xaMustDisplay[axName] && xaMayHide[axName]) ||
59781 (axLetter === 'y' && !yaMustDisplay[axName] && yaMayHide[axName]);
59782
59783 var reverseDflt =
59784 (axLetter === 'y' &&
59785 (
59786 (!yaMustNotReverse[axName] && yaMayReverse[axName]) ||
59787 axHasImage[axName]
59788 ));
59789
59790 var defaultOptions = {
59791 letter: axLetter,
59792 font: layoutOut.font,
59793 outerTicks: outerTicks[axName],
59794 showGrid: !noGrids[axName],
59795 data: ax2traces[axName] || [],
59796 bgColor: bgColor,
59797 calendar: layoutOut.calendar,
59798 automargin: true,
59799 visibleDflt: visibleDflt,
59800 reverseDflt: reverseDflt,
59801 splomStash: ((layoutOut._splomAxes || {})[axLetter] || {})[axId]
59802 };
59803
59804 coerce('uirevision', layoutOut.uirevision);
59805
59806 handleTypeDefaults(axLayoutIn, axLayoutOut, coerce, defaultOptions);
59807 handleAxisDefaults(axLayoutIn, axLayoutOut, coerce, defaultOptions, layoutOut);
59808
59809 var unifiedSpike = unifiedHover && axLetter === hovermode.charAt(0);
59810 var spikecolor = coerce2('spikecolor', unifiedHover ? axLayoutOut.color : undefined);
59811 var spikethickness = coerce2('spikethickness', unifiedHover ? 1.5 : undefined);
59812 var spikedash = coerce2('spikedash', unifiedHover ? 'dot' : undefined);
59813 var spikemode = coerce2('spikemode', unifiedHover ? 'across' : undefined);
59814 var spikesnap = coerce2('spikesnap', unifiedHover ? 'hovered data' : undefined);
59815 var showSpikes = coerce('showspikes', !!unifiedSpike || !!spikecolor || !!spikethickness || !!spikedash || !!spikemode || !!spikesnap);
59816
59817 if(!showSpikes) {
59818 delete axLayoutOut.spikecolor;
59819 delete axLayoutOut.spikethickness;
59820 delete axLayoutOut.spikedash;
59821 delete axLayoutOut.spikemode;
59822 delete axLayoutOut.spikesnap;
59823 }
59824
59825 handlePositionDefaults(axLayoutIn, axLayoutOut, coerce, {
59826 letter: axLetter,
59827 counterAxes: counterAxes[axLetter],
59828 overlayableAxes: getOverlayableAxes(axLetter, axName),
59829 grid: layoutOut.grid
59830 });
59831
59832 coerce('title.standoff');
59833
59834 addMissingMatchedAxis();
59835
59836 axLayoutOut._input = axLayoutIn;
59837 }
59838
59839 // coerce the 'missing' axes
59840 i = 0;
59841 while(i < missingMatchedAxisIds.length) {
59842 axId = missingMatchedAxisIds[i++];
59843 axName = id2name(axId);
59844 axLetter = axName.charAt(0);
59845
59846 if(!Lib.isPlainObject(layoutIn[axName])) {
59847 layoutIn[axName] = {};
59848 }
59849
59850 axLayoutIn = layoutIn[axName];
59851 axLayoutOut = Template.newContainer(layoutOut, axName, axLetter + 'axis');
59852 newAxLayoutOut();
59853
59854 var defaultOptions2 = {
59855 letter: axLetter,
59856 font: layoutOut.font,
59857 outerTicks: outerTicks[axName],
59858 showGrid: !noGrids[axName],
59859 data: [],
59860 bgColor: bgColor,
59861 calendar: layoutOut.calendar,
59862 automargin: true,
59863 visibleDflt: false,
59864 reverseDflt: false,
59865 splomStash: ((layoutOut._splomAxes || {})[axLetter] || {})[axId]
59866 };
59867
59868 coerce('uirevision', layoutOut.uirevision);
59869
59870 axLayoutOut.type = missingMatchedAxisIdsLookup[axId] || 'linear';
59871
59872 handleAxisDefaults(axLayoutIn, axLayoutOut, coerce, defaultOptions2, layoutOut);
59873
59874 handlePositionDefaults(axLayoutIn, axLayoutOut, coerce, {
59875 letter: axLetter,
59876 counterAxes: counterAxes[axLetter],
59877 overlayableAxes: getOverlayableAxes(axLetter, axName),
59878 grid: layoutOut.grid
59879 });
59880
59881 coerce('fixedrange');
59882
59883 addMissingMatchedAxis();
59884
59885 axLayoutOut._input = axLayoutIn;
59886 }
59887
59888 // quick second pass for range slider and selector defaults
59889 var rangeSliderDefaults = getComponentMethod('rangeslider', 'handleDefaults');
59890 var rangeSelectorDefaults = getComponentMethod('rangeselector', 'handleDefaults');
59891
59892 for(i = 0; i < xNames.length; i++) {
59893 axName = xNames[i];
59894 axLayoutIn = layoutIn[axName];
59895 axLayoutOut = layoutOut[axName];
59896
59897 rangeSliderDefaults(layoutIn, layoutOut, axName);
59898
59899 if(axLayoutOut.type === 'date') {
59900 rangeSelectorDefaults(
59901 axLayoutIn,
59902 axLayoutOut,
59903 layoutOut,
59904 yNames,
59905 axLayoutOut.calendar
59906 );
59907 }
59908
59909 coerce('fixedrange');
59910 }
59911
59912 for(i = 0; i < yNames.length; i++) {
59913 axName = yNames[i];
59914 axLayoutIn = layoutIn[axName];
59915 axLayoutOut = layoutOut[axName];
59916
59917 var anchoredAxis = layoutOut[id2name(axLayoutOut.anchor)];
59918
59919 var fixedRangeDflt = getComponentMethod('rangeslider', 'isVisible')(anchoredAxis);
59920
59921 coerce('fixedrange', fixedRangeDflt);
59922 }
59923
59924 // Finally, handle scale constraints and matching axes.
59925 //
59926 // We need to do this after all axes have coerced both `type`
59927 // (so we link only axes of the same type) and
59928 // `fixedrange` (so we can avoid linking from OR TO a fixed axis).
59929
59930 // sets of axes linked by `scaleanchor` along with the scaleratios compounded
59931 // together, populated in handleConstraintDefaults
59932 var constraintGroups = layoutOut._axisConstraintGroups = [];
59933 // similar to _axisConstraintGroups, but for matching axes
59934 var matchGroups = layoutOut._axisMatchGroups = [];
59935 // make sure to include 'missing' axes here
59936 var allAxisIdsIncludingMissing = allAxisIds.concat(missingMatchedAxisIds);
59937 var axNamesIncludingMissing = axNames.concat(Lib.simpleMap(missingMatchedAxisIds, id2name));
59938
59939 for(i = 0; i < axNamesIncludingMissing.length; i++) {
59940 axName = axNamesIncludingMissing[i];
59941 axLetter = axName.charAt(0);
59942 axLayoutIn = layoutIn[axName];
59943 axLayoutOut = layoutOut[axName];
59944
59945 var scaleanchorDflt;
59946 if(axLetter === 'y' && !axLayoutIn.hasOwnProperty('scaleanchor') && axHasImage[axName]) {
59947 scaleanchorDflt = axLayoutOut.anchor;
59948 } else {
59949 scaleanchorDflt = undefined;
59950 }
59951
59952 var constrainDflt;
59953 if(!axLayoutIn.hasOwnProperty('constrain') && axHasImage[axName]) {
59954 constrainDflt = 'domain';
59955 } else {
59956 constrainDflt = undefined;
59957 }
59958
59959 handleConstraintDefaults(axLayoutIn, axLayoutOut, coerce, {
59960 allAxisIds: allAxisIdsIncludingMissing,
59961 layoutOut: layoutOut,
59962 scaleanchorDflt: scaleanchorDflt,
59963 constrainDflt: constrainDflt
59964 });
59965 }
59966
59967 for(i = 0; i < matchGroups.length; i++) {
59968 var group = matchGroups[i];
59969 var rng = null;
59970 var autorange = null;
59971
59972 // find 'matching' range attrs
59973 for(axId in group) {
59974 axLayoutOut = layoutOut[id2name(axId)];
59975 if(!axLayoutOut.matches) {
59976 rng = axLayoutOut.range;
59977 autorange = axLayoutOut.autorange;
59978 }
59979 }
59980 // if `ax.matches` values are reciprocal,
59981 // pick values of first axis in group
59982 if(rng === null || autorange === null) {
59983 for(axId in group) {
59984 axLayoutOut = layoutOut[id2name(axId)];
59985 rng = axLayoutOut.range;
59986 autorange = axLayoutOut.autorange;
59987 break;
59988 }
59989 }
59990 // apply matching range attrs
59991 for(axId in group) {
59992 axLayoutOut = layoutOut[id2name(axId)];
59993 if(axLayoutOut.matches) {
59994 axLayoutOut.range = rng.slice();
59995 axLayoutOut.autorange = autorange;
59996 }
59997 axLayoutOut._matchGroup = group;
59998 }
59999
60000 // remove matching axis from scaleanchor constraint groups (for now)
60001 if(constraintGroups.length) {
60002 for(axId in group) {
60003 for(j = 0; j < constraintGroups.length; j++) {
60004 var group2 = constraintGroups[j];
60005 for(var axId2 in group2) {
60006 if(axId === axId2) {
60007 Lib.warn('Axis ' + axId2 + ' is set with both ' +
60008 'a *scaleanchor* and *matches* constraint; ' +
60009 'ignoring the scale constraint.');
60010
60011 delete group2[axId2];
60012 if(Object.keys(group2).length < 2) {
60013 constraintGroups.splice(j, 1);
60014 }
60015 }
60016 }
60017 }
60018 }
60019 }
60020 }
60021};
60022
60023},{"../../components/color":52,"../../components/fx/helpers":88,"../../components/fx/hovermode_defaults":91,"../../lib":178,"../../plot_api/plot_template":212,"../../registry":269,"../layout_attributes":254,"./axis_defaults":224,"./axis_ids":225,"./constants":228,"./constraints":229,"./layout_attributes":236,"./position_defaults":239,"./type_defaults":247}],238:[function(_dereq_,module,exports){
60024/**
60025* Copyright 2012-2020, Plotly, Inc.
60026* All rights reserved.
60027*
60028* This source code is licensed under the MIT license found in the
60029* LICENSE file in the root directory of this source tree.
60030*/
60031
60032'use strict';
60033
60034var colorMix = _dereq_('tinycolor2').mix;
60035var lightFraction = _dereq_('../../components/color/attributes').lightFraction;
60036var Lib = _dereq_('../../lib');
60037
60038/**
60039 * @param {object} opts :
60040 * - dfltColor {string} : default axis color
60041 * - bgColor {string} : combined subplot bg color
60042 * - blend {number, optional} : blend percentage (to compute dflt grid color)
60043 * - showLine {boolean} : show line by default
60044 * - showGrid {boolean} : show grid by default
60045 * - noZeroLine {boolean} : don't coerce zeroline* attributes
60046 * - attributes {object} : attribute object associated with input containers
60047 */
60048module.exports = function handleLineGridDefaults(containerIn, containerOut, coerce, opts) {
60049 opts = opts || {};
60050
60051 var dfltColor = opts.dfltColor;
60052
60053 function coerce2(attr, dflt) {
60054 return Lib.coerce2(containerIn, containerOut, opts.attributes, attr, dflt);
60055 }
60056
60057 var lineColor = coerce2('linecolor', dfltColor);
60058 var lineWidth = coerce2('linewidth');
60059 var showLine = coerce('showline', opts.showLine || !!lineColor || !!lineWidth);
60060
60061 if(!showLine) {
60062 delete containerOut.linecolor;
60063 delete containerOut.linewidth;
60064 }
60065
60066 var gridColorDflt = colorMix(dfltColor, opts.bgColor, opts.blend || lightFraction).toRgbString();
60067 var gridColor = coerce2('gridcolor', gridColorDflt);
60068 var gridWidth = coerce2('gridwidth');
60069 var showGridLines = coerce('showgrid', opts.showGrid || !!gridColor || !!gridWidth);
60070
60071 if(!showGridLines) {
60072 delete containerOut.gridcolor;
60073 delete containerOut.gridwidth;
60074 }
60075
60076 if(!opts.noZeroLine) {
60077 var zeroLineColor = coerce2('zerolinecolor', dfltColor);
60078 var zeroLineWidth = coerce2('zerolinewidth');
60079 var showZeroLine = coerce('zeroline', opts.showGrid || !!zeroLineColor || !!zeroLineWidth);
60080
60081 if(!showZeroLine) {
60082 delete containerOut.zerolinecolor;
60083 delete containerOut.zerolinewidth;
60084 }
60085 }
60086};
60087
60088},{"../../components/color/attributes":51,"../../lib":178,"tinycolor2":35}],239:[function(_dereq_,module,exports){
60089/**
60090* Copyright 2012-2020, Plotly, Inc.
60091* All rights reserved.
60092*
60093* This source code is licensed under the MIT license found in the
60094* LICENSE file in the root directory of this source tree.
60095*/
60096
60097
60098'use strict';
60099
60100var isNumeric = _dereq_('fast-isnumeric');
60101
60102var Lib = _dereq_('../../lib');
60103
60104
60105module.exports = function handlePositionDefaults(containerIn, containerOut, coerce, options) {
60106 var counterAxes = options.counterAxes || [];
60107 var overlayableAxes = options.overlayableAxes || [];
60108 var letter = options.letter;
60109 var grid = options.grid;
60110
60111 var dfltAnchor, dfltDomain, dfltSide, dfltPosition;
60112
60113 if(grid) {
60114 dfltDomain = grid._domains[letter][grid._axisMap[containerOut._id]];
60115 dfltAnchor = grid._anchors[containerOut._id];
60116 if(dfltDomain) {
60117 dfltSide = grid[letter + 'side'].split(' ')[0];
60118 dfltPosition = grid.domain[letter][dfltSide === 'right' || dfltSide === 'top' ? 1 : 0];
60119 }
60120 }
60121
60122 // Even if there's a grid, this axis may not be in it - fall back on non-grid defaults
60123 dfltDomain = dfltDomain || [0, 1];
60124 dfltAnchor = dfltAnchor || (isNumeric(containerIn.position) ? 'free' : (counterAxes[0] || 'free'));
60125 dfltSide = dfltSide || (letter === 'x' ? 'bottom' : 'left');
60126 dfltPosition = dfltPosition || 0;
60127
60128 var anchor = Lib.coerce(containerIn, containerOut, {
60129 anchor: {
60130 valType: 'enumerated',
60131 values: ['free'].concat(counterAxes),
60132 dflt: dfltAnchor
60133 }
60134 }, 'anchor');
60135
60136 if(anchor === 'free') coerce('position', dfltPosition);
60137
60138 Lib.coerce(containerIn, containerOut, {
60139 side: {
60140 valType: 'enumerated',
60141 values: letter === 'x' ? ['bottom', 'top'] : ['left', 'right'],
60142 dflt: dfltSide
60143 }
60144 }, 'side');
60145
60146 var overlaying = false;
60147 if(overlayableAxes.length) {
60148 overlaying = Lib.coerce(containerIn, containerOut, {
60149 overlaying: {
60150 valType: 'enumerated',
60151 values: [false].concat(overlayableAxes),
60152 dflt: false
60153 }
60154 }, 'overlaying');
60155 }
60156
60157 if(!overlaying) {
60158 // TODO: right now I'm copying this domain over to overlaying axes
60159 // in ax.setscale()... but this means we still need (imperfect) logic
60160 // in the axes popover to hide domain for the overlaying axis.
60161 // perhaps I should make a private version _domain that all axes get???
60162 var domain = coerce('domain', dfltDomain);
60163
60164 // according to https://www.npmjs.com/package/canvas-size
60165 // the minimum value of max canvas width across browsers and devices is 4096
60166 // which applied in the calculation below:
60167 if(domain[0] > domain[1] - 1 / 4096) containerOut.domain = dfltDomain;
60168 Lib.noneOrAll(containerIn.domain, containerOut.domain, dfltDomain);
60169 }
60170
60171 coerce('layer');
60172
60173 return containerOut;
60174};
60175
60176},{"../../lib":178,"fast-isnumeric":18}],240:[function(_dereq_,module,exports){
60177/**
60178* Copyright 2012-2020, Plotly, Inc.
60179* All rights reserved.
60180*
60181* This source code is licensed under the MIT license found in the
60182* LICENSE file in the root directory of this source tree.
60183*/
60184
60185
60186'use strict';
60187
60188var FROM_BL = _dereq_('../../constants/alignment').FROM_BL;
60189
60190module.exports = function scaleZoom(ax, factor, centerFraction) {
60191 if(centerFraction === undefined) {
60192 centerFraction = FROM_BL[ax.constraintoward || 'center'];
60193 }
60194
60195 var rangeLinear = [ax.r2l(ax.range[0]), ax.r2l(ax.range[1])];
60196 var center = rangeLinear[0] + (rangeLinear[1] - rangeLinear[0]) * centerFraction;
60197
60198 ax.range = ax._input.range = [
60199 ax.l2r(center + (rangeLinear[0] - center) * factor),
60200 ax.l2r(center + (rangeLinear[1] - center) * factor)
60201 ];
60202};
60203
60204},{"../../constants/alignment":154}],241:[function(_dereq_,module,exports){
60205/**
60206* Copyright 2012-2020, Plotly, Inc.
60207* All rights reserved.
60208*
60209* This source code is licensed under the MIT license found in the
60210* LICENSE file in the root directory of this source tree.
60211*/
60212
60213
60214'use strict';
60215
60216var polybool = _dereq_('polybooljs');
60217
60218var Registry = _dereq_('../../registry');
60219var dashStyle = _dereq_('../../components/drawing').dashStyle;
60220var Color = _dereq_('../../components/color');
60221var Fx = _dereq_('../../components/fx');
60222var makeEventData = _dereq_('../../components/fx/helpers').makeEventData;
60223var dragHelpers = _dereq_('../../components/dragelement/helpers');
60224var freeMode = dragHelpers.freeMode;
60225var rectMode = dragHelpers.rectMode;
60226var drawMode = dragHelpers.drawMode;
60227var openMode = dragHelpers.openMode;
60228var selectMode = dragHelpers.selectMode;
60229
60230var displayOutlines = _dereq_('../../components/shapes/draw_newshape/display_outlines');
60231var handleEllipse = _dereq_('../../components/shapes/draw_newshape/helpers').handleEllipse;
60232var newShapes = _dereq_('../../components/shapes/draw_newshape/newshapes');
60233
60234var Lib = _dereq_('../../lib');
60235var polygon = _dereq_('../../lib/polygon');
60236var throttle = _dereq_('../../lib/throttle');
60237var getFromId = _dereq_('./axis_ids').getFromId;
60238var clearGlCanvases = _dereq_('../../lib/clear_gl_canvases');
60239
60240var redrawReglTraces = _dereq_('../../plot_api/subroutines').redrawReglTraces;
60241
60242var constants = _dereq_('./constants');
60243var MINSELECT = constants.MINSELECT;
60244
60245var filteredPolygon = polygon.filter;
60246var polygonTester = polygon.tester;
60247
60248var clearSelect = _dereq_('./handle_outline').clearSelect;
60249
60250var helpers = _dereq_('./helpers');
60251var p2r = helpers.p2r;
60252var axValue = helpers.axValue;
60253var getTransform = helpers.getTransform;
60254
60255function prepSelect(e, startX, startY, dragOptions, mode) {
60256 var isFreeMode = freeMode(mode);
60257 var isRectMode = rectMode(mode);
60258 var isOpenMode = openMode(mode);
60259 var isDrawMode = drawMode(mode);
60260 var isSelectMode = selectMode(mode);
60261
60262 var isLine = mode === 'drawline';
60263 var isEllipse = mode === 'drawcircle';
60264 var isLineOrEllipse = isLine || isEllipse; // cases with two start & end positions
60265
60266 var gd = dragOptions.gd;
60267 var fullLayout = gd._fullLayout;
60268 var zoomLayer = fullLayout._zoomlayer;
60269 var dragBBox = dragOptions.element.getBoundingClientRect();
60270 var plotinfo = dragOptions.plotinfo;
60271 var transform = getTransform(plotinfo);
60272 var x0 = startX - dragBBox.left;
60273 var y0 = startY - dragBBox.top;
60274 var x1 = x0;
60275 var y1 = y0;
60276 var path0 = 'M' + x0 + ',' + y0;
60277 var pw = dragOptions.xaxes[0]._length;
60278 var ph = dragOptions.yaxes[0]._length;
60279 var allAxes = dragOptions.xaxes.concat(dragOptions.yaxes);
60280 var subtract = e.altKey &&
60281 !(drawMode(mode) && isOpenMode);
60282
60283 var filterPoly, selectionTester, mergedPolygons, currentPolygon;
60284 var i, searchInfo, eventData;
60285
60286 coerceSelectionsCache(e, gd, dragOptions);
60287
60288 if(isFreeMode) {
60289 filterPoly = filteredPolygon([[x0, y0]], constants.BENDPX);
60290 }
60291
60292 var outlines = zoomLayer.selectAll('path.select-outline-' + plotinfo.id).data(isDrawMode ? [0] : [1, 2]);
60293 var drwStyle = fullLayout.newshape;
60294
60295 outlines.enter()
60296 .append('path')
60297 .attr('class', function(d) { return 'select-outline select-outline-' + d + ' select-outline-' + plotinfo.id; })
60298 .style(isDrawMode ? {
60299 opacity: drwStyle.opacity / 2,
60300 fill: isOpenMode ? undefined : drwStyle.fillcolor,
60301 stroke: drwStyle.line.color,
60302 'stroke-dasharray': dashStyle(drwStyle.line.dash, drwStyle.line.width),
60303 'stroke-width': drwStyle.line.width + 'px'
60304 } : {})
60305 .attr('fill-rule', drwStyle.fillrule)
60306 .classed('cursor-move', isDrawMode ? true : false)
60307 .attr('transform', transform)
60308 .attr('d', path0 + 'Z');
60309
60310 var corners = zoomLayer.append('path')
60311 .attr('class', 'zoombox-corners')
60312 .style({
60313 fill: Color.background,
60314 stroke: Color.defaultLine,
60315 'stroke-width': 1
60316 })
60317 .attr('transform', transform)
60318 .attr('d', 'M0,0Z');
60319
60320
60321 var throttleID = fullLayout._uid + constants.SELECTID;
60322 var selection = [];
60323
60324 // find the traces to search for selection points
60325 var searchTraces = determineSearchTraces(gd, dragOptions.xaxes,
60326 dragOptions.yaxes, dragOptions.subplot);
60327
60328 function ascending(a, b) { return a - b; }
60329
60330 // allow subplots to override fillRangeItems routine
60331 var fillRangeItems;
60332
60333 if(plotinfo.fillRangeItems) {
60334 fillRangeItems = plotinfo.fillRangeItems;
60335 } else {
60336 if(isRectMode) {
60337 fillRangeItems = function(eventData, poly) {
60338 var ranges = eventData.range = {};
60339
60340 for(i = 0; i < allAxes.length; i++) {
60341 var ax = allAxes[i];
60342 var axLetter = ax._id.charAt(0);
60343
60344 ranges[ax._id] = [
60345 p2r(ax, poly[axLetter + 'min']),
60346 p2r(ax, poly[axLetter + 'max'])
60347 ].sort(ascending);
60348 }
60349 };
60350 } else { // case of isFreeMode
60351 fillRangeItems = function(eventData, poly, filterPoly) {
60352 var dataPts = eventData.lassoPoints = {};
60353
60354 for(i = 0; i < allAxes.length; i++) {
60355 var ax = allAxes[i];
60356 dataPts[ax._id] = filterPoly.filtered.map(axValue(ax));
60357 }
60358 };
60359 }
60360 }
60361
60362 dragOptions.moveFn = function(dx0, dy0) {
60363 x1 = Math.max(0, Math.min(pw, dx0 + x0));
60364 y1 = Math.max(0, Math.min(ph, dy0 + y0));
60365
60366 var dx = Math.abs(x1 - x0);
60367 var dy = Math.abs(y1 - y0);
60368
60369 if(isRectMode) {
60370 var direction;
60371 var start, end;
60372
60373 if(isSelectMode) {
60374 var q = fullLayout.selectdirection;
60375
60376 if(q === 'any') {
60377 if(dy < Math.min(dx * 0.6, MINSELECT)) {
60378 direction = 'h';
60379 } else if(dx < Math.min(dy * 0.6, MINSELECT)) {
60380 direction = 'v';
60381 } else {
60382 direction = 'd';
60383 }
60384 } else {
60385 direction = q;
60386 }
60387
60388 switch(direction) {
60389 case 'h':
60390 start = isEllipse ? ph / 2 : 0;
60391 end = ph;
60392 break;
60393 case 'v':
60394 start = isEllipse ? pw / 2 : 0;
60395 end = pw;
60396 break;
60397 }
60398 }
60399
60400 if(isDrawMode) {
60401 switch(fullLayout.newshape.drawdirection) {
60402 case 'vertical':
60403 direction = 'h';
60404 start = isEllipse ? ph / 2 : 0;
60405 end = ph;
60406 break;
60407 case 'horizontal':
60408 direction = 'v';
60409 start = isEllipse ? pw / 2 : 0;
60410 end = pw;
60411 break;
60412 case 'ortho':
60413 if(dx < dy) {
60414 direction = 'h';
60415 start = y0;
60416 end = y1;
60417 } else {
60418 direction = 'v';
60419 start = x0;
60420 end = x1;
60421 }
60422 break;
60423 default: // i.e. case of 'diagonal'
60424 direction = 'd';
60425 }
60426 }
60427
60428 if(direction === 'h') {
60429 // horizontal motion
60430 currentPolygon = isLineOrEllipse ?
60431 handleEllipse(isEllipse, [x1, start], [x1, end]) : // using x1 instead of x0 allows adjusting the line while drawing
60432 [[x0, start], [x0, end], [x1, end], [x1, start]]; // make a vertical box
60433
60434 currentPolygon.xmin = isLineOrEllipse ? x1 : Math.min(x0, x1);
60435 currentPolygon.xmax = isLineOrEllipse ? x1 : Math.max(x0, x1);
60436 currentPolygon.ymin = Math.min(start, end);
60437 currentPolygon.ymax = Math.max(start, end);
60438 // extras to guide users in keeping a straight selection
60439 corners.attr('d', 'M' + currentPolygon.xmin + ',' + (y0 - MINSELECT) +
60440 'h-4v' + (2 * MINSELECT) + 'h4Z' +
60441 'M' + (currentPolygon.xmax - 1) + ',' + (y0 - MINSELECT) +
60442 'h4v' + (2 * MINSELECT) + 'h-4Z');
60443 } else if(direction === 'v') {
60444 // vertical motion
60445 currentPolygon = isLineOrEllipse ?
60446 handleEllipse(isEllipse, [start, y1], [end, y1]) : // using y1 instead of y0 allows adjusting the line while drawing
60447 [[start, y0], [start, y1], [end, y1], [end, y0]]; // make a horizontal box
60448
60449 currentPolygon.xmin = Math.min(start, end);
60450 currentPolygon.xmax = Math.max(start, end);
60451 currentPolygon.ymin = isLineOrEllipse ? y1 : Math.min(y0, y1);
60452 currentPolygon.ymax = isLineOrEllipse ? y1 : Math.max(y0, y1);
60453 corners.attr('d', 'M' + (x0 - MINSELECT) + ',' + currentPolygon.ymin +
60454 'v-4h' + (2 * MINSELECT) + 'v4Z' +
60455 'M' + (x0 - MINSELECT) + ',' + (currentPolygon.ymax - 1) +
60456 'v4h' + (2 * MINSELECT) + 'v-4Z');
60457 } else if(direction === 'd') {
60458 // diagonal motion
60459 currentPolygon = isLineOrEllipse ?
60460 handleEllipse(isEllipse, [x0, y0], [x1, y1]) :
60461 [[x0, y0], [x0, y1], [x1, y1], [x1, y0]];
60462
60463 currentPolygon.xmin = Math.min(x0, x1);
60464 currentPolygon.xmax = Math.max(x0, x1);
60465 currentPolygon.ymin = Math.min(y0, y1);
60466 currentPolygon.ymax = Math.max(y0, y1);
60467 corners.attr('d', 'M0,0Z');
60468 }
60469 } else if(isFreeMode) {
60470 filterPoly.addPt([x1, y1]);
60471 currentPolygon = filterPoly.filtered;
60472 }
60473
60474 // create outline & tester
60475 if(dragOptions.selectionDefs && dragOptions.selectionDefs.length) {
60476 mergedPolygons = mergePolygons(dragOptions.mergedPolygons, currentPolygon, subtract);
60477 currentPolygon.subtract = subtract;
60478 selectionTester = multiTester(dragOptions.selectionDefs.concat([currentPolygon]));
60479 } else {
60480 mergedPolygons = [currentPolygon];
60481 selectionTester = polygonTester(currentPolygon);
60482 }
60483
60484 // display polygons on the screen
60485 displayOutlines(convertPoly(mergedPolygons, isOpenMode), outlines, dragOptions);
60486
60487 if(isSelectMode) {
60488 throttle.throttle(
60489 throttleID,
60490 constants.SELECTDELAY,
60491 function() {
60492 selection = [];
60493
60494 var thisSelection;
60495 var traceSelections = [];
60496 var traceSelection;
60497 for(i = 0; i < searchTraces.length; i++) {
60498 searchInfo = searchTraces[i];
60499
60500 traceSelection = searchInfo._module.selectPoints(searchInfo, selectionTester);
60501 traceSelections.push(traceSelection);
60502
60503 thisSelection = fillSelectionItem(traceSelection, searchInfo);
60504
60505 if(selection.length) {
60506 for(var j = 0; j < thisSelection.length; j++) {
60507 selection.push(thisSelection[j]);
60508 }
60509 } else selection = thisSelection;
60510 }
60511
60512 eventData = {points: selection};
60513 updateSelectedState(gd, searchTraces, eventData);
60514 fillRangeItems(eventData, currentPolygon, filterPoly);
60515 dragOptions.gd.emit('plotly_selecting', eventData);
60516 }
60517 );
60518 }
60519 };
60520
60521 dragOptions.clickFn = function(numClicks, evt) {
60522 corners.remove();
60523
60524 if(gd._fullLayout._activeShapeIndex >= 0) {
60525 gd._fullLayout._deactivateShape(gd);
60526 return;
60527 }
60528 if(isDrawMode) return;
60529
60530 var clickmode = fullLayout.clickmode;
60531
60532 throttle.done(throttleID).then(function() {
60533 throttle.clear(throttleID);
60534 if(numClicks === 2) {
60535 // clear selection on doubleclick
60536 outlines.remove();
60537 for(i = 0; i < searchTraces.length; i++) {
60538 searchInfo = searchTraces[i];
60539 searchInfo._module.selectPoints(searchInfo, false);
60540 }
60541
60542 updateSelectedState(gd, searchTraces);
60543
60544 clearSelectionsCache(dragOptions);
60545
60546 gd.emit('plotly_deselect', null);
60547 } else {
60548 if(clickmode.indexOf('select') > -1) {
60549 selectOnClick(evt, gd, dragOptions.xaxes, dragOptions.yaxes,
60550 dragOptions.subplot, dragOptions, outlines);
60551 }
60552
60553 if(clickmode === 'event') {
60554 // TODO: remove in v2 - this was probably never intended to work as it does,
60555 // but in case anyone depends on it we don't want to break it now.
60556 // Note that click-to-select introduced pre v2 also emitts proper
60557 // event data when clickmode is having 'select' in its flag list.
60558 gd.emit('plotly_selected', undefined);
60559 }
60560 }
60561
60562 Fx.click(gd, evt);
60563 }).catch(Lib.error);
60564 };
60565
60566 dragOptions.doneFn = function() {
60567 corners.remove();
60568
60569 throttle.done(throttleID).then(function() {
60570 throttle.clear(throttleID);
60571 dragOptions.gd.emit('plotly_selected', eventData);
60572
60573 if(currentPolygon && dragOptions.selectionDefs) {
60574 // save last polygons
60575 currentPolygon.subtract = subtract;
60576 dragOptions.selectionDefs.push(currentPolygon);
60577
60578 // we have to keep reference to arrays container
60579 dragOptions.mergedPolygons.length = 0;
60580 [].push.apply(dragOptions.mergedPolygons, mergedPolygons);
60581 }
60582
60583 if(dragOptions.doneFnCompleted) {
60584 dragOptions.doneFnCompleted(selection);
60585 }
60586 }).catch(Lib.error);
60587
60588 if(isDrawMode) {
60589 clearSelectionsCache(dragOptions);
60590 }
60591 };
60592}
60593
60594function selectOnClick(evt, gd, xAxes, yAxes, subplot, dragOptions, polygonOutlines) {
60595 var hoverData = gd._hoverdata;
60596 var fullLayout = gd._fullLayout;
60597 var clickmode = fullLayout.clickmode;
60598 var sendEvents = clickmode.indexOf('event') > -1;
60599 var selection = [];
60600 var searchTraces, searchInfo, currentSelectionDef, selectionTester, traceSelection;
60601 var thisTracesSelection, pointOrBinSelected, subtract, eventData, i;
60602
60603 if(isHoverDataSet(hoverData)) {
60604 coerceSelectionsCache(evt, gd, dragOptions);
60605 searchTraces = determineSearchTraces(gd, xAxes, yAxes, subplot);
60606 var clickedPtInfo = extractClickedPtInfo(hoverData, searchTraces);
60607 var isBinnedTrace = clickedPtInfo.pointNumbers.length > 0;
60608
60609
60610 // Note: potentially costly operation isPointOrBinSelected is
60611 // called as late as possible through the use of an assignment
60612 // in an if condition.
60613 if(isBinnedTrace ?
60614 isOnlyThisBinSelected(searchTraces, clickedPtInfo) :
60615 isOnlyOnePointSelected(searchTraces) &&
60616 (pointOrBinSelected = isPointOrBinSelected(clickedPtInfo))) {
60617 if(polygonOutlines) polygonOutlines.remove();
60618 for(i = 0; i < searchTraces.length; i++) {
60619 searchInfo = searchTraces[i];
60620 searchInfo._module.selectPoints(searchInfo, false);
60621 }
60622
60623 updateSelectedState(gd, searchTraces);
60624
60625 clearSelectionsCache(dragOptions);
60626
60627 if(sendEvents) {
60628 gd.emit('plotly_deselect', null);
60629 }
60630 } else {
60631 subtract = evt.shiftKey &&
60632 (pointOrBinSelected !== undefined ?
60633 pointOrBinSelected :
60634 isPointOrBinSelected(clickedPtInfo));
60635 currentSelectionDef = newPointSelectionDef(clickedPtInfo.pointNumber, clickedPtInfo.searchInfo, subtract);
60636
60637 var allSelectionDefs = dragOptions.selectionDefs.concat([currentSelectionDef]);
60638 selectionTester = multiTester(allSelectionDefs);
60639
60640 for(i = 0; i < searchTraces.length; i++) {
60641 traceSelection = searchTraces[i]._module.selectPoints(searchTraces[i], selectionTester);
60642 thisTracesSelection = fillSelectionItem(traceSelection, searchTraces[i]);
60643
60644 if(selection.length) {
60645 for(var j = 0; j < thisTracesSelection.length; j++) {
60646 selection.push(thisTracesSelection[j]);
60647 }
60648 } else selection = thisTracesSelection;
60649 }
60650
60651 eventData = {points: selection};
60652 updateSelectedState(gd, searchTraces, eventData);
60653
60654 if(currentSelectionDef && dragOptions) {
60655 dragOptions.selectionDefs.push(currentSelectionDef);
60656 }
60657
60658 if(polygonOutlines) {
60659 var polygons = dragOptions.mergedPolygons;
60660 var isOpenMode = openMode(dragOptions.dragmode);
60661
60662 // display polygons on the screen
60663 displayOutlines(convertPoly(polygons, isOpenMode), polygonOutlines, dragOptions);
60664 }
60665
60666 if(sendEvents) {
60667 gd.emit('plotly_selected', eventData);
60668 }
60669 }
60670 }
60671}
60672
60673/**
60674 * Constructs a new point selection definition object.
60675 */
60676function newPointSelectionDef(pointNumber, searchInfo, subtract) {
60677 return {
60678 pointNumber: pointNumber,
60679 searchInfo: searchInfo,
60680 subtract: subtract
60681 };
60682}
60683
60684function isPointSelectionDef(o) {
60685 return 'pointNumber' in o && 'searchInfo' in o;
60686}
60687
60688/*
60689 * Constructs a new point number tester.
60690 */
60691function newPointNumTester(pointSelectionDef) {
60692 return {
60693 xmin: 0,
60694 xmax: 0,
60695 ymin: 0,
60696 ymax: 0,
60697 pts: [],
60698 contains: function(pt, omitFirstEdge, pointNumber, searchInfo) {
60699 var idxWantedTrace = pointSelectionDef.searchInfo.cd[0].trace._expandedIndex;
60700 var idxActualTrace = searchInfo.cd[0].trace._expandedIndex;
60701 return idxActualTrace === idxWantedTrace &&
60702 pointNumber === pointSelectionDef.pointNumber;
60703 },
60704 isRect: false,
60705 degenerate: false,
60706 subtract: pointSelectionDef.subtract
60707 };
60708}
60709
60710/**
60711 * Wraps multiple selection testers.
60712 *
60713 * @param {Array} list - An array of selection testers.
60714 *
60715 * @return a selection tester object with a contains function
60716 * that can be called to evaluate a point against all wrapped
60717 * selection testers that were passed in list.
60718 */
60719function multiTester(list) {
60720 var testers = [];
60721 var xmin = isPointSelectionDef(list[0]) ? 0 : list[0][0][0];
60722 var xmax = xmin;
60723 var ymin = isPointSelectionDef(list[0]) ? 0 : list[0][0][1];
60724 var ymax = ymin;
60725
60726 for(var i = 0; i < list.length; i++) {
60727 if(isPointSelectionDef(list[i])) {
60728 testers.push(newPointNumTester(list[i]));
60729 } else {
60730 var tester = polygon.tester(list[i]);
60731 tester.subtract = list[i].subtract;
60732 testers.push(tester);
60733 xmin = Math.min(xmin, tester.xmin);
60734 xmax = Math.max(xmax, tester.xmax);
60735 ymin = Math.min(ymin, tester.ymin);
60736 ymax = Math.max(ymax, tester.ymax);
60737 }
60738 }
60739
60740 /**
60741 * Tests if the given point is within this tester.
60742 *
60743 * @param {Array} pt - [0] is the x coordinate, [1] is the y coordinate of the point.
60744 * @param {*} arg - An optional parameter to pass down to wrapped testers.
60745 * @param {number} pointNumber - The point number of the point within the underlying data array.
60746 * @param {number} searchInfo - An object identifying the trace the point is contained in.
60747 *
60748 * @return {boolean} true if point is considered to be selected, false otherwise.
60749 */
60750 function contains(pt, arg, pointNumber, searchInfo) {
60751 var contained = false;
60752 for(var i = 0; i < testers.length; i++) {
60753 if(testers[i].contains(pt, arg, pointNumber, searchInfo)) {
60754 // if contained by subtract tester - exclude the point
60755 contained = testers[i].subtract === false;
60756 }
60757 }
60758
60759 return contained;
60760 }
60761
60762 return {
60763 xmin: xmin,
60764 xmax: xmax,
60765 ymin: ymin,
60766 ymax: ymax,
60767 pts: [],
60768 contains: contains,
60769 isRect: false,
60770 degenerate: false
60771 };
60772}
60773
60774function coerceSelectionsCache(evt, gd, dragOptions) {
60775 gd._fullLayout._drawing = false;
60776
60777 var fullLayout = gd._fullLayout;
60778 var plotinfo = dragOptions.plotinfo;
60779 var dragmode = dragOptions.dragmode;
60780
60781 var selectingOnSameSubplot = (
60782 fullLayout._lastSelectedSubplot &&
60783 fullLayout._lastSelectedSubplot === plotinfo.id
60784 );
60785
60786 var hasModifierKey = (evt.shiftKey || evt.altKey) &&
60787 !(drawMode(dragmode) && openMode(dragmode));
60788
60789 if(selectingOnSameSubplot && hasModifierKey &&
60790 (plotinfo.selection && plotinfo.selection.selectionDefs) && !dragOptions.selectionDefs) {
60791 // take over selection definitions from prev mode, if any
60792 dragOptions.selectionDefs = plotinfo.selection.selectionDefs;
60793 dragOptions.mergedPolygons = plotinfo.selection.mergedPolygons;
60794 } else if(!hasModifierKey || !plotinfo.selection) {
60795 clearSelectionsCache(dragOptions);
60796 }
60797
60798 // clear selection outline when selecting a different subplot
60799 if(!selectingOnSameSubplot) {
60800 clearSelect(gd);
60801 fullLayout._lastSelectedSubplot = plotinfo.id;
60802 }
60803}
60804
60805function clearSelectionsCache(dragOptions) {
60806 var dragmode = dragOptions.dragmode;
60807 var plotinfo = dragOptions.plotinfo;
60808
60809 var gd = dragOptions.gd;
60810 if(gd._fullLayout._activeShapeIndex >= 0) {
60811 gd._fullLayout._deactivateShape(gd);
60812 }
60813
60814 if(drawMode(dragmode)) {
60815 var fullLayout = gd._fullLayout;
60816 var zoomLayer = fullLayout._zoomlayer;
60817
60818 var outlines = zoomLayer.selectAll('.select-outline-' + plotinfo.id);
60819 if(outlines && gd._fullLayout._drawing) {
60820 // add shape
60821 var shapes = newShapes(outlines, dragOptions);
60822 if(shapes) {
60823 Registry.call('_guiRelayout', gd, {
60824 shapes: shapes
60825 });
60826 }
60827
60828 gd._fullLayout._drawing = false;
60829 }
60830 }
60831
60832 plotinfo.selection = {};
60833 plotinfo.selection.selectionDefs = dragOptions.selectionDefs = [];
60834 plotinfo.selection.mergedPolygons = dragOptions.mergedPolygons = [];
60835}
60836
60837function determineSearchTraces(gd, xAxes, yAxes, subplot) {
60838 var searchTraces = [];
60839 var xAxisIds = xAxes.map(function(ax) { return ax._id; });
60840 var yAxisIds = yAxes.map(function(ax) { return ax._id; });
60841 var cd, trace, i;
60842
60843 for(i = 0; i < gd.calcdata.length; i++) {
60844 cd = gd.calcdata[i];
60845 trace = cd[0].trace;
60846
60847 if(trace.visible !== true || !trace._module || !trace._module.selectPoints) continue;
60848
60849 if(subplot && (trace.subplot === subplot || trace.geo === subplot)) {
60850 searchTraces.push(createSearchInfo(trace._module, cd, xAxes[0], yAxes[0]));
60851 } else if(
60852 trace.type === 'splom' &&
60853 // FIXME: make sure we don't have more than single axis for splom
60854 trace._xaxes[xAxisIds[0]] && trace._yaxes[yAxisIds[0]]
60855 ) {
60856 var info = createSearchInfo(trace._module, cd, xAxes[0], yAxes[0]);
60857 info.scene = gd._fullLayout._splomScenes[trace.uid];
60858 searchTraces.push(info);
60859 } else if(
60860 trace.type === 'sankey'
60861 ) {
60862 var sankeyInfo = createSearchInfo(trace._module, cd, xAxes[0], yAxes[0]);
60863 searchTraces.push(sankeyInfo);
60864 } else {
60865 if(xAxisIds.indexOf(trace.xaxis) === -1) continue;
60866 if(yAxisIds.indexOf(trace.yaxis) === -1) continue;
60867
60868 searchTraces.push(createSearchInfo(trace._module, cd,
60869 getFromId(gd, trace.xaxis), getFromId(gd, trace.yaxis)));
60870 }
60871 }
60872
60873 return searchTraces;
60874
60875 function createSearchInfo(module, calcData, xaxis, yaxis) {
60876 return {
60877 _module: module,
60878 cd: calcData,
60879 xaxis: xaxis,
60880 yaxis: yaxis
60881 };
60882 }
60883}
60884
60885function isHoverDataSet(hoverData) {
60886 return hoverData &&
60887 Array.isArray(hoverData) &&
60888 hoverData[0].hoverOnBox !== true;
60889}
60890
60891function extractClickedPtInfo(hoverData, searchTraces) {
60892 var hoverDatum = hoverData[0];
60893 var pointNumber = -1;
60894 var pointNumbers = [];
60895 var searchInfo, i;
60896
60897 for(i = 0; i < searchTraces.length; i++) {
60898 searchInfo = searchTraces[i];
60899 if(hoverDatum.fullData._expandedIndex === searchInfo.cd[0].trace._expandedIndex) {
60900 // Special case for box (and violin)
60901 if(hoverDatum.hoverOnBox === true) {
60902 break;
60903 }
60904
60905 // Hint: in some traces like histogram, one graphical element
60906 // doesn't correspond to one particular data point, but to
60907 // bins of data points. Thus, hoverDatum can have a binNumber
60908 // property instead of pointNumber.
60909 if(hoverDatum.pointNumber !== undefined) {
60910 pointNumber = hoverDatum.pointNumber;
60911 } else if(hoverDatum.binNumber !== undefined) {
60912 pointNumber = hoverDatum.binNumber;
60913 pointNumbers = hoverDatum.pointNumbers;
60914 }
60915
60916 break;
60917 }
60918 }
60919
60920 return {
60921 pointNumber: pointNumber,
60922 pointNumbers: pointNumbers,
60923 searchInfo: searchInfo
60924 };
60925}
60926
60927function isPointOrBinSelected(clickedPtInfo) {
60928 var trace = clickedPtInfo.searchInfo.cd[0].trace;
60929 var ptNum = clickedPtInfo.pointNumber;
60930 var ptNums = clickedPtInfo.pointNumbers;
60931 var ptNumsSet = ptNums.length > 0;
60932
60933 // When pointsNumbers is set (e.g. histogram's binning),
60934 // it is assumed that when the first point of
60935 // a bin is selected, all others are as well
60936 var ptNumToTest = ptNumsSet ? ptNums[0] : ptNum;
60937
60938 // TODO potential performance improvement
60939 // Primarily we need this function to determine if a click adds
60940 // or subtracts from a selection.
60941 // In cases `trace.selectedpoints` is a huge array, indexOf
60942 // might be slow. One remedy would be to introduce a hash somewhere.
60943 return trace.selectedpoints ? trace.selectedpoints.indexOf(ptNumToTest) > -1 : false;
60944}
60945
60946function isOnlyThisBinSelected(searchTraces, clickedPtInfo) {
60947 var tracesWithSelectedPts = [];
60948 var searchInfo, trace, isSameTrace, i;
60949
60950 for(i = 0; i < searchTraces.length; i++) {
60951 searchInfo = searchTraces[i];
60952 if(searchInfo.cd[0].trace.selectedpoints && searchInfo.cd[0].trace.selectedpoints.length > 0) {
60953 tracesWithSelectedPts.push(searchInfo);
60954 }
60955 }
60956
60957 if(tracesWithSelectedPts.length === 1) {
60958 isSameTrace = tracesWithSelectedPts[0] === clickedPtInfo.searchInfo;
60959 if(isSameTrace) {
60960 trace = clickedPtInfo.searchInfo.cd[0].trace;
60961 if(trace.selectedpoints.length === clickedPtInfo.pointNumbers.length) {
60962 for(i = 0; i < clickedPtInfo.pointNumbers.length; i++) {
60963 if(trace.selectedpoints.indexOf(clickedPtInfo.pointNumbers[i]) < 0) {
60964 return false;
60965 }
60966 }
60967 return true;
60968 }
60969 }
60970 }
60971
60972 return false;
60973}
60974
60975function isOnlyOnePointSelected(searchTraces) {
60976 var len = 0;
60977 var searchInfo, trace, i;
60978
60979 for(i = 0; i < searchTraces.length; i++) {
60980 searchInfo = searchTraces[i];
60981 trace = searchInfo.cd[0].trace;
60982 if(trace.selectedpoints) {
60983 if(trace.selectedpoints.length > 1) return false;
60984
60985 len += trace.selectedpoints.length;
60986 if(len > 1) return false;
60987 }
60988 }
60989
60990 return len === 1;
60991}
60992
60993function updateSelectedState(gd, searchTraces, eventData) {
60994 var i, searchInfo, cd, trace;
60995
60996 // before anything else, update preGUI if necessary
60997 for(i = 0; i < searchTraces.length; i++) {
60998 var fullInputTrace = searchTraces[i].cd[0].trace._fullInput;
60999 var tracePreGUI = gd._fullLayout._tracePreGUI[fullInputTrace.uid] || {};
61000 if(tracePreGUI.selectedpoints === undefined) {
61001 tracePreGUI.selectedpoints = fullInputTrace._input.selectedpoints || null;
61002 }
61003 }
61004
61005 if(eventData) {
61006 var pts = eventData.points || [];
61007
61008 for(i = 0; i < searchTraces.length; i++) {
61009 trace = searchTraces[i].cd[0].trace;
61010 trace._input.selectedpoints = trace._fullInput.selectedpoints = [];
61011 if(trace._fullInput !== trace) trace.selectedpoints = [];
61012 }
61013
61014 for(i = 0; i < pts.length; i++) {
61015 var pt = pts[i];
61016 var data = pt.data;
61017 var fullData = pt.fullData;
61018
61019 if(pt.pointIndices) {
61020 [].push.apply(data.selectedpoints, pt.pointIndices);
61021 if(trace._fullInput !== trace) {
61022 [].push.apply(fullData.selectedpoints, pt.pointIndices);
61023 }
61024 } else {
61025 data.selectedpoints.push(pt.pointIndex);
61026 if(trace._fullInput !== trace) {
61027 fullData.selectedpoints.push(pt.pointIndex);
61028 }
61029 }
61030 }
61031 } else {
61032 for(i = 0; i < searchTraces.length; i++) {
61033 trace = searchTraces[i].cd[0].trace;
61034 delete trace.selectedpoints;
61035 delete trace._input.selectedpoints;
61036 if(trace._fullInput !== trace) {
61037 delete trace._fullInput.selectedpoints;
61038 }
61039 }
61040 }
61041
61042 var hasRegl = false;
61043
61044 for(i = 0; i < searchTraces.length; i++) {
61045 searchInfo = searchTraces[i];
61046 cd = searchInfo.cd;
61047 trace = cd[0].trace;
61048
61049 if(Registry.traceIs(trace, 'regl')) {
61050 hasRegl = true;
61051 }
61052
61053 var _module = searchInfo._module;
61054 var fn = _module.styleOnSelect || _module.style;
61055 if(fn) {
61056 fn(gd, cd, cd[0].node3);
61057 if(cd[0].nodeRangePlot3) fn(gd, cd, cd[0].nodeRangePlot3);
61058 }
61059 }
61060
61061 if(hasRegl) {
61062 clearGlCanvases(gd);
61063 redrawReglTraces(gd);
61064 }
61065}
61066
61067function mergePolygons(list, poly, subtract) {
61068 var res;
61069
61070 if(subtract) {
61071 res = polybool.difference({
61072 regions: list,
61073 inverted: false
61074 }, {
61075 regions: [poly],
61076 inverted: false
61077 });
61078
61079 return res.regions;
61080 }
61081
61082 res = polybool.union({
61083 regions: list,
61084 inverted: false
61085 }, {
61086 regions: [poly],
61087 inverted: false
61088 });
61089
61090 return res.regions;
61091}
61092
61093function fillSelectionItem(selection, searchInfo) {
61094 if(Array.isArray(selection)) {
61095 var cd = searchInfo.cd;
61096 var trace = searchInfo.cd[0].trace;
61097
61098 for(var i = 0; i < selection.length; i++) {
61099 selection[i] = makeEventData(selection[i], trace, cd);
61100 }
61101 }
61102
61103 return selection;
61104}
61105
61106function convertPoly(polygonsIn, isOpenMode) { // add M and L command to draft positions
61107 var polygonsOut = [];
61108 for(var i = 0; i < polygonsIn.length; i++) {
61109 polygonsOut[i] = [];
61110 for(var j = 0; j < polygonsIn[i].length; j++) {
61111 polygonsOut[i][j] = [];
61112 polygonsOut[i][j][0] = j ? 'L' : 'M';
61113 for(var k = 0; k < polygonsIn[i][j].length; k++) {
61114 polygonsOut[i][j].push(
61115 polygonsIn[i][j][k]
61116 );
61117 }
61118 }
61119
61120 if(!isOpenMode) {
61121 polygonsOut[i].push([
61122 'Z',
61123 polygonsOut[i][0][1], // initial x
61124 polygonsOut[i][0][2] // initial y
61125 ]);
61126 }
61127 }
61128
61129 return polygonsOut;
61130}
61131
61132module.exports = {
61133 prepSelect: prepSelect,
61134 clearSelect: clearSelect,
61135 clearSelectionsCache: clearSelectionsCache,
61136 selectOnClick: selectOnClick
61137};
61138
61139},{"../../components/color":52,"../../components/dragelement/helpers":70,"../../components/drawing":74,"../../components/fx":92,"../../components/fx/helpers":88,"../../components/shapes/draw_newshape/display_outlines":137,"../../components/shapes/draw_newshape/helpers":138,"../../components/shapes/draw_newshape/newshapes":139,"../../lib":178,"../../lib/clear_gl_canvases":167,"../../lib/polygon":190,"../../lib/throttle":200,"../../plot_api/subroutines":213,"../../registry":269,"./axis_ids":225,"./constants":228,"./handle_outline":232,"./helpers":233,"polybooljs":26}],242:[function(_dereq_,module,exports){
61140/**
61141* Copyright 2012-2020, Plotly, Inc.
61142* All rights reserved.
61143*
61144* This source code is licensed under the MIT license found in the
61145* LICENSE file in the root directory of this source tree.
61146*/
61147
61148'use strict';
61149
61150var d3 = _dereq_('d3');
61151var isNumeric = _dereq_('fast-isnumeric');
61152
61153var Lib = _dereq_('../../lib');
61154var cleanNumber = Lib.cleanNumber;
61155var ms2DateTime = Lib.ms2DateTime;
61156var dateTime2ms = Lib.dateTime2ms;
61157var ensureNumber = Lib.ensureNumber;
61158var isArrayOrTypedArray = Lib.isArrayOrTypedArray;
61159
61160var numConstants = _dereq_('../../constants/numerical');
61161var FP_SAFE = numConstants.FP_SAFE;
61162var BADNUM = numConstants.BADNUM;
61163var LOG_CLIP = numConstants.LOG_CLIP;
61164var ONEDAY = numConstants.ONEDAY;
61165var ONEHOUR = numConstants.ONEHOUR;
61166var ONEMIN = numConstants.ONEMIN;
61167var ONESEC = numConstants.ONESEC;
61168
61169var axisIds = _dereq_('./axis_ids');
61170
61171var constants = _dereq_('./constants');
61172var HOUR_PATTERN = constants.HOUR_PATTERN;
61173var WEEKDAY_PATTERN = constants.WEEKDAY_PATTERN;
61174
61175function fromLog(v) {
61176 return Math.pow(10, v);
61177}
61178
61179function isValidCategory(v) {
61180 return v !== null && v !== undefined;
61181}
61182
61183/**
61184 * Define the conversion functions for an axis data is used in 5 ways:
61185 *
61186 * d: data, in whatever form it's provided
61187 * c: calcdata: turned into numbers, but not linearized
61188 * l: linearized - same as c except for log axes (and other nonlinear
61189 * mappings later?) this is used when we need to know if it's
61190 * *possible* to show some data on this axis, without caring about
61191 * the current range
61192 * p: pixel value - mapped to the screen with current size and zoom
61193 * r: ranges, tick0, and annotation positions match one of the above
61194 * but are handled differently for different types:
61195 * - linear and date: data format (d)
61196 * - category: calcdata format (c), and will stay that way because
61197 * the data format has no continuous mapping
61198 * - log: linearized (l) format
61199 * TODO: in v2.0 we plan to change it to data format. At that point
61200 * shapes will work the same way as ranges, tick0, and annotations
61201 * so they can use this conversion too.
61202 *
61203 * Creates/updates these conversion functions, and a few more utilities
61204 * like cleanRange, and makeCalcdata
61205 *
61206 * also clears the autotick constraints ._minDtick, ._forceTick0
61207 */
61208module.exports = function setConvert(ax, fullLayout) {
61209 fullLayout = fullLayout || {};
61210
61211 var axId = (ax._id || 'x');
61212 var axLetter = axId.charAt(0);
61213
61214 function toLog(v, clip) {
61215 if(v > 0) return Math.log(v) / Math.LN10;
61216
61217 else if(v <= 0 && clip && ax.range && ax.range.length === 2) {
61218 // clip NaN (ie past negative infinity) to LOG_CLIP axis
61219 // length past the negative edge
61220 var r0 = ax.range[0];
61221 var r1 = ax.range[1];
61222 return 0.5 * (r0 + r1 - 2 * LOG_CLIP * Math.abs(r0 - r1));
61223 } else return BADNUM;
61224 }
61225
61226 /*
61227 * wrapped dateTime2ms that:
61228 * - accepts ms numbers for backward compatibility
61229 * - inserts a dummy arg so calendar is the 3rd arg (see notes below).
61230 * - defaults to ax.calendar
61231 */
61232 function dt2ms(v, _, calendar, opts) {
61233 // NOTE: Changed this behavior: previously we took any numeric value
61234 // to be a ms, even if it was a string that could be a bare year.
61235 // Now we convert it as a date if at all possible, and only try
61236 // as (local) ms if that fails.
61237 var ms = dateTime2ms(v, calendar || ax.calendar);
61238 if(ms === BADNUM) {
61239 if(isNumeric(v)) {
61240 v = +v;
61241 if((opts || {}).msUTC) {
61242 // For now it is only used
61243 // to fix bar length in milliseconds.
61244 // It could be applied in other places in v2
61245 return v;
61246 }
61247
61248 // keep track of tenths of ms, that `new Date` will drop
61249 // same logic as in Lib.ms2DateTime
61250 var msecTenths = Math.floor(Lib.mod(v + 0.05, 1) * 10);
61251 var msRounded = Math.round(v - msecTenths / 10);
61252 ms = dateTime2ms(new Date(msRounded)) + msecTenths / 10;
61253 } else return BADNUM;
61254 }
61255 return ms;
61256 }
61257
61258 // wrapped ms2DateTime to insert default ax.calendar
61259 function ms2dt(v, r, calendar) {
61260 return ms2DateTime(v, r, calendar || ax.calendar);
61261 }
61262
61263 function getCategoryName(v) {
61264 return ax._categories[Math.round(v)];
61265 }
61266
61267 /*
61268 * setCategoryIndex: return the index of category v,
61269 * inserting it in the list if it's not already there
61270 *
61271 * this will enter the categories in the order it
61272 * encounters them, ie all the categories from the
61273 * first data set, then all the ones from the second
61274 * that aren't in the first etc.
61275 *
61276 * it is assumed that this function is being invoked in the
61277 * already sorted category order; otherwise there would be
61278 * a disconnect between the array and the index returned
61279 */
61280 function setCategoryIndex(v) {
61281 if(isValidCategory(v)) {
61282 if(ax._categoriesMap === undefined) {
61283 ax._categoriesMap = {};
61284 }
61285
61286 if(ax._categoriesMap[v] !== undefined) {
61287 return ax._categoriesMap[v];
61288 } else {
61289 ax._categories.push(typeof v === 'number' ? String(v) : v);
61290
61291 var curLength = ax._categories.length - 1;
61292 ax._categoriesMap[v] = curLength;
61293
61294 return curLength;
61295 }
61296 }
61297 return BADNUM;
61298 }
61299
61300 function setMultiCategoryIndex(arrayIn, len) {
61301 var arrayOut = new Array(len);
61302
61303 for(var i = 0; i < len; i++) {
61304 var v0 = (arrayIn[0] || [])[i];
61305 var v1 = (arrayIn[1] || [])[i];
61306 arrayOut[i] = getCategoryIndex([v0, v1]);
61307 }
61308
61309 return arrayOut;
61310 }
61311
61312 function getCategoryIndex(v) {
61313 if(ax._categoriesMap) {
61314 return ax._categoriesMap[v];
61315 }
61316 }
61317
61318 function getCategoryPosition(v) {
61319 // d2l/d2c variant that that won't add categories but will also
61320 // allow numbers to be mapped to the linearized axis positions
61321 var index = getCategoryIndex(v);
61322 if(index !== undefined) return index;
61323 if(isNumeric(v)) return +v;
61324 }
61325
61326 // include 2 fractional digits on pixel, for PDF zooming etc
61327 function _l2p(v, m, b) { return d3.round(b + m * v, 2); }
61328
61329 function _p2l(px, m, b) { return (px - b) / m; }
61330
61331 var l2p = function l2p(v) {
61332 if(!isNumeric(v)) return BADNUM;
61333 return _l2p(v, ax._m, ax._b);
61334 };
61335
61336 var p2l = function(px) {
61337 return _p2l(px, ax._m, ax._b);
61338 };
61339
61340 if(ax.rangebreaks) {
61341 var isY = axLetter === 'y';
61342
61343 l2p = function(v) {
61344 if(!isNumeric(v)) return BADNUM;
61345 var len = ax._rangebreaks.length;
61346 if(!len) return _l2p(v, ax._m, ax._b);
61347
61348 var flip = isY;
61349 if(ax.range[0] > ax.range[1]) flip = !flip;
61350 var signAx = flip ? -1 : 1;
61351 var pos = signAx * v;
61352
61353 var q = 0;
61354 for(var i = 0; i < len; i++) {
61355 var min = signAx * ax._rangebreaks[i].min;
61356 var max = signAx * ax._rangebreaks[i].max;
61357
61358 if(pos < min) break;
61359 if(pos > max) q = i + 1;
61360 else {
61361 // when falls into break, pick 'closest' offset
61362 q = pos < (min + max) / 2 ? i : i + 1;
61363 break;
61364 }
61365 }
61366 var b2 = ax._B[q] || 0;
61367 if(!isFinite(b2)) return 0; // avoid NaN translate e.g. in positionLabels if one keep zooming exactly into a break
61368 return _l2p(v, ax._m2, b2);
61369 };
61370
61371 p2l = function(px) {
61372 var len = ax._rangebreaks.length;
61373 if(!len) return _p2l(px, ax._m, ax._b);
61374
61375 var q = 0;
61376 for(var i = 0; i < len; i++) {
61377 if(px < ax._rangebreaks[i].pmin) break;
61378 if(px > ax._rangebreaks[i].pmax) q = i + 1;
61379 }
61380 return _p2l(px, ax._m2, ax._B[q]);
61381 };
61382 }
61383
61384 // conversions among c/l/p are fairly simple - do them together for all axis types
61385 ax.c2l = (ax.type === 'log') ? toLog : ensureNumber;
61386 ax.l2c = (ax.type === 'log') ? fromLog : ensureNumber;
61387
61388 ax.l2p = l2p;
61389 ax.p2l = p2l;
61390
61391 ax.c2p = (ax.type === 'log') ? function(v, clip) { return l2p(toLog(v, clip)); } : l2p;
61392 ax.p2c = (ax.type === 'log') ? function(px) { return fromLog(p2l(px)); } : p2l;
61393
61394 /*
61395 * now type-specific conversions for **ALL** other combinations
61396 * they're all written out, instead of being combinations of each other, for
61397 * both clarity and speed.
61398 */
61399 if(['linear', '-'].indexOf(ax.type) !== -1) {
61400 // all are data vals, but d and r need cleaning
61401 ax.d2r = ax.r2d = ax.d2c = ax.r2c = ax.d2l = ax.r2l = cleanNumber;
61402 ax.c2d = ax.c2r = ax.l2d = ax.l2r = ensureNumber;
61403
61404 ax.d2p = ax.r2p = function(v) { return ax.l2p(cleanNumber(v)); };
61405 ax.p2d = ax.p2r = p2l;
61406
61407 ax.cleanPos = ensureNumber;
61408 } else if(ax.type === 'log') {
61409 // d and c are data vals, r and l are logged (but d and r need cleaning)
61410 ax.d2r = ax.d2l = function(v, clip) { return toLog(cleanNumber(v), clip); };
61411 ax.r2d = ax.r2c = function(v) { return fromLog(cleanNumber(v)); };
61412
61413 ax.d2c = ax.r2l = cleanNumber;
61414 ax.c2d = ax.l2r = ensureNumber;
61415
61416 ax.c2r = toLog;
61417 ax.l2d = fromLog;
61418
61419 ax.d2p = function(v, clip) { return ax.l2p(ax.d2r(v, clip)); };
61420 ax.p2d = function(px) { return fromLog(p2l(px)); };
61421
61422 ax.r2p = function(v) { return ax.l2p(cleanNumber(v)); };
61423 ax.p2r = p2l;
61424
61425 ax.cleanPos = ensureNumber;
61426 } else if(ax.type === 'date') {
61427 // r and d are date strings, l and c are ms
61428
61429 /*
61430 * Any of these functions with r and d on either side, calendar is the
61431 * **3rd** argument. log has reserved the second argument.
61432 *
61433 * Unless you need the special behavior of the second arg (ms2DateTime
61434 * uses this to limit precision, toLog uses true to clip negatives
61435 * to offscreen low rather than undefined), it's safe to pass 0.
61436 */
61437 ax.d2r = ax.r2d = Lib.identity;
61438
61439 ax.d2c = ax.r2c = ax.d2l = ax.r2l = dt2ms;
61440 ax.c2d = ax.c2r = ax.l2d = ax.l2r = ms2dt;
61441
61442 ax.d2p = ax.r2p = function(v, _, calendar) { return ax.l2p(dt2ms(v, 0, calendar)); };
61443 ax.p2d = ax.p2r = function(px, r, calendar) { return ms2dt(p2l(px), r, calendar); };
61444
61445 ax.cleanPos = function(v) { return Lib.cleanDate(v, BADNUM, ax.calendar); };
61446 } else if(ax.type === 'category') {
61447 // d is categories (string)
61448 // c and l are indices (numbers)
61449 // r is categories or numbers
61450
61451 ax.d2c = ax.d2l = setCategoryIndex;
61452 ax.r2d = ax.c2d = ax.l2d = getCategoryName;
61453
61454 ax.d2r = ax.d2l_noadd = getCategoryPosition;
61455
61456 ax.r2c = function(v) {
61457 var index = getCategoryPosition(v);
61458 return index !== undefined ? index : ax.fraction2r(0.5);
61459 };
61460
61461 ax.l2r = ax.c2r = ensureNumber;
61462 ax.r2l = getCategoryPosition;
61463
61464 ax.d2p = function(v) { return ax.l2p(ax.r2c(v)); };
61465 ax.p2d = function(px) { return getCategoryName(p2l(px)); };
61466 ax.r2p = ax.d2p;
61467 ax.p2r = p2l;
61468
61469 ax.cleanPos = function(v) {
61470 if(typeof v === 'string' && v !== '') return v;
61471 return ensureNumber(v);
61472 };
61473 } else if(ax.type === 'multicategory') {
61474 // N.B. multicategory axes don't define d2c and d2l,
61475 // as 'data-to-calcdata' conversion needs to take into
61476 // account all data array items as in ax.makeCalcdata.
61477
61478 ax.r2d = ax.c2d = ax.l2d = getCategoryName;
61479 ax.d2r = ax.d2l_noadd = getCategoryPosition;
61480
61481 ax.r2c = function(v) {
61482 var index = getCategoryPosition(v);
61483 return index !== undefined ? index : ax.fraction2r(0.5);
61484 };
61485
61486 ax.r2c_just_indices = getCategoryIndex;
61487
61488 ax.l2r = ax.c2r = ensureNumber;
61489 ax.r2l = getCategoryPosition;
61490
61491 ax.d2p = function(v) { return ax.l2p(ax.r2c(v)); };
61492 ax.p2d = function(px) { return getCategoryName(p2l(px)); };
61493 ax.r2p = ax.d2p;
61494 ax.p2r = p2l;
61495
61496 ax.cleanPos = function(v) {
61497 if(Array.isArray(v) || (typeof v === 'string' && v !== '')) return v;
61498 return ensureNumber(v);
61499 };
61500
61501 ax.setupMultiCategory = function(fullData) {
61502 var traceIndices = ax._traceIndices;
61503 var i, j;
61504
61505 var matchGroups = fullLayout._axisMatchGroups;
61506 if(matchGroups && matchGroups.length && ax._categories.length === 0) {
61507 for(i = 0; i < matchGroups.length; i++) {
61508 var group = matchGroups[i];
61509 if(group[axId]) {
61510 for(var axId2 in group) {
61511 if(axId2 !== axId) {
61512 var ax2 = fullLayout[axisIds.id2name(axId2)];
61513 traceIndices = traceIndices.concat(ax2._traceIndices);
61514 }
61515 }
61516 }
61517 }
61518 }
61519
61520 // [ [cnt, {$cat: index}], for 1,2 ]
61521 var seen = [[0, {}], [0, {}]];
61522 // [ [arrayIn[0][i], arrayIn[1][i]], for i .. N ]
61523 var list = [];
61524
61525 for(i = 0; i < traceIndices.length; i++) {
61526 var trace = fullData[traceIndices[i]];
61527
61528 if(axLetter in trace) {
61529 var arrayIn = trace[axLetter];
61530 var len = trace._length || Lib.minRowLength(arrayIn);
61531
61532 if(isArrayOrTypedArray(arrayIn[0]) && isArrayOrTypedArray(arrayIn[1])) {
61533 for(j = 0; j < len; j++) {
61534 var v0 = arrayIn[0][j];
61535 var v1 = arrayIn[1][j];
61536
61537 if(isValidCategory(v0) && isValidCategory(v1)) {
61538 list.push([v0, v1]);
61539
61540 if(!(v0 in seen[0][1])) {
61541 seen[0][1][v0] = seen[0][0]++;
61542 }
61543 if(!(v1 in seen[1][1])) {
61544 seen[1][1][v1] = seen[1][0]++;
61545 }
61546 }
61547 }
61548 }
61549 }
61550 }
61551
61552 list.sort(function(a, b) {
61553 var ind0 = seen[0][1];
61554 var d = ind0[a[0]] - ind0[b[0]];
61555 if(d) return d;
61556
61557 var ind1 = seen[1][1];
61558 return ind1[a[1]] - ind1[b[1]];
61559 });
61560
61561 for(i = 0; i < list.length; i++) {
61562 setCategoryIndex(list[i]);
61563 }
61564 };
61565 }
61566
61567 // find the range value at the specified (linear) fraction of the axis
61568 ax.fraction2r = function(v) {
61569 var rl0 = ax.r2l(ax.range[0]);
61570 var rl1 = ax.r2l(ax.range[1]);
61571 return ax.l2r(rl0 + v * (rl1 - rl0));
61572 };
61573
61574 // find the fraction of the range at the specified range value
61575 ax.r2fraction = function(v) {
61576 var rl0 = ax.r2l(ax.range[0]);
61577 var rl1 = ax.r2l(ax.range[1]);
61578 return (ax.r2l(v) - rl0) / (rl1 - rl0);
61579 };
61580
61581 /*
61582 * cleanRange: make sure range is a couplet of valid & distinct values
61583 * keep numbers away from the limits of floating point numbers,
61584 * and dates away from the ends of our date system (+/- 9999 years)
61585 *
61586 * optional param rangeAttr: operate on a different attribute, like
61587 * ax._r, rather than ax.range
61588 */
61589 ax.cleanRange = function(rangeAttr, opts) {
61590 if(!opts) opts = {};
61591 if(!rangeAttr) rangeAttr = 'range';
61592
61593 var range = Lib.nestedProperty(ax, rangeAttr).get();
61594 var i, dflt;
61595
61596 if(ax.type === 'date') dflt = Lib.dfltRange(ax.calendar);
61597 else if(axLetter === 'y') dflt = constants.DFLTRANGEY;
61598 else dflt = opts.dfltRange || constants.DFLTRANGEX;
61599
61600 // make sure we don't later mutate the defaults
61601 dflt = dflt.slice();
61602
61603 if(ax.rangemode === 'tozero' || ax.rangemode === 'nonnegative') {
61604 dflt[0] = 0;
61605 }
61606
61607 if(!range || range.length !== 2) {
61608 Lib.nestedProperty(ax, rangeAttr).set(dflt);
61609 return;
61610 }
61611
61612 if(ax.type === 'date' && !ax.autorange) {
61613 // check if milliseconds or js date objects are provided for range
61614 // and convert to date strings
61615 range[0] = Lib.cleanDate(range[0], BADNUM, ax.calendar);
61616 range[1] = Lib.cleanDate(range[1], BADNUM, ax.calendar);
61617 }
61618
61619 for(i = 0; i < 2; i++) {
61620 if(ax.type === 'date') {
61621 if(!Lib.isDateTime(range[i], ax.calendar)) {
61622 ax[rangeAttr] = dflt;
61623 break;
61624 }
61625
61626 if(ax.r2l(range[0]) === ax.r2l(range[1])) {
61627 // split by +/- 1 second
61628 var linCenter = Lib.constrain(ax.r2l(range[0]),
61629 Lib.MIN_MS + 1000, Lib.MAX_MS - 1000);
61630 range[0] = ax.l2r(linCenter - 1000);
61631 range[1] = ax.l2r(linCenter + 1000);
61632 break;
61633 }
61634 } else {
61635 if(!isNumeric(range[i])) {
61636 if(isNumeric(range[1 - i])) {
61637 range[i] = range[1 - i] * (i ? 10 : 0.1);
61638 } else {
61639 ax[rangeAttr] = dflt;
61640 break;
61641 }
61642 }
61643
61644 if(range[i] < -FP_SAFE) range[i] = -FP_SAFE;
61645 else if(range[i] > FP_SAFE) range[i] = FP_SAFE;
61646
61647 if(range[0] === range[1]) {
61648 // somewhat arbitrary: split by 1 or 1ppm, whichever is bigger
61649 var inc = Math.max(1, Math.abs(range[0] * 1e-6));
61650 range[0] -= inc;
61651 range[1] += inc;
61652 }
61653 }
61654 }
61655 };
61656
61657 // set scaling to pixels
61658 ax.setScale = function(usePrivateRange) {
61659 var gs = fullLayout._size;
61660
61661 // make sure we have a domain (pull it in from the axis
61662 // this one is overlaying if necessary)
61663 if(ax.overlaying) {
61664 var ax2 = axisIds.getFromId({ _fullLayout: fullLayout }, ax.overlaying);
61665 ax.domain = ax2.domain;
61666 }
61667
61668 // While transitions are occurring, we get a double-transform
61669 // issue if we transform the drawn layer *and* use the new axis range to
61670 // draw the data. This allows us to construct setConvert using the pre-
61671 // interaction values of the range:
61672 var rangeAttr = (usePrivateRange && ax._r) ? '_r' : 'range';
61673 var calendar = ax.calendar;
61674 ax.cleanRange(rangeAttr);
61675
61676 var rl0 = ax.r2l(ax[rangeAttr][0], calendar);
61677 var rl1 = ax.r2l(ax[rangeAttr][1], calendar);
61678
61679 var isY = axLetter === 'y';
61680 if(isY) {
61681 ax._offset = gs.t + (1 - ax.domain[1]) * gs.h;
61682 ax._length = gs.h * (ax.domain[1] - ax.domain[0]);
61683 ax._m = ax._length / (rl0 - rl1);
61684 ax._b = -ax._m * rl1;
61685 } else {
61686 ax._offset = gs.l + ax.domain[0] * gs.w;
61687 ax._length = gs.w * (ax.domain[1] - ax.domain[0]);
61688 ax._m = ax._length / (rl1 - rl0);
61689 ax._b = -ax._m * rl0;
61690 }
61691
61692 // set of "N" disjoint rangebreaks inside the range
61693 ax._rangebreaks = [];
61694 // length of these rangebreaks in value space - negative on reversed axes
61695 ax._lBreaks = 0;
61696 // l2p slope (same for all intervals)
61697 ax._m2 = 0;
61698 // set of l2p offsets (one for each of the (N+1) piecewise intervals)
61699 ax._B = [];
61700
61701 if(ax.rangebreaks) {
61702 var i, brk;
61703
61704 ax._rangebreaks = ax.locateBreaks(
61705 Math.min(rl0, rl1),
61706 Math.max(rl0, rl1)
61707 );
61708
61709 if(ax._rangebreaks.length) {
61710 for(i = 0; i < ax._rangebreaks.length; i++) {
61711 brk = ax._rangebreaks[i];
61712 ax._lBreaks += Math.abs(brk.max - brk.min);
61713 }
61714
61715 var flip = isY;
61716 if(rl0 > rl1) flip = !flip;
61717 if(flip) ax._rangebreaks.reverse();
61718 var sign = flip ? -1 : 1;
61719
61720 ax._m2 = sign * ax._length / (Math.abs(rl1 - rl0) - ax._lBreaks);
61721 ax._B.push(-ax._m2 * (isY ? rl1 : rl0));
61722 for(i = 0; i < ax._rangebreaks.length; i++) {
61723 brk = ax._rangebreaks[i];
61724 ax._B.push(
61725 ax._B[ax._B.length - 1] -
61726 sign * ax._m2 * (brk.max - brk.min)
61727 );
61728 }
61729
61730 // fill pixel (i.e. 'p') min/max here,
61731 // to not have to loop through the _rangebreaks twice during `p2l`
61732 for(i = 0; i < ax._rangebreaks.length; i++) {
61733 brk = ax._rangebreaks[i];
61734 brk.pmin = l2p(brk.min);
61735 brk.pmax = l2p(brk.max);
61736 }
61737 }
61738 }
61739
61740 if(!isFinite(ax._m) || !isFinite(ax._b) || ax._length < 0) {
61741 fullLayout._replotting = false;
61742 throw new Error('Something went wrong with axis scaling');
61743 }
61744 };
61745
61746 ax.maskBreaks = function(v) {
61747 var rangebreaksIn = ax.rangebreaks || [];
61748 var bnds, b0, b1, vb, vDate;
61749
61750 for(var i = 0; i < rangebreaksIn.length; i++) {
61751 var brk = rangebreaksIn[i];
61752
61753 if(brk.enabled) {
61754 if(brk.bounds) {
61755 var pattern = brk.pattern;
61756 bnds = Lib.simpleMap(brk.bounds, pattern ?
61757 cleanNumber :
61758 ax.d2c // case of pattern: ''
61759 );
61760 b0 = bnds[0];
61761 b1 = bnds[1];
61762
61763 switch(pattern) {
61764 case WEEKDAY_PATTERN:
61765 vDate = new Date(v);
61766 vb = vDate.getUTCDay();
61767
61768 if(b0 > b1) {
61769 b1 += 7;
61770 if(vb < b0) vb += 7;
61771 }
61772
61773 break;
61774 case HOUR_PATTERN:
61775 vDate = new Date(v);
61776 var hours = vDate.getUTCHours();
61777 var minutes = vDate.getUTCMinutes();
61778 var seconds = vDate.getUTCSeconds();
61779 var milliseconds = vDate.getUTCMilliseconds();
61780
61781 vb = hours + (
61782 minutes / 60 +
61783 seconds / 3600 +
61784 milliseconds / 3600000
61785 );
61786
61787 if(b0 > b1) {
61788 b1 += 24;
61789 if(vb < b0) vb += 24;
61790 }
61791
61792 break;
61793 case '':
61794 // N.B. should work on date axes as well!
61795 // e.g. { bounds: ['2020-01-04', '2020-01-05 23:59'] }
61796 // TODO should work with reversed-range axes
61797 vb = v;
61798 break;
61799 }
61800
61801 if(vb >= b0 && vb < b1) return BADNUM;
61802 } else {
61803 var vals = Lib.simpleMap(brk.values, ax.d2c).sort(Lib.sorterAsc);
61804 for(var j = 0; j < vals.length; j++) {
61805 b0 = vals[j];
61806 b1 = b0 + brk.dvalue;
61807 if(v >= b0 && v < b1) return BADNUM;
61808 }
61809 }
61810 }
61811 }
61812 return v;
61813 };
61814
61815 ax.locateBreaks = function(r0, r1) {
61816 var i, bnds, b0, b1;
61817
61818 var rangebreaksOut = [];
61819 if(!ax.rangebreaks) return rangebreaksOut;
61820
61821 var rangebreaksIn = ax.rangebreaks.slice().sort(function(a, b) {
61822 if(a.pattern === WEEKDAY_PATTERN && b.pattern === HOUR_PATTERN) return -1;
61823 if(b.pattern === WEEKDAY_PATTERN && a.pattern === HOUR_PATTERN) return 1;
61824 return 0;
61825 });
61826
61827 var addBreak = function(min, max) {
61828 min = Lib.constrain(min, r0, r1);
61829 max = Lib.constrain(max, r0, r1);
61830 if(min === max) return;
61831
61832 var isNewBreak = true;
61833 for(var j = 0; j < rangebreaksOut.length; j++) {
61834 var brkj = rangebreaksOut[j];
61835 if(min < brkj.max && max >= brkj.min) {
61836 if(min < brkj.min) {
61837 brkj.min = min;
61838 }
61839 if(max > brkj.max) {
61840 brkj.max = max;
61841 }
61842 isNewBreak = false;
61843 }
61844 }
61845 if(isNewBreak) {
61846 rangebreaksOut.push({min: min, max: max});
61847 }
61848 };
61849
61850 for(i = 0; i < rangebreaksIn.length; i++) {
61851 var brk = rangebreaksIn[i];
61852
61853 if(brk.enabled) {
61854 if(brk.bounds) {
61855 var t0 = r0;
61856 var t1 = r1;
61857 if(brk.pattern) {
61858 // to remove decimal (most often found in auto ranges)
61859 t0 = Math.floor(t0);
61860 }
61861
61862 bnds = Lib.simpleMap(brk.bounds, brk.pattern ? cleanNumber : ax.r2l);
61863 b0 = bnds[0];
61864 b1 = bnds[1];
61865
61866 // r0 value as date
61867 var t0Date = new Date(t0);
61868 // r0 value for break pattern
61869 var bndDelta;
61870 // step in ms between rangebreaks
61871 var step;
61872
61873 switch(brk.pattern) {
61874 case WEEKDAY_PATTERN:
61875 step = 7 * ONEDAY;
61876
61877 bndDelta = (
61878 (b1 < b0 ? 7 : 0) +
61879 (b1 - b0)
61880 ) * ONEDAY;
61881
61882 t0 += b0 * ONEDAY - (
61883 t0Date.getUTCDay() * ONEDAY +
61884 t0Date.getUTCHours() * ONEHOUR +
61885 t0Date.getUTCMinutes() * ONEMIN +
61886 t0Date.getUTCSeconds() * ONESEC +
61887 t0Date.getUTCMilliseconds()
61888 );
61889 break;
61890 case HOUR_PATTERN:
61891 step = ONEDAY;
61892
61893 bndDelta = (
61894 (b1 < b0 ? 24 : 0) +
61895 (b1 - b0)
61896 ) * ONEHOUR;
61897
61898 t0 += b0 * ONEHOUR - (
61899 t0Date.getUTCHours() * ONEHOUR +
61900 t0Date.getUTCMinutes() * ONEMIN +
61901 t0Date.getUTCSeconds() * ONESEC +
61902 t0Date.getUTCMilliseconds()
61903 );
61904 break;
61905 default:
61906 t0 = Math.min(bnds[0], bnds[1]);
61907 t1 = Math.max(bnds[0], bnds[1]);
61908 step = t1 - t0;
61909 bndDelta = step;
61910 }
61911
61912 for(var t = t0; t < t1; t += step) {
61913 addBreak(t, t + bndDelta);
61914 }
61915 } else {
61916 var vals = Lib.simpleMap(brk.values, ax.d2c);
61917 for(var j = 0; j < vals.length; j++) {
61918 b0 = vals[j];
61919 b1 = b0 + brk.dvalue;
61920 addBreak(b0, b1);
61921 }
61922 }
61923 }
61924 }
61925
61926 rangebreaksOut.sort(function(a, b) { return a.min - b.min; });
61927
61928 return rangebreaksOut;
61929 };
61930
61931 // makeCalcdata: takes an x or y array and converts it
61932 // to a position on the axis object "ax"
61933 // inputs:
61934 // trace - a data object from gd.data
61935 // axLetter - a string, either 'x' or 'y', for which item
61936 // to convert (TODO: is this now always the same as
61937 // the first letter of ax._id?)
61938 // in case the expected data isn't there, make a list of
61939 // integers based on the opposite data
61940 ax.makeCalcdata = function(trace, axLetter, opts) {
61941 var arrayIn, arrayOut, i, len;
61942
61943 var axType = ax.type;
61944 var cal = axType === 'date' && trace[axLetter + 'calendar'];
61945
61946 if(axLetter in trace) {
61947 arrayIn = trace[axLetter];
61948 len = trace._length || Lib.minRowLength(arrayIn);
61949
61950 if(Lib.isTypedArray(arrayIn) && (axType === 'linear' || axType === 'log')) {
61951 if(len === arrayIn.length) {
61952 return arrayIn;
61953 } else if(arrayIn.subarray) {
61954 return arrayIn.subarray(0, len);
61955 }
61956 }
61957
61958 if(axType === 'multicategory') {
61959 return setMultiCategoryIndex(arrayIn, len);
61960 }
61961
61962 arrayOut = new Array(len);
61963 for(i = 0; i < len; i++) {
61964 arrayOut[i] = ax.d2c(arrayIn[i], 0, cal, opts);
61965 }
61966 } else {
61967 var v0 = ((axLetter + '0') in trace) ? ax.d2c(trace[axLetter + '0'], 0, cal) : 0;
61968 var dv = (trace['d' + axLetter]) ? Number(trace['d' + axLetter]) : 1;
61969
61970 // the opposing data, for size if we have x and dx etc
61971 arrayIn = trace[{x: 'y', y: 'x'}[axLetter]];
61972 len = trace._length || arrayIn.length;
61973 arrayOut = new Array(len);
61974
61975 for(i = 0; i < len; i++) {
61976 arrayOut[i] = v0 + i * dv;
61977 }
61978 }
61979
61980 // mask (i.e. set to BADNUM) coords that fall inside rangebreaks
61981 if(ax.rangebreaks) {
61982 for(i = 0; i < len; i++) {
61983 arrayOut[i] = ax.maskBreaks(arrayOut[i]);
61984 }
61985 }
61986
61987 return arrayOut;
61988 };
61989
61990 ax.isValidRange = function(range) {
61991 return (
61992 Array.isArray(range) &&
61993 range.length === 2 &&
61994 isNumeric(ax.r2l(range[0])) &&
61995 isNumeric(ax.r2l(range[1]))
61996 );
61997 };
61998
61999 ax.isPtWithinRange = function(d, calendar) {
62000 var coord = ax.c2l(d[axLetter], null, calendar);
62001 var r0 = ax.r2l(ax.range[0]);
62002 var r1 = ax.r2l(ax.range[1]);
62003
62004 if(r0 < r1) {
62005 return r0 <= coord && coord <= r1;
62006 } else {
62007 // Reversed axis case.
62008 return r1 <= coord && coord <= r0;
62009 }
62010 };
62011
62012 // should skip if not category nor multicategory
62013 ax.clearCalc = function() {
62014 var emptyCategories = function() {
62015 ax._categories = [];
62016 ax._categoriesMap = {};
62017 };
62018
62019 var matchGroups = fullLayout._axisMatchGroups;
62020
62021 if(matchGroups && matchGroups.length) {
62022 var found = false;
62023
62024 for(var i = 0; i < matchGroups.length; i++) {
62025 var group = matchGroups[i];
62026
62027 if(group[axId]) {
62028 found = true;
62029 var categories = null;
62030 var categoriesMap = null;
62031
62032 for(var axId2 in group) {
62033 var ax2 = fullLayout[axisIds.id2name(axId2)];
62034 if(ax2._categories) {
62035 categories = ax2._categories;
62036 categoriesMap = ax2._categoriesMap;
62037 break;
62038 }
62039 }
62040
62041 if(categories && categoriesMap) {
62042 ax._categories = categories;
62043 ax._categoriesMap = categoriesMap;
62044 } else {
62045 emptyCategories();
62046 }
62047 break;
62048 }
62049 }
62050 if(!found) emptyCategories();
62051 } else {
62052 emptyCategories();
62053 }
62054
62055 if(ax._initialCategories) {
62056 for(var j = 0; j < ax._initialCategories.length; j++) {
62057 setCategoryIndex(ax._initialCategories[j]);
62058 }
62059 }
62060 };
62061
62062 // sort the axis (and all the matching ones) by _initialCategories
62063 // returns the indices of the traces affected by the reordering
62064 ax.sortByInitialCategories = function() {
62065 var affectedTraces = [];
62066 var emptyCategories = function() {
62067 ax._categories = [];
62068 ax._categoriesMap = {};
62069 };
62070
62071 emptyCategories();
62072
62073 if(ax._initialCategories) {
62074 for(var j = 0; j < ax._initialCategories.length; j++) {
62075 setCategoryIndex(ax._initialCategories[j]);
62076 }
62077 }
62078
62079 affectedTraces = affectedTraces.concat(ax._traceIndices);
62080
62081 // Propagate to matching axes
62082 var group = ax._matchGroup;
62083 for(var axId2 in group) {
62084 if(axId === axId2) continue;
62085 var ax2 = fullLayout[axisIds.id2name(axId2)];
62086 ax2._categories = ax._categories;
62087 ax2._categoriesMap = ax._categoriesMap;
62088 affectedTraces = affectedTraces.concat(ax2._traceIndices);
62089 }
62090 return affectedTraces;
62091 };
62092
62093 // Propagate localization into the axis so that
62094 // methods in Axes can use it w/o having to pass fullLayout
62095 // Default (non-d3) number formatting uses separators directly
62096 // dates and d3-formatted numbers use the d3 locale
62097 // Fall back on default format for dummy axes that don't care about formatting
62098 var locale = fullLayout._d3locale;
62099 if(ax.type === 'date') {
62100 ax._dateFormat = locale ? locale.timeFormat.utc : d3.time.format.utc;
62101 ax._extraFormat = fullLayout._extraFormat;
62102 }
62103 // occasionally we need _numFormat to pass through
62104 // even though it won't be needed by this axis
62105 ax._separators = fullLayout.separators;
62106 ax._numFormat = locale ? locale.numberFormat : d3.format;
62107
62108 // and for bar charts and box plots: reset forced minimum tick spacing
62109 delete ax._minDtick;
62110 delete ax._forceTick0;
62111};
62112
62113},{"../../constants/numerical":158,"../../lib":178,"./axis_ids":225,"./constants":228,"d3":16,"fast-isnumeric":18}],243:[function(_dereq_,module,exports){
62114/**
62115* Copyright 2012-2020, Plotly, Inc.
62116* All rights reserved.
62117*
62118* This source code is licensed under the MIT license found in the
62119* LICENSE file in the root directory of this source tree.
62120*/
62121
62122
62123'use strict';
62124
62125var Lib = _dereq_('../../lib');
62126var layoutAttributes = _dereq_('./layout_attributes');
62127var handleArrayContainerDefaults = _dereq_('../array_container_defaults');
62128
62129module.exports = function handleTickLabelDefaults(containerIn, containerOut, coerce, axType, options, config) {
62130 if(!config || config.pass === 1) {
62131 handlePrefixSuffix(containerIn, containerOut, coerce, axType, options);
62132 }
62133
62134 if(!config || config.pass === 2) {
62135 handleOtherDefaults(containerIn, containerOut, coerce, axType, options);
62136 }
62137};
62138
62139function handlePrefixSuffix(containerIn, containerOut, coerce, axType, options) {
62140 var showAttrDflt = getShowAttrDflt(containerIn);
62141
62142 var tickPrefix = coerce('tickprefix');
62143 if(tickPrefix) coerce('showtickprefix', showAttrDflt);
62144
62145 var tickSuffix = coerce('ticksuffix', options.tickSuffixDflt);
62146 if(tickSuffix) coerce('showticksuffix', showAttrDflt);
62147}
62148
62149function handleOtherDefaults(containerIn, containerOut, coerce, axType, options) {
62150 var showAttrDflt = getShowAttrDflt(containerIn);
62151
62152 var tickPrefix = coerce('tickprefix');
62153 if(tickPrefix) coerce('showtickprefix', showAttrDflt);
62154
62155 var tickSuffix = coerce('ticksuffix', options.tickSuffixDflt);
62156 if(tickSuffix) coerce('showticksuffix', showAttrDflt);
62157
62158 var showTickLabels = coerce('showticklabels');
62159 if(showTickLabels) {
62160 var font = options.font || {};
62161 var contColor = containerOut.color;
62162 // as with titlefont.color, inherit axis.color only if one was
62163 // explicitly provided
62164 var dfltFontColor = (contColor && contColor !== layoutAttributes.color.dflt) ?
62165 contColor : font.color;
62166 Lib.coerceFont(coerce, 'tickfont', {
62167 family: font.family,
62168 size: font.size,
62169 color: dfltFontColor
62170 });
62171 coerce('tickangle');
62172
62173 if(axType !== 'category') {
62174 var tickFormat = coerce('tickformat');
62175
62176 handleArrayContainerDefaults(containerIn, containerOut, {
62177 name: 'tickformatstops',
62178 inclusionAttr: 'enabled',
62179 handleItemDefaults: tickformatstopDefaults
62180 });
62181 if(!containerOut.tickformatstops.length) {
62182 delete containerOut.tickformatstops;
62183 }
62184
62185 if(!tickFormat && axType !== 'date') {
62186 coerce('showexponent', showAttrDflt);
62187 coerce('exponentformat');
62188 coerce('separatethousands');
62189 }
62190 }
62191 }
62192}
62193
62194/*
62195 * Attributes 'showexponent', 'showtickprefix' and 'showticksuffix'
62196 * share values.
62197 *
62198 * If only 1 attribute is set,
62199 * the remaining attributes inherit that value.
62200 *
62201 * If 2 attributes are set to the same value,
62202 * the remaining attribute inherits that value.
62203 *
62204 * If 2 attributes are set to different values,
62205 * the remaining is set to its dflt value.
62206 *
62207 */
62208function getShowAttrDflt(containerIn) {
62209 var showAttrsAll = ['showexponent', 'showtickprefix', 'showticksuffix'];
62210 var showAttrs = showAttrsAll.filter(function(a) {
62211 return containerIn[a] !== undefined;
62212 });
62213 var sameVal = function(a) {
62214 return containerIn[a] === containerIn[showAttrs[0]];
62215 };
62216
62217 if(showAttrs.every(sameVal) || showAttrs.length === 1) {
62218 return containerIn[showAttrs[0]];
62219 }
62220}
62221
62222function tickformatstopDefaults(valueIn, valueOut) {
62223 function coerce(attr, dflt) {
62224 return Lib.coerce(valueIn, valueOut, layoutAttributes.tickformatstops, attr, dflt);
62225 }
62226
62227 var enabled = coerce('enabled');
62228 if(enabled) {
62229 coerce('dtickrange');
62230 coerce('value');
62231 }
62232}
62233
62234},{"../../lib":178,"../array_container_defaults":218,"./layout_attributes":236}],244:[function(_dereq_,module,exports){
62235/**
62236* Copyright 2012-2020, Plotly, Inc.
62237* All rights reserved.
62238*
62239* This source code is licensed under the MIT license found in the
62240* LICENSE file in the root directory of this source tree.
62241*/
62242
62243
62244'use strict';
62245
62246var Lib = _dereq_('../../lib');
62247
62248var layoutAttributes = _dereq_('./layout_attributes');
62249
62250
62251/**
62252 * options: inherits outerTicks from axes.handleAxisDefaults
62253 */
62254module.exports = function handleTickDefaults(containerIn, containerOut, coerce, options) {
62255 var tickLen = Lib.coerce2(containerIn, containerOut, layoutAttributes, 'ticklen');
62256 var tickWidth = Lib.coerce2(containerIn, containerOut, layoutAttributes, 'tickwidth');
62257 var tickColor = Lib.coerce2(containerIn, containerOut, layoutAttributes, 'tickcolor', containerOut.color);
62258 var showTicks = coerce('ticks', (options.outerTicks || tickLen || tickWidth || tickColor) ? 'outside' : '');
62259
62260 if(!showTicks) {
62261 delete containerOut.ticklen;
62262 delete containerOut.tickwidth;
62263 delete containerOut.tickcolor;
62264 }
62265};
62266
62267},{"../../lib":178,"./layout_attributes":236}],245:[function(_dereq_,module,exports){
62268/**
62269* Copyright 2012-2020, Plotly, Inc.
62270* All rights reserved.
62271*
62272* This source code is licensed under the MIT license found in the
62273* LICENSE file in the root directory of this source tree.
62274*/
62275
62276'use strict';
62277
62278var cleanTicks = _dereq_('./clean_ticks');
62279var isArrayOrTypedArray = _dereq_('../../lib').isArrayOrTypedArray;
62280
62281module.exports = function handleTickValueDefaults(containerIn, containerOut, coerce, axType) {
62282 function readInput(attr) {
62283 var v = containerIn[attr];
62284 return (
62285 v !== undefined
62286 ) ? v : (containerOut._template || {})[attr];
62287 }
62288
62289 var _tick0 = readInput('tick0');
62290 var _dtick = readInput('dtick');
62291 var _tickvals = readInput('tickvals');
62292
62293 var tickmodeDefault = isArrayOrTypedArray(_tickvals) ? 'array' :
62294 _dtick ? 'linear' :
62295 'auto';
62296 var tickmode = coerce('tickmode', tickmodeDefault);
62297
62298 if(tickmode === 'auto') coerce('nticks');
62299 else if(tickmode === 'linear') {
62300 // dtick is usually a positive number, but there are some
62301 // special strings available for log or date axes
62302 // tick0 also has special logic
62303 var dtick = containerOut.dtick = cleanTicks.dtick(
62304 _dtick, axType);
62305 containerOut.tick0 = cleanTicks.tick0(
62306 _tick0, axType, containerOut.calendar, dtick);
62307 } else if(axType !== 'multicategory') {
62308 var tickvals = coerce('tickvals');
62309 if(tickvals === undefined) containerOut.tickmode = 'auto';
62310 else coerce('ticktext');
62311 }
62312};
62313
62314},{"../../lib":178,"./clean_ticks":227}],246:[function(_dereq_,module,exports){
62315/**
62316* Copyright 2012-2020, Plotly, Inc.
62317* All rights reserved.
62318*
62319* This source code is licensed under the MIT license found in the
62320* LICENSE file in the root directory of this source tree.
62321*/
62322
62323'use strict';
62324
62325var d3 = _dereq_('d3');
62326
62327var Registry = _dereq_('../../registry');
62328var Lib = _dereq_('../../lib');
62329var Drawing = _dereq_('../../components/drawing');
62330var Axes = _dereq_('./axes');
62331
62332/**
62333 * transitionAxes
62334 *
62335 * transition axes from one set of ranges to another, using a svg
62336 * transformations, similar to during panning.
62337 *
62338 * @param {DOM element | object} gd
62339 * @param {array} edits : array of 'edits', each item with
62340 * - plotinfo {object} subplot object
62341 * - xr0 {array} initial x-range
62342 * - xr1 {array} end x-range
62343 * - yr0 {array} initial y-range
62344 * - yr1 {array} end y-range
62345 * @param {object} transitionOpts
62346 * @param {function} makeOnCompleteCallback
62347 */
62348module.exports = function transitionAxes(gd, edits, transitionOpts, makeOnCompleteCallback) {
62349 var fullLayout = gd._fullLayout;
62350
62351 // special case for redraw:false Plotly.animate that relies on this
62352 // to update axis-referenced layout components
62353 if(edits.length === 0) {
62354 Axes.redrawComponents(gd);
62355 return;
62356 }
62357
62358 function unsetSubplotTransform(subplot) {
62359 var xa = subplot.xaxis;
62360 var ya = subplot.yaxis;
62361
62362 fullLayout._defs.select('#' + subplot.clipId + '> rect')
62363 .call(Drawing.setTranslate, 0, 0)
62364 .call(Drawing.setScale, 1, 1);
62365
62366 subplot.plot
62367 .call(Drawing.setTranslate, xa._offset, ya._offset)
62368 .call(Drawing.setScale, 1, 1);
62369
62370 var traceGroups = subplot.plot.selectAll('.scatterlayer .trace');
62371
62372 // This is specifically directed at scatter traces, applying an inverse
62373 // scale to individual points to counteract the scale of the trace
62374 // as a whole:
62375 traceGroups.selectAll('.point')
62376 .call(Drawing.setPointGroupScale, 1, 1);
62377 traceGroups.selectAll('.textpoint')
62378 .call(Drawing.setTextPointsScale, 1, 1);
62379 traceGroups
62380 .call(Drawing.hideOutsideRangePoints, subplot);
62381 }
62382
62383 function updateSubplot(edit, progress) {
62384 var plotinfo = edit.plotinfo;
62385 var xa = plotinfo.xaxis;
62386 var ya = plotinfo.yaxis;
62387 var xlen = xa._length;
62388 var ylen = ya._length;
62389 var editX = !!edit.xr1;
62390 var editY = !!edit.yr1;
62391 var viewBox = [];
62392
62393 if(editX) {
62394 var xr0 = Lib.simpleMap(edit.xr0, xa.r2l);
62395 var xr1 = Lib.simpleMap(edit.xr1, xa.r2l);
62396 var dx0 = xr0[1] - xr0[0];
62397 var dx1 = xr1[1] - xr1[0];
62398 viewBox[0] = (xr0[0] * (1 - progress) + progress * xr1[0] - xr0[0]) / (xr0[1] - xr0[0]) * xlen;
62399 viewBox[2] = xlen * ((1 - progress) + progress * dx1 / dx0);
62400 xa.range[0] = xa.l2r(xr0[0] * (1 - progress) + progress * xr1[0]);
62401 xa.range[1] = xa.l2r(xr0[1] * (1 - progress) + progress * xr1[1]);
62402 } else {
62403 viewBox[0] = 0;
62404 viewBox[2] = xlen;
62405 }
62406
62407 if(editY) {
62408 var yr0 = Lib.simpleMap(edit.yr0, ya.r2l);
62409 var yr1 = Lib.simpleMap(edit.yr1, ya.r2l);
62410 var dy0 = yr0[1] - yr0[0];
62411 var dy1 = yr1[1] - yr1[0];
62412 viewBox[1] = (yr0[1] * (1 - progress) + progress * yr1[1] - yr0[1]) / (yr0[0] - yr0[1]) * ylen;
62413 viewBox[3] = ylen * ((1 - progress) + progress * dy1 / dy0);
62414 ya.range[0] = xa.l2r(yr0[0] * (1 - progress) + progress * yr1[0]);
62415 ya.range[1] = ya.l2r(yr0[1] * (1 - progress) + progress * yr1[1]);
62416 } else {
62417 viewBox[1] = 0;
62418 viewBox[3] = ylen;
62419 }
62420
62421 Axes.drawOne(gd, xa, {skipTitle: true});
62422 Axes.drawOne(gd, ya, {skipTitle: true});
62423 Axes.redrawComponents(gd, [xa._id, ya._id]);
62424
62425 var xScaleFactor = editX ? xlen / viewBox[2] : 1;
62426 var yScaleFactor = editY ? ylen / viewBox[3] : 1;
62427 var clipDx = editX ? viewBox[0] : 0;
62428 var clipDy = editY ? viewBox[1] : 0;
62429 var fracDx = editX ? (viewBox[0] / viewBox[2] * xlen) : 0;
62430 var fracDy = editY ? (viewBox[1] / viewBox[3] * ylen) : 0;
62431 var plotDx = xa._offset - fracDx;
62432 var plotDy = ya._offset - fracDy;
62433
62434 plotinfo.clipRect
62435 .call(Drawing.setTranslate, clipDx, clipDy)
62436 .call(Drawing.setScale, 1 / xScaleFactor, 1 / yScaleFactor);
62437
62438 plotinfo.plot
62439 .call(Drawing.setTranslate, plotDx, plotDy)
62440 .call(Drawing.setScale, xScaleFactor, yScaleFactor);
62441
62442 // apply an inverse scale to individual points to counteract
62443 // the scale of the trace group.
62444 Drawing.setPointGroupScale(plotinfo.zoomScalePts, 1 / xScaleFactor, 1 / yScaleFactor);
62445 Drawing.setTextPointsScale(plotinfo.zoomScaleTxt, 1 / xScaleFactor, 1 / yScaleFactor);
62446 }
62447
62448 var onComplete;
62449 if(makeOnCompleteCallback) {
62450 // This module makes the choice whether or not it notifies Plotly.transition
62451 // about completion:
62452 onComplete = makeOnCompleteCallback();
62453 }
62454
62455 function transitionComplete() {
62456 var aobj = {};
62457
62458 for(var i = 0; i < edits.length; i++) {
62459 var edit = edits[i];
62460 var xa = edit.plotinfo.xaxis;
62461 var ya = edit.plotinfo.yaxis;
62462 if(edit.xr1) aobj[xa._name + '.range'] = edit.xr1.slice();
62463 if(edit.yr1) aobj[ya._name + '.range'] = edit.yr1.slice();
62464 }
62465
62466 // Signal that this transition has completed:
62467 onComplete && onComplete();
62468
62469 return Registry.call('relayout', gd, aobj).then(function() {
62470 for(var i = 0; i < edits.length; i++) {
62471 unsetSubplotTransform(edits[i].plotinfo);
62472 }
62473 });
62474 }
62475
62476 function transitionInterrupt() {
62477 var aobj = {};
62478
62479 for(var i = 0; i < edits.length; i++) {
62480 var edit = edits[i];
62481 var xa = edit.plotinfo.xaxis;
62482 var ya = edit.plotinfo.yaxis;
62483 if(edit.xr0) aobj[xa._name + '.range'] = edit.xr0.slice();
62484 if(edit.yr0) aobj[ya._name + '.range'] = edit.yr0.slice();
62485 }
62486
62487 return Registry.call('relayout', gd, aobj).then(function() {
62488 for(var i = 0; i < edits.length; i++) {
62489 unsetSubplotTransform(edits[i].plotinfo);
62490 }
62491 });
62492 }
62493
62494 var t1, t2, raf;
62495 var easeFn = d3.ease(transitionOpts.easing);
62496
62497 gd._transitionData._interruptCallbacks.push(function() {
62498 window.cancelAnimationFrame(raf);
62499 raf = null;
62500 return transitionInterrupt();
62501 });
62502
62503 function doFrame() {
62504 t2 = Date.now();
62505
62506 var tInterp = Math.min(1, (t2 - t1) / transitionOpts.duration);
62507 var progress = easeFn(tInterp);
62508
62509 for(var i = 0; i < edits.length; i++) {
62510 updateSubplot(edits[i], progress);
62511 }
62512
62513 if(t2 - t1 > transitionOpts.duration) {
62514 transitionComplete();
62515 raf = window.cancelAnimationFrame(doFrame);
62516 } else {
62517 raf = window.requestAnimationFrame(doFrame);
62518 }
62519 }
62520
62521 t1 = Date.now();
62522 raf = window.requestAnimationFrame(doFrame);
62523
62524 return Promise.resolve();
62525};
62526
62527},{"../../components/drawing":74,"../../lib":178,"../../registry":269,"./axes":222,"d3":16}],247:[function(_dereq_,module,exports){
62528/**
62529* Copyright 2012-2020, Plotly, Inc.
62530* All rights reserved.
62531*
62532* This source code is licensed under the MIT license found in the
62533* LICENSE file in the root directory of this source tree.
62534*/
62535
62536'use strict';
62537
62538var traceIs = _dereq_('../../registry').traceIs;
62539var autoType = _dereq_('./axis_autotype');
62540
62541/*
62542 * data: the plot data to use in choosing auto type
62543 * name: axis object name (ie 'xaxis') if one should be stored
62544 */
62545module.exports = function handleTypeDefaults(containerIn, containerOut, coerce, options) {
62546 var axType = coerce('type', (options.splomStash || {}).type);
62547
62548 if(axType === '-') {
62549 setAutoType(containerOut, options.data);
62550
62551 if(containerOut.type === '-') {
62552 containerOut.type = 'linear';
62553 } else {
62554 // copy autoType back to input axis
62555 // note that if this object didn't exist
62556 // in the input layout, we have to put it in
62557 // this happens in the main supplyDefaults function
62558 containerIn.type = containerOut.type;
62559 }
62560 }
62561};
62562
62563function setAutoType(ax, data) {
62564 // new logic: let people specify any type they want,
62565 // only autotype if type is '-'
62566 if(ax.type !== '-') return;
62567
62568 var id = ax._id;
62569 var axLetter = id.charAt(0);
62570 var i;
62571
62572 // support 3d
62573 if(id.indexOf('scene') !== -1) id = axLetter;
62574
62575 var d0 = getFirstNonEmptyTrace(data, id, axLetter);
62576 if(!d0) return;
62577
62578 // first check for histograms, as the count direction
62579 // should always default to a linear axis
62580 if(d0.type === 'histogram' &&
62581 axLetter === {v: 'y', h: 'x'}[d0.orientation || 'v']
62582 ) {
62583 ax.type = 'linear';
62584 return;
62585 }
62586
62587 var calAttr = axLetter + 'calendar';
62588 var calendar = d0[calAttr];
62589 var opts = {noMultiCategory: !traceIs(d0, 'cartesian') || traceIs(d0, 'noMultiCategory')};
62590
62591 // To not confuse 2D x/y used for per-box sample points for multicategory coordinates
62592 if(d0.type === 'box' && d0._hasPreCompStats &&
62593 axLetter === {h: 'x', v: 'y'}[d0.orientation || 'v']
62594 ) {
62595 opts.noMultiCategory = true;
62596 }
62597
62598 // check all boxes on this x axis to see
62599 // if they're dates, numbers, or categories
62600 if(isBoxWithoutPositionCoords(d0, axLetter)) {
62601 var posLetter = getBoxPosLetter(d0);
62602 var boxPositions = [];
62603
62604 for(i = 0; i < data.length; i++) {
62605 var trace = data[i];
62606 if(!traceIs(trace, 'box-violin') || (trace[axLetter + 'axis'] || axLetter) !== id) continue;
62607
62608 if(trace[posLetter] !== undefined) boxPositions.push(trace[posLetter][0]);
62609 else if(trace.name !== undefined) boxPositions.push(trace.name);
62610 else boxPositions.push('text');
62611
62612 if(trace[calAttr] !== calendar) calendar = undefined;
62613 }
62614
62615 ax.type = autoType(boxPositions, calendar, opts);
62616 } else if(d0.type === 'splom') {
62617 var dimensions = d0.dimensions;
62618 var dim = dimensions[d0._axesDim[id]];
62619 if(dim.visible) ax.type = autoType(dim.values, calendar, opts);
62620 } else {
62621 ax.type = autoType(d0[axLetter] || [d0[axLetter + '0']], calendar, opts);
62622 }
62623}
62624
62625function getFirstNonEmptyTrace(data, id, axLetter) {
62626 for(var i = 0; i < data.length; i++) {
62627 var trace = data[i];
62628
62629 if(trace.type === 'splom' &&
62630 trace._length > 0 &&
62631 (trace['_' + axLetter + 'axes'] || {})[id]
62632 ) {
62633 return trace;
62634 }
62635
62636 if((trace[axLetter + 'axis'] || axLetter) === id) {
62637 if(isBoxWithoutPositionCoords(trace, axLetter)) {
62638 return trace;
62639 } else if((trace[axLetter] || []).length || trace[axLetter + '0']) {
62640 return trace;
62641 }
62642 }
62643 }
62644}
62645
62646function getBoxPosLetter(trace) {
62647 return {v: 'x', h: 'y'}[trace.orientation || 'v'];
62648}
62649
62650function isBoxWithoutPositionCoords(trace, axLetter) {
62651 var posLetter = getBoxPosLetter(trace);
62652 var isBox = traceIs(trace, 'box-violin');
62653 var isCandlestick = traceIs(trace._fullInput || {}, 'candlestick');
62654
62655 return (
62656 isBox &&
62657 !isCandlestick &&
62658 axLetter === posLetter &&
62659 trace[posLetter] === undefined &&
62660 trace[posLetter + '0'] === undefined
62661 );
62662}
62663
62664},{"../../registry":269,"./axis_autotype":223}],248:[function(_dereq_,module,exports){
62665/**
62666* Copyright 2012-2020, Plotly, Inc.
62667* All rights reserved.
62668*
62669* This source code is licensed under the MIT license found in the
62670* LICENSE file in the root directory of this source tree.
62671*/
62672
62673'use strict';
62674
62675var Registry = _dereq_('../registry');
62676var Lib = _dereq_('../lib');
62677
62678/*
62679 * Create or update an observer. This function is designed to be
62680 * idempotent so that it can be called over and over as the component
62681 * updates, and will attach and detach listeners as needed.
62682 *
62683 * @param {optional object} container
62684 * An object on which the observer is stored. This is the mechanism
62685 * by which it is idempotent. If it already exists, another won't be
62686 * added. Each time it's called, the value lookup table is updated.
62687 * @param {array} commandList
62688 * An array of commands, following either `buttons` of `updatemenus`
62689 * or `steps` of `sliders`.
62690 * @param {function} onchange
62691 * A listener called when the value is changed. Receives data object
62692 * with information about the new state.
62693 */
62694exports.manageCommandObserver = function(gd, container, commandList, onchange) {
62695 var ret = {};
62696 var enabled = true;
62697
62698 if(container && container._commandObserver) {
62699 ret = container._commandObserver;
62700 }
62701
62702 if(!ret.cache) {
62703 ret.cache = {};
62704 }
62705
62706 // Either create or just recompute this:
62707 ret.lookupTable = {};
62708
62709 var binding = exports.hasSimpleAPICommandBindings(gd, commandList, ret.lookupTable);
62710
62711 if(container && container._commandObserver) {
62712 if(!binding) {
62713 // If container exists and there are no longer any bindings,
62714 // remove existing:
62715 if(container._commandObserver.remove) {
62716 container._commandObserver.remove();
62717 container._commandObserver = null;
62718 return ret;
62719 }
62720 } else {
62721 // If container exists and there *are* bindings, then the lookup
62722 // table should have been updated and check is already attached,
62723 // so there's nothing to be done:
62724 return ret;
62725 }
62726 }
62727
62728 // Determine whether there's anything to do for this binding:
62729
62730 if(binding) {
62731 // Build the cache:
62732 bindingValueHasChanged(gd, binding, ret.cache);
62733
62734 ret.check = function check() {
62735 if(!enabled) return;
62736
62737 var update = bindingValueHasChanged(gd, binding, ret.cache);
62738
62739 if(update.changed && onchange) {
62740 // Disable checks for the duration of this command in order to avoid
62741 // infinite loops:
62742 if(ret.lookupTable[update.value] !== undefined) {
62743 ret.disable();
62744 Promise.resolve(onchange({
62745 value: update.value,
62746 type: binding.type,
62747 prop: binding.prop,
62748 traces: binding.traces,
62749 index: ret.lookupTable[update.value]
62750 })).then(ret.enable, ret.enable);
62751 }
62752 }
62753
62754 return update.changed;
62755 };
62756
62757 var checkEvents = [
62758 'plotly_relayout',
62759 'plotly_redraw',
62760 'plotly_restyle',
62761 'plotly_update',
62762 'plotly_animatingframe',
62763 'plotly_afterplot'
62764 ];
62765
62766 for(var i = 0; i < checkEvents.length; i++) {
62767 gd._internalOn(checkEvents[i], ret.check);
62768 }
62769
62770 ret.remove = function() {
62771 for(var i = 0; i < checkEvents.length; i++) {
62772 gd._removeInternalListener(checkEvents[i], ret.check);
62773 }
62774 };
62775 } else {
62776 // TODO: It'd be really neat to actually give a *reason* for this, but at least a warning
62777 // is a start
62778 Lib.log('Unable to automatically bind plot updates to API command');
62779
62780 ret.lookupTable = {};
62781 ret.remove = function() {};
62782 }
62783
62784 ret.disable = function disable() {
62785 enabled = false;
62786 };
62787
62788 ret.enable = function enable() {
62789 enabled = true;
62790 };
62791
62792 if(container) {
62793 container._commandObserver = ret;
62794 }
62795
62796 return ret;
62797};
62798
62799/*
62800 * This function checks to see if an array of objects containing
62801 * method and args properties is compatible with automatic two-way
62802 * binding. The criteria right now are that
62803 *
62804 * 1. multiple traces may be affected
62805 * 2. only one property may be affected
62806 * 3. the same property must be affected by all commands
62807 */
62808exports.hasSimpleAPICommandBindings = function(gd, commandList, bindingsByValue) {
62809 var i;
62810 var n = commandList.length;
62811
62812 var refBinding;
62813
62814 for(i = 0; i < n; i++) {
62815 var binding;
62816 var command = commandList[i];
62817 var method = command.method;
62818 var args = command.args;
62819
62820 if(!Array.isArray(args)) args = [];
62821
62822 // If any command has no method, refuse to bind:
62823 if(!method) {
62824 return false;
62825 }
62826 var bindings = exports.computeAPICommandBindings(gd, method, args);
62827
62828 // Right now, handle one and *only* one property being set:
62829 if(bindings.length !== 1) {
62830 return false;
62831 }
62832
62833 if(!refBinding) {
62834 refBinding = bindings[0];
62835 if(Array.isArray(refBinding.traces)) {
62836 refBinding.traces.sort();
62837 }
62838 } else {
62839 binding = bindings[0];
62840 if(binding.type !== refBinding.type) {
62841 return false;
62842 }
62843 if(binding.prop !== refBinding.prop) {
62844 return false;
62845 }
62846 if(Array.isArray(refBinding.traces)) {
62847 if(Array.isArray(binding.traces)) {
62848 binding.traces.sort();
62849 for(var j = 0; j < refBinding.traces.length; j++) {
62850 if(refBinding.traces[j] !== binding.traces[j]) {
62851 return false;
62852 }
62853 }
62854 } else {
62855 return false;
62856 }
62857 } else {
62858 if(binding.prop !== refBinding.prop) {
62859 return false;
62860 }
62861 }
62862 }
62863
62864 binding = bindings[0];
62865 var value = binding.value;
62866 if(Array.isArray(value)) {
62867 if(value.length === 1) {
62868 value = value[0];
62869 } else {
62870 return false;
62871 }
62872 }
62873 if(bindingsByValue) {
62874 bindingsByValue[value] = i;
62875 }
62876 }
62877
62878 return refBinding;
62879};
62880
62881function bindingValueHasChanged(gd, binding, cache) {
62882 var container, value, obj;
62883 var changed = false;
62884
62885 if(binding.type === 'data') {
62886 // If it's data, we need to get a trace. Based on the limited scope
62887 // of what we cover, we can just take the first trace from the list,
62888 // or otherwise just the first trace:
62889 container = gd._fullData[binding.traces !== null ? binding.traces[0] : 0];
62890 } else if(binding.type === 'layout') {
62891 container = gd._fullLayout;
62892 } else {
62893 return false;
62894 }
62895
62896 value = Lib.nestedProperty(container, binding.prop).get();
62897
62898 obj = cache[binding.type] = cache[binding.type] || {};
62899
62900 if(obj.hasOwnProperty(binding.prop)) {
62901 if(obj[binding.prop] !== value) {
62902 changed = true;
62903 }
62904 }
62905
62906 obj[binding.prop] = value;
62907
62908 return {
62909 changed: changed,
62910 value: value
62911 };
62912}
62913
62914/*
62915 * Execute an API command. There's really not much to this; it just provides
62916 * a common hook so that implementations don't need to be synchronized across
62917 * multiple components with the ability to invoke API commands.
62918 *
62919 * @param {string} method
62920 * The name of the plotly command to execute. Must be one of 'animate',
62921 * 'restyle', 'relayout', 'update'.
62922 * @param {array} args
62923 * A list of arguments passed to the API command
62924 */
62925exports.executeAPICommand = function(gd, method, args) {
62926 if(method === 'skip') return Promise.resolve();
62927
62928 var _method = Registry.apiMethodRegistry[method];
62929 var allArgs = [gd];
62930 if(!Array.isArray(args)) args = [];
62931
62932 for(var i = 0; i < args.length; i++) {
62933 allArgs.push(args[i]);
62934 }
62935
62936 return _method.apply(null, allArgs).catch(function(err) {
62937 Lib.warn('API call to Plotly.' + method + ' rejected.', err);
62938 return Promise.reject(err);
62939 });
62940};
62941
62942exports.computeAPICommandBindings = function(gd, method, args) {
62943 var bindings;
62944
62945 if(!Array.isArray(args)) args = [];
62946
62947 switch(method) {
62948 case 'restyle':
62949 bindings = computeDataBindings(gd, args);
62950 break;
62951 case 'relayout':
62952 bindings = computeLayoutBindings(gd, args);
62953 break;
62954 case 'update':
62955 bindings = computeDataBindings(gd, [args[0], args[2]])
62956 .concat(computeLayoutBindings(gd, [args[1]]));
62957 break;
62958 case 'animate':
62959 bindings = computeAnimateBindings(gd, args);
62960 break;
62961 default:
62962 // This is the case where intelligent logic about what affects
62963 // this command is not implemented. It causes no ill effects.
62964 // For example, addFrames simply won't bind to a control component.
62965 bindings = [];
62966 }
62967 return bindings;
62968};
62969
62970function computeAnimateBindings(gd, args) {
62971 // We'll assume that the only relevant modification an animation
62972 // makes that's meaningfully tracked is the frame:
62973 if(Array.isArray(args[0]) && args[0].length === 1 && ['string', 'number'].indexOf(typeof args[0][0]) !== -1) {
62974 return [{type: 'layout', prop: '_currentFrame', value: args[0][0].toString()}];
62975 } else {
62976 return [];
62977 }
62978}
62979
62980function computeLayoutBindings(gd, args) {
62981 var bindings = [];
62982
62983 var astr = args[0];
62984 var aobj = {};
62985 if(typeof astr === 'string') {
62986 aobj[astr] = args[1];
62987 } else if(Lib.isPlainObject(astr)) {
62988 aobj = astr;
62989 } else {
62990 return bindings;
62991 }
62992
62993 crawl(aobj, function(path, attrName, attr) {
62994 bindings.push({type: 'layout', prop: path, value: attr});
62995 }, '', 0);
62996
62997 return bindings;
62998}
62999
63000function computeDataBindings(gd, args) {
63001 var traces, astr, val, aobj;
63002 var bindings = [];
63003
63004 // Logic copied from Plotly.restyle:
63005 astr = args[0];
63006 val = args[1];
63007 traces = args[2];
63008 aobj = {};
63009 if(typeof astr === 'string') {
63010 aobj[astr] = val;
63011 } else if(Lib.isPlainObject(astr)) {
63012 // the 3-arg form
63013 aobj = astr;
63014
63015 if(traces === undefined) {
63016 traces = val;
63017 }
63018 } else {
63019 return bindings;
63020 }
63021
63022 if(traces === undefined) {
63023 // Explicitly assign this to null instead of undefined:
63024 traces = null;
63025 }
63026
63027 crawl(aobj, function(path, attrName, _attr) {
63028 var thisTraces;
63029 var attr;
63030
63031 if(Array.isArray(_attr)) {
63032 attr = _attr.slice();
63033
63034 var nAttr = Math.min(attr.length, gd.data.length);
63035 if(traces) {
63036 nAttr = Math.min(nAttr, traces.length);
63037 }
63038 thisTraces = [];
63039 for(var j = 0; j < nAttr; j++) {
63040 thisTraces[j] = traces ? traces[j] : j;
63041 }
63042 } else {
63043 attr = _attr;
63044 thisTraces = traces ? traces.slice() : null;
63045 }
63046
63047 // Convert [7] to just 7 when traces is null:
63048 if(thisTraces === null) {
63049 if(Array.isArray(attr)) {
63050 attr = attr[0];
63051 }
63052 } else if(Array.isArray(thisTraces)) {
63053 if(!Array.isArray(attr)) {
63054 var tmp = attr;
63055 attr = [];
63056 for(var i = 0; i < thisTraces.length; i++) {
63057 attr[i] = tmp;
63058 }
63059 }
63060 attr.length = Math.min(thisTraces.length, attr.length);
63061 }
63062
63063 bindings.push({
63064 type: 'data',
63065 prop: path,
63066 traces: thisTraces,
63067 value: attr
63068 });
63069 }, '', 0);
63070
63071 return bindings;
63072}
63073
63074function crawl(attrs, callback, path, depth) {
63075 Object.keys(attrs).forEach(function(attrName) {
63076 var attr = attrs[attrName];
63077
63078 if(attrName[0] === '_') return;
63079
63080 var thisPath = path + (depth > 0 ? '.' : '') + attrName;
63081
63082 if(Lib.isPlainObject(attr)) {
63083 crawl(attr, callback, thisPath, depth + 1);
63084 } else {
63085 // Only execute the callback on leaf nodes:
63086 callback(thisPath, attrName, attr);
63087 }
63088 });
63089}
63090
63091},{"../lib":178,"../registry":269}],249:[function(_dereq_,module,exports){
63092/**
63093* Copyright 2012-2020, Plotly, Inc.
63094* All rights reserved.
63095*
63096* This source code is licensed under the MIT license found in the
63097* LICENSE file in the root directory of this source tree.
63098*/
63099
63100'use strict';
63101
63102var extendFlat = _dereq_('../lib/extend').extendFlat;
63103
63104/**
63105 * Make a xy domain attribute group
63106 *
63107 * @param {object} opts
63108 * @param {string}
63109 * opts.name: name to be inserted in the default description
63110 * @param {boolean}
63111 * opts.trace: set to true for trace containers
63112 * @param {string}
63113 * opts.editType: editType for all pieces
63114 * @param {boolean}
63115 * opts.noGridCell: set to true to omit `row` and `column`
63116 *
63117 * @param {object} extra
63118 * @param {string}
63119 * extra.description: extra description. N.B we use
63120 * a separate extra container to make it compatible with
63121 * the compress_attributes transform.
63122 *
63123 * @return {object} attributes object containing {x,y} as specified
63124 */
63125exports.attributes = function(opts, extra) {
63126 opts = opts || {};
63127 extra = extra || {};
63128
63129 var base = {
63130 valType: 'info_array',
63131
63132 editType: opts.editType,
63133 items: [
63134 {valType: 'number', min: 0, max: 1, editType: opts.editType},
63135 {valType: 'number', min: 0, max: 1, editType: opts.editType}
63136 ],
63137 dflt: [0, 1]
63138 };
63139
63140 var namePart = opts.name ? opts.name + ' ' : '';
63141 var contPart = opts.trace ? 'trace ' : 'subplot ';
63142 var descPart = extra.description ? ' ' + extra.description : '';
63143
63144 var out = {
63145 x: extendFlat({}, base, {
63146
63147 }),
63148 y: extendFlat({}, base, {
63149
63150 }),
63151 editType: opts.editType
63152 };
63153
63154 if(!opts.noGridCell) {
63155 out.row = {
63156 valType: 'integer',
63157 min: 0,
63158 dflt: 0,
63159
63160 editType: opts.editType,
63161
63162 };
63163 out.column = {
63164 valType: 'integer',
63165 min: 0,
63166 dflt: 0,
63167
63168 editType: opts.editType,
63169
63170 };
63171 }
63172
63173 return out;
63174};
63175
63176exports.defaults = function(containerOut, layout, coerce, dfltDomains) {
63177 var dfltX = (dfltDomains && dfltDomains.x) || [0, 1];
63178 var dfltY = (dfltDomains && dfltDomains.y) || [0, 1];
63179
63180 var grid = layout.grid;
63181 if(grid) {
63182 var column = coerce('domain.column');
63183 if(column !== undefined) {
63184 if(column < grid.columns) dfltX = grid._domains.x[column];
63185 else delete containerOut.domain.column;
63186 }
63187
63188 var row = coerce('domain.row');
63189 if(row !== undefined) {
63190 if(row < grid.rows) dfltY = grid._domains.y[row];
63191 else delete containerOut.domain.row;
63192 }
63193 }
63194
63195 var x = coerce('domain.x', dfltX);
63196 var y = coerce('domain.y', dfltY);
63197
63198 // don't accept bad input data
63199 if(!(x[0] < x[1])) containerOut.domain.x = dfltX.slice();
63200 if(!(y[0] < y[1])) containerOut.domain.y = dfltY.slice();
63201};
63202
63203},{"../lib/extend":173}],250:[function(_dereq_,module,exports){
63204/**
63205* Copyright 2012-2020, Plotly, Inc.
63206* All rights reserved.
63207*
63208* This source code is licensed under the MIT license found in the
63209* LICENSE file in the root directory of this source tree.
63210*/
63211
63212'use strict';
63213
63214/*
63215 * make a font attribute group
63216 *
63217 * @param {object} opts
63218 * @param {string}
63219 * opts.description: where & how this font is used
63220 * @param {optional bool} arrayOk:
63221 * should each part (family, size, color) be arrayOk? default false.
63222 * @param {string} editType:
63223 * the editType for all pieces of this font
63224 * @param {optional string} colorEditType:
63225 * a separate editType just for color
63226 *
63227 * @return {object} attributes object containing {family, size, color} as specified
63228 */
63229module.exports = function(opts) {
63230 var editType = opts.editType;
63231 var colorEditType = opts.colorEditType;
63232 if(colorEditType === undefined) colorEditType = editType;
63233 var attrs = {
63234 family: {
63235 valType: 'string',
63236
63237 noBlank: true,
63238 strict: true,
63239 editType: editType,
63240
63241 },
63242 size: {
63243 valType: 'number',
63244
63245 min: 1,
63246 editType: editType
63247 },
63248 color: {
63249 valType: 'color',
63250
63251 editType: colorEditType
63252 },
63253 editType: editType,
63254 // blank strings so compress_attributes can remove
63255 // TODO - that's uber hacky... better solution?
63256
63257 };
63258
63259 if(opts.arrayOk) {
63260 attrs.family.arrayOk = true;
63261 attrs.size.arrayOk = true;
63262 attrs.color.arrayOk = true;
63263 }
63264
63265 return attrs;
63266};
63267
63268},{}],251:[function(_dereq_,module,exports){
63269/**
63270* Copyright 2012-2020, Plotly, Inc.
63271* All rights reserved.
63272*
63273* This source code is licensed under the MIT license found in the
63274* LICENSE file in the root directory of this source tree.
63275*/
63276
63277'use strict';
63278
63279module.exports = {
63280 _isLinkedToArray: 'frames_entry',
63281
63282 group: {
63283 valType: 'string',
63284
63285
63286 },
63287 name: {
63288 valType: 'string',
63289
63290
63291 },
63292 traces: {
63293 valType: 'any',
63294
63295
63296 },
63297 baseframe: {
63298 valType: 'string',
63299
63300
63301 },
63302 data: {
63303 valType: 'any',
63304
63305
63306 },
63307 layout: {
63308 valType: 'any',
63309
63310
63311 }
63312};
63313
63314},{}],252:[function(_dereq_,module,exports){
63315/**
63316* Copyright 2012-2020, Plotly, Inc.
63317* All rights reserved.
63318*
63319* This source code is licensed under the MIT license found in the
63320* LICENSE file in the root directory of this source tree.
63321*/
63322
63323'use strict';
63324
63325var Registry = _dereq_('../registry');
63326var SUBPLOT_PATTERN = _dereq_('./cartesian/constants').SUBPLOT_PATTERN;
63327
63328/**
63329 * Get calcdata trace(s) associated with a given subplot
63330 *
63331 * @param {array} calcData: as in gd.calcdata
63332 * @param {string} type: subplot type
63333 * @param {string} subplotId: subplot id to look for
63334 *
63335 * @return {array} array of calcdata traces
63336 */
63337exports.getSubplotCalcData = function(calcData, type, subplotId) {
63338 var basePlotModule = Registry.subplotsRegistry[type];
63339 if(!basePlotModule) return [];
63340
63341 var attr = basePlotModule.attr;
63342 var subplotCalcData = [];
63343
63344 for(var i = 0; i < calcData.length; i++) {
63345 var calcTrace = calcData[i];
63346 var trace = calcTrace[0].trace;
63347
63348 if(trace[attr] === subplotId) subplotCalcData.push(calcTrace);
63349 }
63350
63351 return subplotCalcData;
63352};
63353/**
63354 * Get calcdata trace(s) that can be plotted with a given module
63355 * NOTE: this isn't necessarily just exactly matching trace type,
63356 * if multiple trace types use the same plotting routine, they will be
63357 * collected here.
63358 * In order to not plot the same thing multiple times, we return two arrays,
63359 * the calcdata we *will* plot with this module, and the ones we *won't*
63360 *
63361 * @param {array} calcdata: as in gd.calcdata
63362 * @param {object|string|fn} arg1:
63363 * the plotting module, or its name, or its plot method
63364 *
63365 * @return {array[array]} [foundCalcdata, remainingCalcdata]
63366 */
63367exports.getModuleCalcData = function(calcdata, arg1) {
63368 var moduleCalcData = [];
63369 var remainingCalcData = [];
63370
63371 var plotMethod;
63372 if(typeof arg1 === 'string') {
63373 plotMethod = Registry.getModule(arg1).plot;
63374 } else if(typeof arg1 === 'function') {
63375 plotMethod = arg1;
63376 } else {
63377 plotMethod = arg1.plot;
63378 }
63379 if(!plotMethod) {
63380 return [moduleCalcData, calcdata];
63381 }
63382
63383 for(var i = 0; i < calcdata.length; i++) {
63384 var cd = calcdata[i];
63385 var trace = cd[0].trace;
63386 // N.B.
63387 // - 'legendonly' traces do not make it past here
63388 // - skip over 'visible' traces that got trimmed completely during calc transforms
63389 if(trace.visible !== true || trace._length === 0) continue;
63390
63391 // group calcdata trace not by 'module' (as the name of this function
63392 // would suggest), but by 'module plot method' so that if some traces
63393 // share the same module plot method (e.g. bar and histogram), we
63394 // only call it one!
63395 if(trace._module.plot === plotMethod) {
63396 moduleCalcData.push(cd);
63397 } else {
63398 remainingCalcData.push(cd);
63399 }
63400 }
63401
63402 return [moduleCalcData, remainingCalcData];
63403};
63404
63405/**
63406 * Get the data trace(s) associated with a given subplot.
63407 *
63408 * @param {array} data plotly full data array.
63409 * @param {string} type subplot type to look for.
63410 * @param {string} subplotId subplot id to look for.
63411 *
63412 * @return {array} list of trace objects.
63413 *
63414 */
63415exports.getSubplotData = function getSubplotData(data, type, subplotId) {
63416 if(!Registry.subplotsRegistry[type]) return [];
63417
63418 var attr = Registry.subplotsRegistry[type].attr;
63419 var subplotData = [];
63420 var trace, subplotX, subplotY;
63421
63422 if(type === 'gl2d') {
63423 var spmatch = subplotId.match(SUBPLOT_PATTERN);
63424 subplotX = 'x' + spmatch[1];
63425 subplotY = 'y' + spmatch[2];
63426 }
63427
63428 for(var i = 0; i < data.length; i++) {
63429 trace = data[i];
63430
63431 if(type === 'gl2d' && Registry.traceIs(trace, 'gl2d')) {
63432 if(trace[attr[0]] === subplotX && trace[attr[1]] === subplotY) {
63433 subplotData.push(trace);
63434 }
63435 } else {
63436 if(trace[attr] === subplotId) subplotData.push(trace);
63437 }
63438 }
63439
63440 return subplotData;
63441};
63442
63443},{"../registry":269,"./cartesian/constants":228}],253:[function(_dereq_,module,exports){
63444/**
63445* Copyright 2012-2020, Plotly, Inc.
63446* All rights reserved.
63447*
63448* This source code is licensed under the MIT license found in the
63449* LICENSE file in the root directory of this source tree.
63450*/
63451
63452
63453'use strict';
63454
63455function xformMatrix(m, v) {
63456 var out = [0, 0, 0, 0];
63457 var i, j;
63458
63459 for(i = 0; i < 4; ++i) {
63460 for(j = 0; j < 4; ++j) {
63461 out[j] += m[4 * i + j] * v[i];
63462 }
63463 }
63464
63465 return out;
63466}
63467
63468function project(camera, v) {
63469 var p = xformMatrix(camera.projection,
63470 xformMatrix(camera.view,
63471 xformMatrix(camera.model, [v[0], v[1], v[2], 1])));
63472 return p;
63473}
63474
63475module.exports = project;
63476
63477},{}],254:[function(_dereq_,module,exports){
63478/**
63479* Copyright 2012-2020, Plotly, Inc.
63480* All rights reserved.
63481*
63482* This source code is licensed under the MIT license found in the
63483* LICENSE file in the root directory of this source tree.
63484*/
63485
63486'use strict';
63487
63488var fontAttrs = _dereq_('./font_attributes');
63489var animationAttrs = _dereq_('./animation_attributes');
63490var colorAttrs = _dereq_('../components/color/attributes');
63491var drawNewShapeAttrs = _dereq_('../components/shapes/draw_newshape/attributes');
63492var padAttrs = _dereq_('./pad_attributes');
63493var extendFlat = _dereq_('../lib/extend').extendFlat;
63494
63495var globalFont = fontAttrs({
63496 editType: 'calc',
63497
63498});
63499globalFont.family.dflt = '"Open Sans", verdana, arial, sans-serif';
63500globalFont.size.dflt = 12;
63501globalFont.color.dflt = colorAttrs.defaultLine;
63502
63503module.exports = {
63504 font: globalFont,
63505 title: {
63506 text: {
63507 valType: 'string',
63508
63509 editType: 'layoutstyle',
63510
63511 },
63512 font: fontAttrs({
63513 editType: 'layoutstyle',
63514
63515 }),
63516 xref: {
63517 valType: 'enumerated',
63518 dflt: 'container',
63519 values: ['container', 'paper'],
63520
63521 editType: 'layoutstyle',
63522
63523 },
63524 yref: {
63525 valType: 'enumerated',
63526 dflt: 'container',
63527 values: ['container', 'paper'],
63528
63529 editType: 'layoutstyle',
63530
63531 },
63532 x: {
63533 valType: 'number',
63534 min: 0,
63535 max: 1,
63536 dflt: 0.5,
63537
63538 editType: 'layoutstyle',
63539
63540 },
63541 y: {
63542 valType: 'number',
63543 min: 0,
63544 max: 1,
63545 dflt: 'auto',
63546
63547 editType: 'layoutstyle',
63548
63549 },
63550 xanchor: {
63551 valType: 'enumerated',
63552 dflt: 'auto',
63553 values: ['auto', 'left', 'center', 'right'],
63554
63555 editType: 'layoutstyle',
63556
63557 },
63558 yanchor: {
63559 valType: 'enumerated',
63560 dflt: 'auto',
63561 values: ['auto', 'top', 'middle', 'bottom'],
63562
63563 editType: 'layoutstyle',
63564
63565 },
63566 pad: extendFlat(padAttrs({editType: 'layoutstyle'}), {
63567
63568 }),
63569 editType: 'layoutstyle'
63570 },
63571 uniformtext: {
63572 mode: {
63573 valType: 'enumerated',
63574 values: [false, 'hide', 'show'],
63575 dflt: false,
63576
63577 editType: 'plot',
63578
63579 },
63580 minsize: {
63581 valType: 'number',
63582 min: 0,
63583 dflt: 0,
63584
63585 editType: 'plot',
63586
63587 },
63588 editType: 'plot'
63589 },
63590 autosize: {
63591 valType: 'boolean',
63592
63593 dflt: false,
63594 // autosize, width, and height get special editType treatment in _relayout
63595 // so we can handle noop resizes more efficiently
63596 editType: 'none',
63597
63598 },
63599 width: {
63600 valType: 'number',
63601
63602 min: 10,
63603 dflt: 700,
63604 editType: 'plot',
63605
63606 },
63607 height: {
63608 valType: 'number',
63609
63610 min: 10,
63611 dflt: 450,
63612 editType: 'plot',
63613
63614 },
63615 margin: {
63616 l: {
63617 valType: 'number',
63618
63619 min: 0,
63620 dflt: 80,
63621 editType: 'plot',
63622
63623 },
63624 r: {
63625 valType: 'number',
63626
63627 min: 0,
63628 dflt: 80,
63629 editType: 'plot',
63630
63631 },
63632 t: {
63633 valType: 'number',
63634
63635 min: 0,
63636 dflt: 100,
63637 editType: 'plot',
63638
63639 },
63640 b: {
63641 valType: 'number',
63642
63643 min: 0,
63644 dflt: 80,
63645 editType: 'plot',
63646
63647 },
63648 pad: {
63649 valType: 'number',
63650
63651 min: 0,
63652 dflt: 0,
63653 editType: 'plot',
63654
63655 },
63656 autoexpand: {
63657 valType: 'boolean',
63658
63659 dflt: true,
63660 editType: 'plot',
63661
63662 },
63663 editType: 'plot'
63664 },
63665 paper_bgcolor: {
63666 valType: 'color',
63667
63668 dflt: colorAttrs.background,
63669 editType: 'plot',
63670
63671 },
63672 plot_bgcolor: {
63673 // defined here, but set in cartesian.supplyLayoutDefaults
63674 // because it needs to know if there are (2D) axes or not
63675 valType: 'color',
63676
63677 dflt: colorAttrs.background,
63678 editType: 'layoutstyle',
63679
63680 },
63681 separators: {
63682 valType: 'string',
63683
63684 editType: 'plot',
63685
63686 },
63687 hidesources: {
63688 valType: 'boolean',
63689
63690 dflt: false,
63691 editType: 'plot',
63692
63693 },
63694 showlegend: {
63695 // handled in legend.supplyLayoutDefaults
63696 // but included here because it's not in the legend object
63697 valType: 'boolean',
63698
63699 editType: 'legend',
63700
63701 },
63702 colorway: {
63703 valType: 'colorlist',
63704 dflt: colorAttrs.defaults,
63705
63706 editType: 'calc',
63707
63708 },
63709 datarevision: {
63710 valType: 'any',
63711
63712 editType: 'calc',
63713
63714 },
63715 uirevision: {
63716 valType: 'any',
63717
63718 editType: 'none',
63719
63720 },
63721 editrevision: {
63722 valType: 'any',
63723
63724 editType: 'none',
63725
63726 },
63727 selectionrevision: {
63728 valType: 'any',
63729
63730 editType: 'none',
63731
63732 },
63733 template: {
63734 valType: 'any',
63735
63736 editType: 'calc',
63737
63738 },
63739 modebar: {
63740 orientation: {
63741 valType: 'enumerated',
63742 values: ['v', 'h'],
63743 dflt: 'h',
63744
63745 editType: 'modebar',
63746
63747 },
63748 bgcolor: {
63749 valType: 'color',
63750
63751 editType: 'modebar',
63752
63753 },
63754 color: {
63755 valType: 'color',
63756
63757 editType: 'modebar',
63758
63759 },
63760 activecolor: {
63761 valType: 'color',
63762
63763 editType: 'modebar',
63764
63765 },
63766 uirevision: {
63767 valType: 'any',
63768
63769 editType: 'none',
63770
63771 },
63772 editType: 'modebar'
63773 },
63774
63775 newshape: drawNewShapeAttrs.newshape,
63776 activeshape: drawNewShapeAttrs.activeshape,
63777
63778 meta: {
63779 valType: 'any',
63780 arrayOk: true,
63781
63782 editType: 'plot',
63783
63784 },
63785
63786 transition: extendFlat({}, animationAttrs.transition, {
63787
63788 editType: 'none'
63789 }),
63790 _deprecated: {
63791 title: {
63792 valType: 'string',
63793
63794 editType: 'layoutstyle',
63795
63796 },
63797 titlefont: fontAttrs({
63798 editType: 'layoutstyle',
63799
63800 })
63801 }
63802};
63803
63804},{"../components/color/attributes":51,"../components/shapes/draw_newshape/attributes":134,"../lib/extend":173,"./animation_attributes":217,"./font_attributes":250,"./pad_attributes":255}],255:[function(_dereq_,module,exports){
63805/**
63806* Copyright 2012-2020, Plotly, Inc.
63807* All rights reserved.
63808*
63809* This source code is licensed under the MIT license found in the
63810* LICENSE file in the root directory of this source tree.
63811*/
63812
63813'use strict';
63814
63815/**
63816 * Creates a set of padding attributes.
63817 *
63818 * @param {object} opts
63819 * @param {string} editType:
63820 * the editType for all pieces of this padding definition
63821 *
63822 * @return {object} attributes object containing {t, r, b, l} as specified
63823 */
63824module.exports = function(opts) {
63825 var editType = opts.editType;
63826 return {
63827 t: {
63828 valType: 'number',
63829 dflt: 0,
63830
63831 editType: editType,
63832
63833 },
63834 r: {
63835 valType: 'number',
63836 dflt: 0,
63837
63838 editType: editType,
63839
63840 },
63841 b: {
63842 valType: 'number',
63843 dflt: 0,
63844
63845 editType: editType,
63846
63847 },
63848 l: {
63849 valType: 'number',
63850 dflt: 0,
63851
63852 editType: editType,
63853
63854 },
63855 editType: editType
63856 };
63857};
63858
63859},{}],256:[function(_dereq_,module,exports){
63860/**
63861* Copyright 2012-2020, Plotly, Inc.
63862* All rights reserved.
63863*
63864* This source code is licensed under the MIT license found in the
63865* LICENSE file in the root directory of this source tree.
63866*/
63867
63868'use strict';
63869
63870var d3 = _dereq_('d3');
63871var isNumeric = _dereq_('fast-isnumeric');
63872
63873var Registry = _dereq_('../registry');
63874var PlotSchema = _dereq_('../plot_api/plot_schema');
63875var Template = _dereq_('../plot_api/plot_template');
63876var Lib = _dereq_('../lib');
63877var Color = _dereq_('../components/color');
63878var BADNUM = _dereq_('../constants/numerical').BADNUM;
63879
63880var axisIDs = _dereq_('./cartesian/axis_ids');
63881var clearSelect = _dereq_('./cartesian/handle_outline').clearSelect;
63882
63883var animationAttrs = _dereq_('./animation_attributes');
63884var frameAttrs = _dereq_('./frame_attributes');
63885
63886var getModuleCalcData = _dereq_('../plots/get_data').getModuleCalcData;
63887
63888var relinkPrivateKeys = Lib.relinkPrivateKeys;
63889var _ = Lib._;
63890
63891var plots = module.exports = {};
63892
63893// Expose registry methods on Plots for backward-compatibility
63894Lib.extendFlat(plots, Registry);
63895
63896plots.attributes = _dereq_('./attributes');
63897plots.attributes.type.values = plots.allTypes;
63898plots.fontAttrs = _dereq_('./font_attributes');
63899plots.layoutAttributes = _dereq_('./layout_attributes');
63900
63901// TODO make this a plot attribute?
63902plots.fontWeight = 'normal';
63903
63904var transformsRegistry = plots.transformsRegistry;
63905
63906var commandModule = _dereq_('./command');
63907plots.executeAPICommand = commandModule.executeAPICommand;
63908plots.computeAPICommandBindings = commandModule.computeAPICommandBindings;
63909plots.manageCommandObserver = commandModule.manageCommandObserver;
63910plots.hasSimpleAPICommandBindings = commandModule.hasSimpleAPICommandBindings;
63911
63912// in some cases the browser doesn't seem to know how big
63913// the text is at first, so it needs to draw it,
63914// then wait a little, then draw it again
63915plots.redrawText = function(gd) {
63916 gd = Lib.getGraphDiv(gd);
63917
63918 var fullLayout = gd._fullLayout || {};
63919 var hasPolar = fullLayout._has && fullLayout._has('polar');
63920 var hasLegacyPolar = !hasPolar && gd.data && gd.data[0] && gd.data[0].r;
63921
63922 // do not work if polar is present
63923 if(hasLegacyPolar) return;
63924
63925 return new Promise(function(resolve) {
63926 setTimeout(function() {
63927 Registry.getComponentMethod('annotations', 'draw')(gd);
63928 Registry.getComponentMethod('legend', 'draw')(gd);
63929 Registry.getComponentMethod('colorbar', 'draw')(gd);
63930 resolve(plots.previousPromises(gd));
63931 }, 300);
63932 });
63933};
63934
63935// resize plot about the container size
63936plots.resize = function(gd) {
63937 gd = Lib.getGraphDiv(gd);
63938
63939 var resolveLastResize;
63940 var p = new Promise(function(resolve, reject) {
63941 if(!gd || Lib.isHidden(gd)) {
63942 reject(new Error('Resize must be passed a displayed plot div element.'));
63943 }
63944
63945 if(gd._redrawTimer) clearTimeout(gd._redrawTimer);
63946 if(gd._resolveResize) resolveLastResize = gd._resolveResize;
63947 gd._resolveResize = resolve;
63948
63949 gd._redrawTimer = setTimeout(function() {
63950 // return if there is nothing to resize or is hidden
63951 if(!gd.layout || (gd.layout.width && gd.layout.height) || Lib.isHidden(gd)) {
63952 resolve(gd);
63953 return;
63954 }
63955
63956 delete gd.layout.width;
63957 delete gd.layout.height;
63958
63959 // autosizing doesn't count as a change that needs saving
63960 var oldchanged = gd.changed;
63961
63962 // nor should it be included in the undo queue
63963 gd.autoplay = true;
63964
63965 Registry.call('relayout', gd, {autosize: true}).then(function() {
63966 gd.changed = oldchanged;
63967 // Only resolve if a new call hasn't been made!
63968 if(gd._resolveResize === resolve) {
63969 delete gd._resolveResize;
63970 resolve(gd);
63971 }
63972 });
63973 }, 100);
63974 });
63975
63976 if(resolveLastResize) resolveLastResize(p);
63977 return p;
63978};
63979
63980
63981// for use in Lib.syncOrAsync, check if there are any
63982// pending promises in this plot and wait for them
63983plots.previousPromises = function(gd) {
63984 if((gd._promises || []).length) {
63985 return Promise.all(gd._promises)
63986 .then(function() { gd._promises = []; });
63987 }
63988};
63989
63990/**
63991 * Adds the 'Edit chart' link.
63992 * Note that now Plotly.plot() calls this so it can regenerate whenever it replots
63993 *
63994 * Add source links to your graph inside the 'showSources' config argument.
63995 */
63996plots.addLinks = function(gd) {
63997 // Do not do anything if showLink and showSources are not set to true in config
63998 if(!gd._context.showLink && !gd._context.showSources) return;
63999
64000 var fullLayout = gd._fullLayout;
64001
64002 var linkContainer = Lib.ensureSingle(fullLayout._paper, 'text', 'js-plot-link-container', function(s) {
64003 s.style({
64004 'font-family': '"Open Sans", Arial, sans-serif',
64005 'font-size': '12px',
64006 'fill': Color.defaultLine,
64007 'pointer-events': 'all'
64008 })
64009 .each(function() {
64010 var links = d3.select(this);
64011 links.append('tspan').classed('js-link-to-tool', true);
64012 links.append('tspan').classed('js-link-spacer', true);
64013 links.append('tspan').classed('js-sourcelinks', true);
64014 });
64015 });
64016
64017 // The text node inside svg
64018 var text = linkContainer.node();
64019 var attrs = {y: fullLayout._paper.attr('height') - 9};
64020
64021 // If text's width is bigger than the layout
64022 // Check that text is a child node or document.body
64023 // because otherwise IE/Edge might throw an exception
64024 // when calling getComputedTextLength().
64025 // Apparently offsetParent is null for invisibles.
64026 if(document.body.contains(text) && text.getComputedTextLength() >= (fullLayout.width - 20)) {
64027 // Align the text at the left
64028 attrs['text-anchor'] = 'start';
64029 attrs.x = 5;
64030 } else {
64031 // Align the text at the right
64032 attrs['text-anchor'] = 'end';
64033 attrs.x = fullLayout._paper.attr('width') - 7;
64034 }
64035
64036 linkContainer.attr(attrs);
64037
64038 var toolspan = linkContainer.select('.js-link-to-tool');
64039 var spacespan = linkContainer.select('.js-link-spacer');
64040 var sourcespan = linkContainer.select('.js-sourcelinks');
64041
64042 if(gd._context.showSources) gd._context.showSources(gd);
64043
64044 // 'view in plotly' link for embedded plots
64045 if(gd._context.showLink) positionPlayWithData(gd, toolspan);
64046
64047 // separator if we have both sources and tool link
64048 spacespan.text((toolspan.text() && sourcespan.text()) ? ' - ' : '');
64049};
64050
64051// note that now this function is only adding the brand in
64052// iframes and 3rd-party apps
64053function positionPlayWithData(gd, container) {
64054 container.text('');
64055 var link = container.append('a')
64056 .attr({
64057 'xlink:xlink:href': '#',
64058 'class': 'link--impt link--embedview',
64059 'font-weight': 'bold'
64060 })
64061 .text(gd._context.linkText + ' ' + String.fromCharCode(187));
64062
64063 if(gd._context.sendData) {
64064 link.on('click', function() {
64065 plots.sendDataToCloud(gd);
64066 });
64067 } else {
64068 var path = window.location.pathname.split('/');
64069 var query = window.location.search;
64070 link.attr({
64071 'xlink:xlink:show': 'new',
64072 'xlink:xlink:href': '/' + path[2].split('.')[0] + '/' + path[1] + query
64073 });
64074 }
64075}
64076
64077plots.sendDataToCloud = function(gd) {
64078 var baseUrl = (window.PLOTLYENV || {}).BASE_URL || gd._context.plotlyServerURL;
64079 if(!baseUrl) return;
64080
64081 gd.emit('plotly_beforeexport');
64082
64083 var hiddenformDiv = d3.select(gd)
64084 .append('div')
64085 .attr('id', 'hiddenform')
64086 .style('display', 'none');
64087
64088 var hiddenform = hiddenformDiv
64089 .append('form')
64090 .attr({
64091 action: baseUrl + '/external',
64092 method: 'post',
64093 target: '_blank'
64094 });
64095
64096 var hiddenformInput = hiddenform
64097 .append('input')
64098 .attr({
64099 type: 'text',
64100 name: 'data'
64101 });
64102
64103 hiddenformInput.node().value = plots.graphJson(gd, false, 'keepdata');
64104 hiddenform.node().submit();
64105 hiddenformDiv.remove();
64106
64107 gd.emit('plotly_afterexport');
64108 return false;
64109};
64110
64111var d3FormatKeys = [
64112 'days', 'shortDays', 'months', 'shortMonths', 'periods',
64113 'dateTime', 'date', 'time',
64114 'decimal', 'thousands', 'grouping', 'currency'
64115];
64116
64117var extraFormatKeys = [
64118 'year', 'month', 'dayMonth', 'dayMonthYear'
64119];
64120
64121/*
64122 * Fill in default values
64123 * @param {DOM element} gd
64124 * @param {object} opts
64125 * @param {boolean} opts.skipUpdateCalc: normally if the existing gd.calcdata looks
64126 * compatible with the new gd._fullData we finish by linking the new _fullData traces
64127 * to the old gd.calcdata, so it's correctly set if we're not going to recalc. But also,
64128 * if there are calcTransforms on the trace, we first remap data arrays from the old full
64129 * trace into the new one. Use skipUpdateCalc to defer this (needed by Plotly.react)
64130 *
64131 * gd.data, gd.layout:
64132 * are precisely what the user specified (except as modified by cleanData/cleanLayout),
64133 * these fields shouldn't be modified (except for filling in some auto values)
64134 * nor used directly after the supply defaults step.
64135 *
64136 * gd._fullData, gd._fullLayout:
64137 * are complete descriptions of how to draw the plot,
64138 * use these fields in all required computations.
64139 *
64140 * gd._fullLayout._modules
64141 * is a list of all the trace modules required to draw the plot.
64142 *
64143 * gd._fullLayout._visibleModules
64144 * subset of _modules, a list of modules corresponding to visible:true traces.
64145 *
64146 * gd._fullLayout._basePlotModules
64147 * is a list of all the plot modules required to draw the plot.
64148 *
64149 * gd._fullLayout._transformModules
64150 * is a list of all the transform modules invoked.
64151 *
64152 */
64153plots.supplyDefaults = function(gd, opts) {
64154 var skipUpdateCalc = opts && opts.skipUpdateCalc;
64155 var oldFullLayout = gd._fullLayout || {};
64156
64157 if(oldFullLayout._skipDefaults) {
64158 delete oldFullLayout._skipDefaults;
64159 return;
64160 }
64161
64162 var newFullLayout = gd._fullLayout = {};
64163 var newLayout = gd.layout || {};
64164
64165 var oldFullData = gd._fullData || [];
64166 var newFullData = gd._fullData = [];
64167 var newData = gd.data || [];
64168
64169 var oldCalcdata = gd.calcdata || [];
64170
64171 var context = gd._context || {};
64172
64173 var i;
64174
64175 // Create all the storage space for frames, but only if doesn't already exist
64176 if(!gd._transitionData) plots.createTransitionData(gd);
64177
64178 // So we only need to do this once (and since we have gd here)
64179 // get the translated placeholder titles.
64180 // These ones get used as default values so need to be known at supplyDefaults
64181 // others keep their blank defaults but render the placeholder as desired later
64182 // TODO: make these work the same way, only inserting the placeholder text at draw time?
64183 // The challenge is that this has slightly different behavior right now in editable mode:
64184 // using the placeholder as default makes this text permanently (but lightly) visible,
64185 // but explicit '' for these titles gives you a placeholder that's hidden until you mouse
64186 // over it - so you're not distracted by it if you really don't want a title, but if you do
64187 // and you're new to plotly you may not be able to find it.
64188 // When editable=false the two behave the same, no title is drawn.
64189 newFullLayout._dfltTitle = {
64190 plot: _(gd, 'Click to enter Plot title'),
64191 x: _(gd, 'Click to enter X axis title'),
64192 y: _(gd, 'Click to enter Y axis title'),
64193 colorbar: _(gd, 'Click to enter Colorscale title'),
64194 annotation: _(gd, 'new text')
64195 };
64196 newFullLayout._traceWord = _(gd, 'trace');
64197
64198 var formatObj = getFormatObj(gd, d3FormatKeys);
64199
64200 // stash the token from context so mapbox subplots can use it as default
64201 newFullLayout._mapboxAccessToken = context.mapboxAccessToken;
64202
64203 // first fill in what we can of layout without looking at data
64204 // because fullData needs a few things from layout
64205 if(oldFullLayout._initialAutoSizeIsDone) {
64206 // coerce the updated layout while preserving width and height
64207 var oldWidth = oldFullLayout.width;
64208 var oldHeight = oldFullLayout.height;
64209
64210 plots.supplyLayoutGlobalDefaults(newLayout, newFullLayout, formatObj);
64211
64212 if(!newLayout.width) newFullLayout.width = oldWidth;
64213 if(!newLayout.height) newFullLayout.height = oldHeight;
64214 plots.sanitizeMargins(newFullLayout);
64215 } else {
64216 // coerce the updated layout and autosize if needed
64217 plots.supplyLayoutGlobalDefaults(newLayout, newFullLayout, formatObj);
64218
64219 var missingWidthOrHeight = (!newLayout.width || !newLayout.height);
64220 var autosize = newFullLayout.autosize;
64221 var autosizable = context.autosizable;
64222 var initialAutoSize = missingWidthOrHeight && (autosize || autosizable);
64223
64224 if(initialAutoSize) plots.plotAutoSize(gd, newLayout, newFullLayout);
64225 else if(missingWidthOrHeight) plots.sanitizeMargins(newFullLayout);
64226
64227 // for backwards-compatibility with Plotly v1.x.x
64228 if(!autosize && missingWidthOrHeight) {
64229 newLayout.width = newFullLayout.width;
64230 newLayout.height = newFullLayout.height;
64231 }
64232 }
64233
64234 newFullLayout._d3locale = getFormatter(formatObj, newFullLayout.separators);
64235 newFullLayout._extraFormat = getFormatObj(gd, extraFormatKeys);
64236
64237 newFullLayout._initialAutoSizeIsDone = true;
64238
64239 // keep track of how many traces are inputted
64240 newFullLayout._dataLength = newData.length;
64241
64242 // clear the lists of trace and baseplot modules, and subplots
64243 newFullLayout._modules = [];
64244 newFullLayout._visibleModules = [];
64245 newFullLayout._basePlotModules = [];
64246 var subplots = newFullLayout._subplots = emptySubplotLists();
64247
64248 // initialize axis and subplot hash objects for splom-generated grids
64249 var splomAxes = newFullLayout._splomAxes = {x: {}, y: {}};
64250 var splomSubplots = newFullLayout._splomSubplots = {};
64251 // initialize splom grid defaults
64252 newFullLayout._splomGridDflt = {};
64253
64254 // for stacked area traces to share config across traces
64255 newFullLayout._scatterStackOpts = {};
64256 // for the first scatter trace on each subplot (so it knows tonext->tozero)
64257 newFullLayout._firstScatter = {};
64258 // for grouped bar/box/violin trace to share config across traces
64259 newFullLayout._alignmentOpts = {};
64260 // track color axes referenced in the data
64261 newFullLayout._colorAxes = {};
64262
64263 // for traces to request a default rangeslider on their x axes
64264 // eg set `_requestRangeslider.x2 = true` for xaxis2
64265 newFullLayout._requestRangeslider = {};
64266
64267 // pull uids from old data to use as new defaults
64268 newFullLayout._traceUids = getTraceUids(oldFullData, newData);
64269
64270 // then do the data
64271 newFullLayout._globalTransforms = (gd._context || {}).globalTransforms;
64272 plots.supplyDataDefaults(newData, newFullData, newLayout, newFullLayout);
64273
64274 // redo grid size defaults with info about splom x/y axes,
64275 // and fill in generated cartesian axes and subplots
64276 var splomXa = Object.keys(splomAxes.x);
64277 var splomYa = Object.keys(splomAxes.y);
64278 if(splomXa.length > 1 && splomYa.length > 1) {
64279 Registry.getComponentMethod('grid', 'sizeDefaults')(newLayout, newFullLayout);
64280
64281 for(i = 0; i < splomXa.length; i++) {
64282 Lib.pushUnique(subplots.xaxis, splomXa[i]);
64283 }
64284 for(i = 0; i < splomYa.length; i++) {
64285 Lib.pushUnique(subplots.yaxis, splomYa[i]);
64286 }
64287 for(var k in splomSubplots) {
64288 Lib.pushUnique(subplots.cartesian, k);
64289 }
64290 }
64291
64292 // attach helper method to check whether a plot type is present on graph
64293 newFullLayout._has = plots._hasPlotType.bind(newFullLayout);
64294
64295 if(oldFullData.length === newFullData.length) {
64296 for(i = 0; i < newFullData.length; i++) {
64297 relinkPrivateKeys(newFullData[i], oldFullData[i]);
64298 }
64299 }
64300
64301 // finally, fill in the pieces of layout that may need to look at data
64302 plots.supplyLayoutModuleDefaults(newLayout, newFullLayout, newFullData, gd._transitionData);
64303
64304 // Special cases that introduce interactions between traces.
64305 // This is after relinkPrivateKeys so we can use those in crossTraceDefaults
64306 // and after layout module defaults, so we can use eg barmode
64307 var _modules = newFullLayout._visibleModules;
64308 var crossTraceDefaultsFuncs = [];
64309 for(i = 0; i < _modules.length; i++) {
64310 var funci = _modules[i].crossTraceDefaults;
64311 // some trace types share crossTraceDefaults (ie histogram2d, histogram2dcontour)
64312 if(funci) Lib.pushUnique(crossTraceDefaultsFuncs, funci);
64313 }
64314 for(i = 0; i < crossTraceDefaultsFuncs.length; i++) {
64315 crossTraceDefaultsFuncs[i](newFullData, newFullLayout);
64316 }
64317
64318 // turn on flag to optimize large splom-only graphs
64319 // mostly by omitting SVG layers during Cartesian.drawFramework
64320 newFullLayout._hasOnlyLargeSploms = (
64321 newFullLayout._basePlotModules.length === 1 &&
64322 newFullLayout._basePlotModules[0].name === 'splom' &&
64323 splomXa.length > 15 &&
64324 splomYa.length > 15 &&
64325 newFullLayout.shapes.length === 0 &&
64326 newFullLayout.images.length === 0
64327 );
64328
64329 // TODO remove in v2.0.0
64330 // add has-plot-type refs to fullLayout for backward compatibility
64331 newFullLayout._hasCartesian = newFullLayout._has('cartesian');
64332 newFullLayout._hasGeo = newFullLayout._has('geo');
64333 newFullLayout._hasGL3D = newFullLayout._has('gl3d');
64334 newFullLayout._hasGL2D = newFullLayout._has('gl2d');
64335 newFullLayout._hasTernary = newFullLayout._has('ternary');
64336 newFullLayout._hasPie = newFullLayout._has('pie');
64337
64338 // relink / initialize subplot axis objects
64339 plots.linkSubplots(newFullData, newFullLayout, oldFullData, oldFullLayout);
64340
64341 // clean subplots and other artifacts from previous plot calls
64342 plots.cleanPlot(newFullData, newFullLayout, oldFullData, oldFullLayout);
64343
64344 var hadGL2D = !!(oldFullLayout._has && oldFullLayout._has('gl2d'));
64345 var hasGL2D = !!(newFullLayout._has && newFullLayout._has('gl2d'));
64346 var hadCartesian = !!(oldFullLayout._has && oldFullLayout._has('cartesian'));
64347 var hasCartesian = !!(newFullLayout._has && newFullLayout._has('cartesian'));
64348 var hadBgLayer = hadCartesian || hadGL2D;
64349 var hasBgLayer = hasCartesian || hasGL2D;
64350 if(hadBgLayer && !hasBgLayer) {
64351 // remove bgLayer
64352 oldFullLayout._bgLayer.remove();
64353 } else if(hasBgLayer && !hadBgLayer) {
64354 // create bgLayer
64355 newFullLayout._shouldCreateBgLayer = true;
64356 }
64357
64358 // clear selection outline until we implement persistent selection,
64359 // don't clear them though when drag handlers (e.g. listening to
64360 // `plotly_selecting`) update the graph.
64361 // we should try to come up with a better solution when implementing
64362 // https://github.com/plotly/plotly.js/issues/1851
64363 if(oldFullLayout._zoomlayer && !gd._dragging) {
64364 clearSelect({ // mock old gd
64365 _fullLayout: oldFullLayout
64366 });
64367 }
64368
64369
64370 // fill in meta helpers
64371 fillMetaTextHelpers(newFullData, newFullLayout);
64372
64373 // relink functions and _ attributes to promote consistency between plots
64374 relinkPrivateKeys(newFullLayout, oldFullLayout);
64375
64376 // colorscale crossTraceDefaults needs newFullLayout with relinked keys
64377 Registry.getComponentMethod('colorscale', 'crossTraceDefaults')(newFullData, newFullLayout);
64378
64379 // For persisting GUI-driven changes in layout
64380 // _preGUI and _tracePreGUI were already copied over in relinkPrivateKeys
64381 if(!newFullLayout._preGUI) newFullLayout._preGUI = {};
64382 // track trace GUI changes by uid rather than by trace index
64383 if(!newFullLayout._tracePreGUI) newFullLayout._tracePreGUI = {};
64384 var tracePreGUI = newFullLayout._tracePreGUI;
64385 var uids = {};
64386 var uid;
64387 for(uid in tracePreGUI) uids[uid] = 'old';
64388 for(i = 0; i < newFullData.length; i++) {
64389 uid = newFullData[i]._fullInput.uid;
64390 if(!uids[uid]) tracePreGUI[uid] = {};
64391 uids[uid] = 'new';
64392 }
64393 for(uid in uids) {
64394 if(uids[uid] === 'old') delete tracePreGUI[uid];
64395 }
64396
64397 // set up containers for margin calculations
64398 initMargins(newFullLayout);
64399
64400 // collect and do some initial calculations for rangesliders
64401 Registry.getComponentMethod('rangeslider', 'makeData')(newFullLayout);
64402
64403 // update object references in calcdata
64404 if(!skipUpdateCalc && oldCalcdata.length === newFullData.length) {
64405 plots.supplyDefaultsUpdateCalc(oldCalcdata, newFullData);
64406 }
64407};
64408
64409plots.supplyDefaultsUpdateCalc = function(oldCalcdata, newFullData) {
64410 for(var i = 0; i < newFullData.length; i++) {
64411 var newTrace = newFullData[i];
64412 var cd0 = (oldCalcdata[i] || [])[0];
64413 if(cd0 && cd0.trace) {
64414 var oldTrace = cd0.trace;
64415 if(oldTrace._hasCalcTransform) {
64416 var arrayAttrs = oldTrace._arrayAttrs;
64417 var j, astr, oldArrayVal;
64418
64419 for(j = 0; j < arrayAttrs.length; j++) {
64420 astr = arrayAttrs[j];
64421 oldArrayVal = Lib.nestedProperty(oldTrace, astr).get().slice();
64422 Lib.nestedProperty(newTrace, astr).set(oldArrayVal);
64423 }
64424 }
64425 cd0.trace = newTrace;
64426 }
64427 }
64428};
64429
64430/**
64431 * Create a list of uid strings satisfying (in this order of importance):
64432 * 1. all unique, all strings
64433 * 2. matches input uids if provided
64434 * 3. matches previous data uids
64435 */
64436function getTraceUids(oldFullData, newData) {
64437 var len = newData.length;
64438 var oldFullInput = [];
64439 var i, prevFullInput;
64440 for(i = 0; i < oldFullData.length; i++) {
64441 var thisFullInput = oldFullData[i]._fullInput;
64442 if(thisFullInput !== prevFullInput) oldFullInput.push(thisFullInput);
64443 prevFullInput = thisFullInput;
64444 }
64445 var oldLen = oldFullInput.length;
64446 var out = new Array(len);
64447 var seenUids = {};
64448
64449 function setUid(uid, i) {
64450 out[i] = uid;
64451 seenUids[uid] = 1;
64452 }
64453
64454 function tryUid(uid, i) {
64455 if(uid && typeof uid === 'string' && !seenUids[uid]) {
64456 setUid(uid, i);
64457 return true;
64458 }
64459 }
64460
64461 for(i = 0; i < len; i++) {
64462 var newUid = newData[i].uid;
64463 if(typeof newUid === 'number') newUid = String(newUid);
64464
64465 if(tryUid(newUid, i)) continue;
64466 if(i < oldLen && tryUid(oldFullInput[i].uid, i)) continue;
64467 setUid(Lib.randstr(seenUids), i);
64468 }
64469
64470 return out;
64471}
64472
64473/**
64474 * Make a container for collecting subplots we need to display.
64475 *
64476 * Finds all subplot types we need to enumerate once and caches it,
64477 * but makes a new output object each time.
64478 * Single-trace subplots (which have no `id`) such as pie, table, etc
64479 * do not need to be collected because we just draw all visible traces.
64480 */
64481function emptySubplotLists() {
64482 var collectableSubplotTypes = Registry.collectableSubplotTypes;
64483 var out = {};
64484 var i, j;
64485
64486 if(!collectableSubplotTypes) {
64487 collectableSubplotTypes = [];
64488
64489 var subplotsRegistry = Registry.subplotsRegistry;
64490
64491 for(var subplotType in subplotsRegistry) {
64492 var subplotModule = subplotsRegistry[subplotType];
64493 var subplotAttr = subplotModule.attr;
64494
64495 if(subplotAttr) {
64496 collectableSubplotTypes.push(subplotType);
64497
64498 // special case, currently just for cartesian:
64499 // we need to enumerate axes, not just subplots
64500 if(Array.isArray(subplotAttr)) {
64501 for(j = 0; j < subplotAttr.length; j++) {
64502 Lib.pushUnique(collectableSubplotTypes, subplotAttr[j]);
64503 }
64504 }
64505 }
64506 }
64507 }
64508
64509 for(i = 0; i < collectableSubplotTypes.length; i++) {
64510 out[collectableSubplotTypes[i]] = [];
64511 }
64512 return out;
64513}
64514
64515/**
64516 * getFormatObj: use _context to get the format object from locale.
64517 * Used to get d3.locale argument object and extraFormat argument object
64518 *
64519 * Regarding d3.locale argument :
64520 * decimal and thousands can be overridden later by layout.separators
64521 * grouping and currency are not presently used by our automatic number
64522 * formatting system but can be used by custom formats.
64523 *
64524 * @returns {object} d3.locale format object
64525 */
64526function getFormatObj(gd, formatKeys) {
64527 var locale = gd._context.locale;
64528 if(!locale) locale === 'en-US';
64529
64530 var formatDone = false;
64531 var formatObj = {};
64532
64533 function includeFormat(newFormat) {
64534 var formatFinished = true;
64535 for(var i = 0; i < formatKeys.length; i++) {
64536 var formatKey = formatKeys[i];
64537 if(!formatObj[formatKey]) {
64538 if(newFormat[formatKey]) {
64539 formatObj[formatKey] = newFormat[formatKey];
64540 } else formatFinished = false;
64541 }
64542 }
64543 if(formatFinished) formatDone = true;
64544 }
64545
64546 // same as localize, look for format parts in each format spec in the chain
64547 for(var i = 0; i < 2; i++) {
64548 var locales = gd._context.locales;
64549 for(var j = 0; j < 2; j++) {
64550 var formatj = (locales[locale] || {}).format;
64551 if(formatj) {
64552 includeFormat(formatj);
64553 if(formatDone) break;
64554 }
64555 locales = Registry.localeRegistry;
64556 }
64557
64558 var baseLocale = locale.split('-')[0];
64559 if(formatDone || baseLocale === locale) break;
64560 locale = baseLocale;
64561 }
64562
64563 // lastly pick out defaults from english (non-US, as DMY is so much more common)
64564 if(!formatDone) includeFormat(Registry.localeRegistry.en.format);
64565
64566 return formatObj;
64567}
64568
64569/**
64570 * getFormatter: combine the final separators with the locale formatting object
64571 * we pulled earlier to generate number and time formatters
64572 * TODO: remove separators in v2, only use locale, so we don't need this step?
64573 *
64574 * @param {object} formatObj: d3.locale format object
64575 * @param {string} separators: length-2 string to override decimal and thousands
64576 * separators in number formatting
64577 *
64578 * @returns {object} {numberFormat, timeFormat} d3 formatter factory functions
64579 * for numbers and time
64580 */
64581function getFormatter(formatObj, separators) {
64582 formatObj.decimal = separators.charAt(0);
64583 formatObj.thousands = separators.charAt(1);
64584
64585 return d3.locale(formatObj);
64586}
64587
64588function fillMetaTextHelpers(newFullData, newFullLayout) {
64589 var _meta;
64590 var meta4data = [];
64591
64592 if(newFullLayout.meta) {
64593 _meta = newFullLayout._meta = {
64594 meta: newFullLayout.meta,
64595 layout: {meta: newFullLayout.meta}
64596 };
64597 }
64598
64599 for(var i = 0; i < newFullData.length; i++) {
64600 var trace = newFullData[i];
64601
64602 if(trace.meta) {
64603 meta4data[trace.index] = trace._meta = {meta: trace.meta};
64604 } else if(newFullLayout.meta) {
64605 trace._meta = {meta: newFullLayout.meta};
64606 }
64607 if(newFullLayout.meta) {
64608 trace._meta.layout = {meta: newFullLayout.meta};
64609 }
64610 }
64611
64612 if(meta4data.length) {
64613 if(!_meta) {
64614 _meta = newFullLayout._meta = {};
64615 }
64616 _meta.data = meta4data;
64617 }
64618}
64619
64620// Create storage for all of the data related to frames and transitions:
64621plots.createTransitionData = function(gd) {
64622 // Set up the default keyframe if it doesn't exist:
64623 if(!gd._transitionData) {
64624 gd._transitionData = {};
64625 }
64626
64627 if(!gd._transitionData._frames) {
64628 gd._transitionData._frames = [];
64629 }
64630
64631 if(!gd._transitionData._frameHash) {
64632 gd._transitionData._frameHash = {};
64633 }
64634
64635 if(!gd._transitionData._counter) {
64636 gd._transitionData._counter = 0;
64637 }
64638
64639 if(!gd._transitionData._interruptCallbacks) {
64640 gd._transitionData._interruptCallbacks = [];
64641 }
64642};
64643
64644// helper function to be bound to fullLayout to check
64645// whether a certain plot type is present on plot
64646// or trace has a category
64647plots._hasPlotType = function(category) {
64648 var i;
64649
64650 // check base plot modules
64651 var basePlotModules = this._basePlotModules || [];
64652 for(i = 0; i < basePlotModules.length; i++) {
64653 if(basePlotModules[i].name === category) return true;
64654 }
64655
64656 // check trace modules (including non-visible:true)
64657 var modules = this._modules || [];
64658 for(i = 0; i < modules.length; i++) {
64659 var name = modules[i].name;
64660 if(name === category) return true;
64661 // N.B. this is modules[i] along with 'categories' as a hash object
64662 var _module = Registry.modules[name];
64663 if(_module && _module.categories[category]) return true;
64664 }
64665
64666 return false;
64667};
64668
64669plots.cleanPlot = function(newFullData, newFullLayout, oldFullData, oldFullLayout) {
64670 var i, j;
64671
64672 var basePlotModules = oldFullLayout._basePlotModules || [];
64673 for(i = 0; i < basePlotModules.length; i++) {
64674 var _module = basePlotModules[i];
64675
64676 if(_module.clean) {
64677 _module.clean(newFullData, newFullLayout, oldFullData, oldFullLayout);
64678 }
64679 }
64680
64681 var hadGl = oldFullLayout._has && oldFullLayout._has('gl');
64682 var hasGl = newFullLayout._has && newFullLayout._has('gl');
64683
64684 if(hadGl && !hasGl) {
64685 if(oldFullLayout._glcontainer !== undefined) {
64686 oldFullLayout._glcontainer.selectAll('.gl-canvas').remove();
64687 oldFullLayout._glcontainer.selectAll('.no-webgl').remove();
64688 oldFullLayout._glcanvas = null;
64689 }
64690 }
64691
64692 var hasInfoLayer = !!oldFullLayout._infolayer;
64693
64694 oldLoop:
64695 for(i = 0; i < oldFullData.length; i++) {
64696 var oldTrace = oldFullData[i];
64697 var oldUid = oldTrace.uid;
64698
64699 for(j = 0; j < newFullData.length; j++) {
64700 var newTrace = newFullData[j];
64701
64702 if(oldUid === newTrace.uid) continue oldLoop;
64703 }
64704
64705 // clean old colorbars
64706 if(hasInfoLayer) {
64707 oldFullLayout._infolayer.select('.cb' + oldUid).remove();
64708 }
64709 }
64710};
64711
64712plots.linkSubplots = function(newFullData, newFullLayout, oldFullData, oldFullLayout) {
64713 var i, j;
64714
64715 var oldSubplots = oldFullLayout._plots || {};
64716 var newSubplots = newFullLayout._plots = {};
64717 var newSubplotList = newFullLayout._subplots;
64718
64719 var mockGd = {
64720 _fullData: newFullData,
64721 _fullLayout: newFullLayout
64722 };
64723
64724 var ids = newSubplotList.cartesian.concat(newSubplotList.gl2d || []);
64725
64726 for(i = 0; i < ids.length; i++) {
64727 var id = ids[i];
64728 var oldSubplot = oldSubplots[id];
64729 var xaxis = axisIDs.getFromId(mockGd, id, 'x');
64730 var yaxis = axisIDs.getFromId(mockGd, id, 'y');
64731 var plotinfo;
64732
64733 // link or create subplot object
64734 if(oldSubplot) {
64735 plotinfo = newSubplots[id] = oldSubplot;
64736 } else {
64737 plotinfo = newSubplots[id] = {};
64738 plotinfo.id = id;
64739 }
64740
64741 // add these axis ids to each others' subplot lists
64742 xaxis._counterAxes.push(yaxis._id);
64743 yaxis._counterAxes.push(xaxis._id);
64744 xaxis._subplotsWith.push(id);
64745 yaxis._subplotsWith.push(id);
64746
64747 // update x and y axis layout object refs
64748 plotinfo.xaxis = xaxis;
64749 plotinfo.yaxis = yaxis;
64750
64751 // By default, we clip at the subplot level,
64752 // but if one trace on a given subplot has *cliponaxis* set to false,
64753 // we need to clip at the trace module layer level;
64754 // find this out here, once of for all.
64755 plotinfo._hasClipOnAxisFalse = false;
64756
64757 for(j = 0; j < newFullData.length; j++) {
64758 var trace = newFullData[j];
64759
64760 if(
64761 trace.xaxis === plotinfo.xaxis._id &&
64762 trace.yaxis === plotinfo.yaxis._id &&
64763 trace.cliponaxis === false
64764 ) {
64765 plotinfo._hasClipOnAxisFalse = true;
64766 break;
64767 }
64768 }
64769 }
64770
64771 // while we're at it, link overlaying axes to their main axes and
64772 // anchored axes to the axes they're anchored to
64773 var axList = axisIDs.list(mockGd, null, true);
64774 var ax;
64775 for(i = 0; i < axList.length; i++) {
64776 ax = axList[i];
64777 var mainAx = null;
64778
64779 if(ax.overlaying) {
64780 mainAx = axisIDs.getFromId(mockGd, ax.overlaying);
64781
64782 // you cannot overlay an axis that's already overlaying another
64783 if(mainAx && mainAx.overlaying) {
64784 ax.overlaying = false;
64785 mainAx = null;
64786 }
64787 }
64788 ax._mainAxis = mainAx || ax;
64789
64790 /*
64791 * For now force overlays to overlay completely... so they
64792 * can drag together correctly and share backgrounds.
64793 * Later perhaps we make separate axis domain and
64794 * tick/line domain or something, so they can still share
64795 * the (possibly larger) dragger and background but don't
64796 * have to both be drawn over that whole domain
64797 */
64798 if(mainAx) ax.domain = mainAx.domain.slice();
64799
64800 ax._anchorAxis = ax.anchor === 'free' ?
64801 null :
64802 axisIDs.getFromId(mockGd, ax.anchor);
64803 }
64804
64805 // finally, we can find the main subplot for each axis
64806 // (on which the ticks & labels are drawn)
64807 for(i = 0; i < axList.length; i++) {
64808 ax = axList[i];
64809 ax._counterAxes.sort(axisIDs.idSort);
64810 ax._subplotsWith.sort(Lib.subplotSort);
64811 ax._mainSubplot = findMainSubplot(ax, newFullLayout);
64812
64813 // find "full" domain span of counter axes,
64814 // this loop can be costly, so only compute it when required
64815 if(ax._counterAxes.length && (
64816 (ax.spikemode && ax.spikemode.indexOf('across') !== -1) ||
64817 (ax.automargin && ax.mirror && ax.anchor !== 'free') ||
64818 Registry.getComponentMethod('rangeslider', 'isVisible')(ax)
64819 )) {
64820 var min = 1;
64821 var max = 0;
64822 for(j = 0; j < ax._counterAxes.length; j++) {
64823 var ax2 = axisIDs.getFromId(mockGd, ax._counterAxes[j]);
64824 min = Math.min(min, ax2.domain[0]);
64825 max = Math.max(max, ax2.domain[1]);
64826 }
64827 if(min < max) {
64828 ax._counterDomainMin = min;
64829 ax._counterDomainMax = max;
64830 }
64831 }
64832 }
64833};
64834
64835function findMainSubplot(ax, fullLayout) {
64836 var mockGd = {_fullLayout: fullLayout};
64837
64838 var isX = ax._id.charAt(0) === 'x';
64839 var anchorAx = ax._mainAxis._anchorAxis;
64840 var mainSubplotID = '';
64841 var nextBestMainSubplotID = '';
64842 var anchorID = '';
64843
64844 // First try the main ID with the anchor
64845 if(anchorAx) {
64846 anchorID = anchorAx._mainAxis._id;
64847 mainSubplotID = isX ? (ax._id + anchorID) : (anchorID + ax._id);
64848 }
64849
64850 // Then look for a subplot with the counteraxis overlaying the anchor
64851 // If that fails just use the first subplot including this axis
64852 if(!mainSubplotID || !fullLayout._plots[mainSubplotID]) {
64853 mainSubplotID = '';
64854
64855 var counterIDs = ax._counterAxes;
64856 for(var j = 0; j < counterIDs.length; j++) {
64857 var counterPart = counterIDs[j];
64858 var id = isX ? (ax._id + counterPart) : (counterPart + ax._id);
64859 if(!nextBestMainSubplotID) nextBestMainSubplotID = id;
64860 var counterAx = axisIDs.getFromId(mockGd, counterPart);
64861 if(anchorID && counterAx.overlaying === anchorID) {
64862 mainSubplotID = id;
64863 break;
64864 }
64865 }
64866 }
64867
64868 return mainSubplotID || nextBestMainSubplotID;
64869}
64870
64871// This function clears any trace attributes with valType: color and
64872// no set dflt filed in the plot schema. This is needed because groupby (which
64873// is the only transform for which this currently applies) supplies parent
64874// trace defaults, then expanded trace defaults. The result is that `null`
64875// colors are default-supplied and inherited as a color instead of a null.
64876// The result is that expanded trace default colors have no effect, with
64877// the final result that groups are indistinguishable. This function clears
64878// those colors so that individual groupby groups get unique colors.
64879plots.clearExpandedTraceDefaultColors = function(trace) {
64880 var colorAttrs, path, i;
64881
64882 // This uses weird closure state in order to satisfy the linter rule
64883 // that we can't create functions in a loop.
64884 function locateColorAttrs(attr, attrName, attrs, level) {
64885 path[level] = attrName;
64886 path.length = level + 1;
64887 if(attr.valType === 'color' && attr.dflt === undefined) {
64888 colorAttrs.push(path.join('.'));
64889 }
64890 }
64891
64892 path = [];
64893
64894 // Get the cached colorAttrs:
64895 colorAttrs = trace._module._colorAttrs;
64896
64897 // Or else compute and cache the colorAttrs on the module:
64898 if(!colorAttrs) {
64899 trace._module._colorAttrs = colorAttrs = [];
64900 PlotSchema.crawl(
64901 trace._module.attributes,
64902 locateColorAttrs
64903 );
64904 }
64905
64906 for(i = 0; i < colorAttrs.length; i++) {
64907 var origprop = Lib.nestedProperty(trace, '_input.' + colorAttrs[i]);
64908
64909 if(!origprop.get()) {
64910 Lib.nestedProperty(trace, colorAttrs[i]).set(null);
64911 }
64912 }
64913};
64914
64915
64916plots.supplyDataDefaults = function(dataIn, dataOut, layout, fullLayout) {
64917 var modules = fullLayout._modules;
64918 var visibleModules = fullLayout._visibleModules;
64919 var basePlotModules = fullLayout._basePlotModules;
64920 var cnt = 0;
64921 var colorCnt = 0;
64922
64923 var i, fullTrace, trace;
64924
64925 fullLayout._transformModules = [];
64926
64927 function pushModule(fullTrace) {
64928 dataOut.push(fullTrace);
64929
64930 var _module = fullTrace._module;
64931 if(!_module) return;
64932
64933 Lib.pushUnique(modules, _module);
64934 if(fullTrace.visible === true) Lib.pushUnique(visibleModules, _module);
64935 Lib.pushUnique(basePlotModules, fullTrace._module.basePlotModule);
64936 cnt++;
64937
64938 // TODO: do we really want color not to increment for explicitly invisible traces?
64939 // This logic is weird, but matches previous behavior: traces that you explicitly
64940 // set to visible:false do not increment the color, but traces WE determine to be
64941 // empty or invalid (and thus set to visible:false) DO increment color.
64942 // I kind of think we should just let all traces increment color, visible or not.
64943 // see mock: axes-autotype-empty vs. a test of restyling visible: false that
64944 // I can't find right now...
64945 if(fullTrace._input.visible !== false) colorCnt++;
64946 }
64947
64948 var carpetIndex = {};
64949 var carpetDependents = [];
64950 var dataTemplate = (layout.template || {}).data || {};
64951 var templater = Template.traceTemplater(dataTemplate);
64952
64953 for(i = 0; i < dataIn.length; i++) {
64954 trace = dataIn[i];
64955
64956 // reuse uid we may have pulled out of oldFullData
64957 // Note: templater supplies trace type
64958 fullTrace = templater.newTrace(trace);
64959 fullTrace.uid = fullLayout._traceUids[i];
64960 plots.supplyTraceDefaults(trace, fullTrace, colorCnt, fullLayout, i);
64961
64962 fullTrace.index = i;
64963 fullTrace._input = trace;
64964 fullTrace._expandedIndex = cnt;
64965
64966 if(fullTrace.transforms && fullTrace.transforms.length) {
64967 var sdInvisible = trace.visible !== false && fullTrace.visible === false;
64968
64969 var expandedTraces = applyTransforms(fullTrace, dataOut, layout, fullLayout);
64970
64971 for(var j = 0; j < expandedTraces.length; j++) {
64972 var expandedTrace = expandedTraces[j];
64973
64974 // No further templating during transforms.
64975 var fullExpandedTrace = {
64976 _template: fullTrace._template,
64977 type: fullTrace.type,
64978 // set uid using parent uid and expanded index
64979 // to promote consistency between update calls
64980 uid: fullTrace.uid + j
64981 };
64982
64983 // If the first supplyDefaults created `visible: false`,
64984 // clear it before running supplyDefaults a second time,
64985 // because sometimes there are items we still want to coerce
64986 // inside trace modules before determining that the trace is
64987 // again `visible: false`, for example partial visibilities
64988 // in `splom` traces.
64989 if(sdInvisible && expandedTrace.visible === false) {
64990 delete expandedTrace.visible;
64991 }
64992
64993 plots.supplyTraceDefaults(expandedTrace, fullExpandedTrace, cnt, fullLayout, i);
64994
64995 // relink private (i.e. underscore) keys expanded trace to full expanded trace so
64996 // that transform supply-default methods can set _ keys for future use.
64997 relinkPrivateKeys(fullExpandedTrace, expandedTrace);
64998
64999 // add info about parent data trace
65000 fullExpandedTrace.index = i;
65001 fullExpandedTrace._input = trace;
65002 fullExpandedTrace._fullInput = fullTrace;
65003
65004 // add info about the expanded data
65005 fullExpandedTrace._expandedIndex = cnt;
65006 fullExpandedTrace._expandedInput = expandedTrace;
65007
65008 pushModule(fullExpandedTrace);
65009 }
65010 } else {
65011 // add identify refs for consistency with transformed traces
65012 fullTrace._fullInput = fullTrace;
65013 fullTrace._expandedInput = fullTrace;
65014
65015 pushModule(fullTrace);
65016 }
65017
65018 if(Registry.traceIs(fullTrace, 'carpetAxis')) {
65019 carpetIndex[fullTrace.carpet] = fullTrace;
65020 }
65021
65022 if(Registry.traceIs(fullTrace, 'carpetDependent')) {
65023 carpetDependents.push(i);
65024 }
65025 }
65026
65027 for(i = 0; i < carpetDependents.length; i++) {
65028 fullTrace = dataOut[carpetDependents[i]];
65029
65030 if(!fullTrace.visible) continue;
65031
65032 var carpetAxis = carpetIndex[fullTrace.carpet];
65033 fullTrace._carpet = carpetAxis;
65034
65035 if(!carpetAxis || !carpetAxis.visible) {
65036 fullTrace.visible = false;
65037 continue;
65038 }
65039
65040 fullTrace.xaxis = carpetAxis.xaxis;
65041 fullTrace.yaxis = carpetAxis.yaxis;
65042 }
65043};
65044
65045plots.supplyAnimationDefaults = function(opts) {
65046 opts = opts || {};
65047 var i;
65048 var optsOut = {};
65049
65050 function coerce(attr, dflt) {
65051 return Lib.coerce(opts || {}, optsOut, animationAttrs, attr, dflt);
65052 }
65053
65054 coerce('mode');
65055 coerce('direction');
65056 coerce('fromcurrent');
65057
65058 if(Array.isArray(opts.frame)) {
65059 optsOut.frame = [];
65060 for(i = 0; i < opts.frame.length; i++) {
65061 optsOut.frame[i] = plots.supplyAnimationFrameDefaults(opts.frame[i] || {});
65062 }
65063 } else {
65064 optsOut.frame = plots.supplyAnimationFrameDefaults(opts.frame || {});
65065 }
65066
65067 if(Array.isArray(opts.transition)) {
65068 optsOut.transition = [];
65069 for(i = 0; i < opts.transition.length; i++) {
65070 optsOut.transition[i] = plots.supplyAnimationTransitionDefaults(opts.transition[i] || {});
65071 }
65072 } else {
65073 optsOut.transition = plots.supplyAnimationTransitionDefaults(opts.transition || {});
65074 }
65075
65076 return optsOut;
65077};
65078
65079plots.supplyAnimationFrameDefaults = function(opts) {
65080 var optsOut = {};
65081
65082 function coerce(attr, dflt) {
65083 return Lib.coerce(opts || {}, optsOut, animationAttrs.frame, attr, dflt);
65084 }
65085
65086 coerce('duration');
65087 coerce('redraw');
65088
65089 return optsOut;
65090};
65091
65092plots.supplyAnimationTransitionDefaults = function(opts) {
65093 var optsOut = {};
65094
65095 function coerce(attr, dflt) {
65096 return Lib.coerce(opts || {}, optsOut, animationAttrs.transition, attr, dflt);
65097 }
65098
65099 coerce('duration');
65100 coerce('easing');
65101
65102 return optsOut;
65103};
65104
65105plots.supplyFrameDefaults = function(frameIn) {
65106 var frameOut = {};
65107
65108 function coerce(attr, dflt) {
65109 return Lib.coerce(frameIn, frameOut, frameAttrs, attr, dflt);
65110 }
65111
65112 coerce('group');
65113 coerce('name');
65114 coerce('traces');
65115 coerce('baseframe');
65116 coerce('data');
65117 coerce('layout');
65118
65119 return frameOut;
65120};
65121
65122plots.supplyTraceDefaults = function(traceIn, traceOut, colorIndex, layout, traceInIndex) {
65123 var colorway = layout.colorway || Color.defaults;
65124 var defaultColor = colorway[colorIndex % colorway.length];
65125
65126 var i;
65127
65128 function coerce(attr, dflt) {
65129 return Lib.coerce(traceIn, traceOut, plots.attributes, attr, dflt);
65130 }
65131
65132 var visible = coerce('visible');
65133
65134 coerce('type');
65135 coerce('name', layout._traceWord + ' ' + traceInIndex);
65136
65137 coerce('uirevision', layout.uirevision);
65138
65139 // we want even invisible traces to make their would-be subplots visible
65140 // so coerce the subplot id(s) now no matter what
65141 var _module = plots.getModule(traceOut);
65142
65143 traceOut._module = _module;
65144 if(_module) {
65145 var basePlotModule = _module.basePlotModule;
65146 var subplotAttr = basePlotModule.attr;
65147 var subplotAttrs = basePlotModule.attributes;
65148 if(subplotAttr && subplotAttrs) {
65149 var subplots = layout._subplots;
65150 var subplotId = '';
65151
65152 if(
65153 visible ||
65154 basePlotModule.name !== 'gl2d' // for now just drop empty gl2d subplots
65155 // TODO - currently if we draw an empty gl2d subplot, it draws
65156 // nothing then gets stuck and you can't get it back without newPlot
65157 // sort this out in the regl refactor?
65158 ) {
65159 if(Array.isArray(subplotAttr)) {
65160 for(i = 0; i < subplotAttr.length; i++) {
65161 var attri = subplotAttr[i];
65162 var vali = Lib.coerce(traceIn, traceOut, subplotAttrs, attri);
65163
65164 if(subplots[attri]) Lib.pushUnique(subplots[attri], vali);
65165 subplotId += vali;
65166 }
65167 } else {
65168 subplotId = Lib.coerce(traceIn, traceOut, subplotAttrs, subplotAttr);
65169 }
65170
65171 if(subplots[basePlotModule.name]) {
65172 Lib.pushUnique(subplots[basePlotModule.name], subplotId);
65173 }
65174 }
65175 }
65176 }
65177
65178 if(visible) {
65179 coerce('customdata');
65180 coerce('ids');
65181 coerce('meta');
65182
65183 if(Registry.traceIs(traceOut, 'showLegend')) {
65184 Lib.coerce(traceIn, traceOut,
65185 _module.attributes.showlegend ? _module.attributes : plots.attributes,
65186 'showlegend'
65187 );
65188
65189 coerce('legendgroup');
65190
65191 traceOut._dfltShowLegend = true;
65192 } else {
65193 traceOut._dfltShowLegend = false;
65194 }
65195
65196 if(_module) {
65197 _module.supplyDefaults(traceIn, traceOut, defaultColor, layout);
65198 }
65199
65200 if(!Registry.traceIs(traceOut, 'noOpacity')) {
65201 coerce('opacity');
65202 }
65203
65204 if(Registry.traceIs(traceOut, 'notLegendIsolatable')) {
65205 // This clears out the legendonly state for traces like carpet that
65206 // cannot be isolated in the legend
65207 traceOut.visible = !!traceOut.visible;
65208 }
65209
65210 if(!Registry.traceIs(traceOut, 'noHover')) {
65211 if(!traceOut.hovertemplate) Lib.coerceHoverinfo(traceIn, traceOut, layout);
65212
65213 // parcats support hover, but not hoverlabel stylings (yet)
65214 if(traceOut.type !== 'parcats') {
65215 Registry.getComponentMethod('fx', 'supplyDefaults')(traceIn, traceOut, defaultColor, layout);
65216 }
65217 }
65218
65219 if(_module && _module.selectPoints) {
65220 coerce('selectedpoints');
65221 }
65222
65223 plots.supplyTransformDefaults(traceIn, traceOut, layout);
65224 }
65225
65226 return traceOut;
65227};
65228
65229/**
65230 * hasMakesDataTransform: does this trace have a transform that makes its own
65231 * data, either by grabbing it from somewhere else or by creating it from input
65232 * parameters? If so, we should still keep going with supplyDefaults
65233 * even if the trace is invisible, which may just be because it has no data yet.
65234 */
65235function hasMakesDataTransform(trace) {
65236 var transforms = trace.transforms;
65237 if(Array.isArray(transforms) && transforms.length) {
65238 for(var i = 0; i < transforms.length; i++) {
65239 var ti = transforms[i];
65240 var _module = ti._module || transformsRegistry[ti.type];
65241 if(_module && _module.makesData) return true;
65242 }
65243 }
65244 return false;
65245}
65246
65247plots.hasMakesDataTransform = hasMakesDataTransform;
65248
65249plots.supplyTransformDefaults = function(traceIn, traceOut, layout) {
65250 // For now we only allow transforms on 1D traces, ie those that specify a _length.
65251 // If we were to implement 2D transforms, we'd need to have each transform
65252 // describe its own applicability and disable itself when it doesn't apply.
65253 // Also allow transforms that make their own data, but not in globalTransforms
65254 if(!(traceOut._length || hasMakesDataTransform(traceIn))) return;
65255
65256 var globalTransforms = layout._globalTransforms || [];
65257 var transformModules = layout._transformModules || [];
65258
65259 if(!Array.isArray(traceIn.transforms) && globalTransforms.length === 0) return;
65260
65261 var containerIn = traceIn.transforms || [];
65262 var transformList = globalTransforms.concat(containerIn);
65263 var containerOut = traceOut.transforms = [];
65264
65265 for(var i = 0; i < transformList.length; i++) {
65266 var transformIn = transformList[i];
65267 var type = transformIn.type;
65268 var _module = transformsRegistry[type];
65269 var transformOut;
65270
65271 /*
65272 * Supply defaults may run twice. First pass runs all supply defaults steps
65273 * and adds the _module to any output transforms.
65274 * If transforms exist another pass is run so that any generated traces also
65275 * go through supply defaults. This has the effect of rerunning
65276 * supplyTransformDefaults. If the transform does not have a `transform`
65277 * function it could not have generated any new traces and the second stage
65278 * is unnecessary. We detect this case with the following variables.
65279 */
65280 var isFirstStage = !(transformIn._module && transformIn._module === _module);
65281 var doLaterStages = _module && typeof _module.transform === 'function';
65282
65283 if(!_module) Lib.warn('Unrecognized transform type ' + type + '.');
65284
65285 if(_module && _module.supplyDefaults && (isFirstStage || doLaterStages)) {
65286 transformOut = _module.supplyDefaults(transformIn, traceOut, layout, traceIn);
65287 transformOut.type = type;
65288 transformOut._module = _module;
65289
65290 Lib.pushUnique(transformModules, _module);
65291 } else {
65292 transformOut = Lib.extendFlat({}, transformIn);
65293 }
65294
65295 containerOut.push(transformOut);
65296 }
65297};
65298
65299function applyTransforms(fullTrace, fullData, layout, fullLayout) {
65300 var container = fullTrace.transforms;
65301 var dataOut = [fullTrace];
65302
65303 for(var i = 0; i < container.length; i++) {
65304 var transform = container[i];
65305 var _module = transformsRegistry[transform.type];
65306
65307 if(_module && _module.transform) {
65308 dataOut = _module.transform(dataOut, {
65309 transform: transform,
65310 fullTrace: fullTrace,
65311 fullData: fullData,
65312 layout: layout,
65313 fullLayout: fullLayout,
65314 transformIndex: i
65315 });
65316 }
65317 }
65318
65319 return dataOut;
65320}
65321
65322plots.supplyLayoutGlobalDefaults = function(layoutIn, layoutOut, formatObj) {
65323 function coerce(attr, dflt) {
65324 return Lib.coerce(layoutIn, layoutOut, plots.layoutAttributes, attr, dflt);
65325 }
65326
65327 var template = layoutIn.template;
65328 if(Lib.isPlainObject(template)) {
65329 layoutOut.template = template;
65330 layoutOut._template = template.layout;
65331 layoutOut._dataTemplate = template.data;
65332 }
65333
65334 var globalFont = Lib.coerceFont(coerce, 'font');
65335
65336 coerce('title.text', layoutOut._dfltTitle.plot);
65337
65338 Lib.coerceFont(coerce, 'title.font', {
65339 family: globalFont.family,
65340 size: Math.round(globalFont.size * 1.4),
65341 color: globalFont.color
65342 });
65343
65344 coerce('title.xref');
65345 coerce('title.yref');
65346 coerce('title.x');
65347 coerce('title.y');
65348 coerce('title.xanchor');
65349 coerce('title.yanchor');
65350 coerce('title.pad.t');
65351 coerce('title.pad.r');
65352 coerce('title.pad.b');
65353 coerce('title.pad.l');
65354
65355 var uniformtextMode = coerce('uniformtext.mode');
65356 if(uniformtextMode) {
65357 coerce('uniformtext.minsize');
65358 }
65359
65360 // Make sure that autosize is defaulted to *true*
65361 // on layouts with no set width and height for backward compatibly,
65362 // in particular https://plotly.com/javascript/responsive-fluid-layout/
65363 //
65364 // Before https://github.com/plotly/plotly.js/pull/635 ,
65365 // layouts with no set width and height were set temporary set to 'initial'
65366 // to pass through the autosize routine
65367 //
65368 // This behavior is subject to change in v2.
65369 coerce('autosize', !(layoutIn.width && layoutIn.height));
65370
65371 coerce('width');
65372 coerce('height');
65373 coerce('margin.l');
65374 coerce('margin.r');
65375 coerce('margin.t');
65376 coerce('margin.b');
65377 coerce('margin.pad');
65378 coerce('margin.autoexpand');
65379
65380 if(layoutIn.width && layoutIn.height) plots.sanitizeMargins(layoutOut);
65381
65382 Registry.getComponentMethod('grid', 'sizeDefaults')(layoutIn, layoutOut);
65383
65384 coerce('paper_bgcolor');
65385
65386 coerce('separators', formatObj.decimal + formatObj.thousands);
65387 coerce('hidesources');
65388
65389 coerce('colorway');
65390
65391 coerce('datarevision');
65392 var uirevision = coerce('uirevision');
65393 coerce('editrevision', uirevision);
65394 coerce('selectionrevision', uirevision);
65395
65396 coerce('modebar.orientation');
65397 coerce('modebar.bgcolor', Color.addOpacity(layoutOut.paper_bgcolor, 0.5));
65398 var modebarDefaultColor = Color.contrast(Color.rgb(layoutOut.modebar.bgcolor));
65399 coerce('modebar.color', Color.addOpacity(modebarDefaultColor, 0.3));
65400 coerce('modebar.activecolor', Color.addOpacity(modebarDefaultColor, 0.7));
65401 coerce('modebar.uirevision', uirevision);
65402
65403 Registry.getComponentMethod(
65404 'shapes',
65405 'supplyDrawNewShapeDefaults'
65406 )(layoutIn, layoutOut, coerce);
65407
65408 coerce('meta');
65409
65410 // do not include defaults in fullLayout when users do not set transition
65411 if(Lib.isPlainObject(layoutIn.transition)) {
65412 coerce('transition.duration');
65413 coerce('transition.easing');
65414 coerce('transition.ordering');
65415 }
65416
65417 Registry.getComponentMethod(
65418 'calendars',
65419 'handleDefaults'
65420 )(layoutIn, layoutOut, 'calendar');
65421
65422 Registry.getComponentMethod(
65423 'fx',
65424 'supplyLayoutGlobalDefaults'
65425 )(layoutIn, layoutOut, coerce);
65426};
65427
65428function getComputedSize(attr) {
65429 return (
65430 (typeof attr === 'string') &&
65431 (attr.substr(attr.length - 2) === 'px') &&
65432 parseFloat(attr)
65433 );
65434}
65435
65436
65437plots.plotAutoSize = function plotAutoSize(gd, layout, fullLayout) {
65438 var context = gd._context || {};
65439 var frameMargins = context.frameMargins;
65440 var newWidth;
65441 var newHeight;
65442
65443 var isPlotDiv = Lib.isPlotDiv(gd);
65444
65445 if(isPlotDiv) gd.emit('plotly_autosize');
65446
65447 // embedded in an iframe - just take the full iframe size
65448 // if we get to this point, with no aspect ratio restrictions
65449 if(context.fillFrame) {
65450 newWidth = window.innerWidth;
65451 newHeight = window.innerHeight;
65452
65453 // somehow we get a few extra px height sometimes...
65454 // just hide it
65455 document.body.style.overflow = 'hidden';
65456 } else {
65457 // plotly.js - let the developers do what they want, either
65458 // provide height and width for the container div,
65459 // specify size in layout, or take the defaults,
65460 // but don't enforce any ratio restrictions
65461 var computedStyle = isPlotDiv ? window.getComputedStyle(gd) : {};
65462
65463 newWidth = getComputedSize(computedStyle.width) || getComputedSize(computedStyle.maxWidth) || fullLayout.width;
65464 newHeight = getComputedSize(computedStyle.height) || getComputedSize(computedStyle.maxHeight) || fullLayout.height;
65465
65466 if(isNumeric(frameMargins) && frameMargins > 0) {
65467 var factor = 1 - 2 * frameMargins;
65468 newWidth = Math.round(factor * newWidth);
65469 newHeight = Math.round(factor * newHeight);
65470 }
65471 }
65472
65473 var minWidth = plots.layoutAttributes.width.min;
65474 var minHeight = plots.layoutAttributes.height.min;
65475 if(newWidth < minWidth) newWidth = minWidth;
65476 if(newHeight < minHeight) newHeight = minHeight;
65477
65478 var widthHasChanged = !layout.width &&
65479 (Math.abs(fullLayout.width - newWidth) > 1);
65480 var heightHasChanged = !layout.height &&
65481 (Math.abs(fullLayout.height - newHeight) > 1);
65482
65483 if(heightHasChanged || widthHasChanged) {
65484 if(widthHasChanged) fullLayout.width = newWidth;
65485 if(heightHasChanged) fullLayout.height = newHeight;
65486 }
65487
65488 // cache initial autosize value, used in relayout when
65489 // width or height values are set to null
65490 if(!gd._initialAutoSize) {
65491 gd._initialAutoSize = { width: newWidth, height: newHeight };
65492 }
65493
65494 plots.sanitizeMargins(fullLayout);
65495};
65496
65497plots.supplyLayoutModuleDefaults = function(layoutIn, layoutOut, fullData, transitionData) {
65498 var componentsRegistry = Registry.componentsRegistry;
65499 var basePlotModules = layoutOut._basePlotModules;
65500 var component, i, _module;
65501
65502 var Cartesian = Registry.subplotsRegistry.cartesian;
65503
65504 // check if any components need to add more base plot modules
65505 // that weren't captured by traces
65506 for(component in componentsRegistry) {
65507 _module = componentsRegistry[component];
65508
65509 if(_module.includeBasePlot) {
65510 _module.includeBasePlot(layoutIn, layoutOut);
65511 }
65512 }
65513
65514 // make sure we *at least* have some cartesian axes
65515 if(!basePlotModules.length) {
65516 basePlotModules.push(Cartesian);
65517 }
65518
65519 // ensure all cartesian axes have at least one subplot
65520 if(layoutOut._has('cartesian')) {
65521 Registry.getComponentMethod('grid', 'contentDefaults')(layoutIn, layoutOut);
65522 Cartesian.finalizeSubplots(layoutIn, layoutOut);
65523 }
65524
65525 // sort subplot lists
65526 for(var subplotType in layoutOut._subplots) {
65527 layoutOut._subplots[subplotType].sort(Lib.subplotSort);
65528 }
65529
65530 // base plot module layout defaults
65531 for(i = 0; i < basePlotModules.length; i++) {
65532 _module = basePlotModules[i];
65533
65534 // e.g. pie does not have a layout-defaults step
65535 if(_module.supplyLayoutDefaults) {
65536 _module.supplyLayoutDefaults(layoutIn, layoutOut, fullData);
65537 }
65538 }
65539
65540 // trace module layout defaults
65541 // use _modules rather than _visibleModules so that even
65542 // legendonly traces can include settings - eg barmode, which affects
65543 // legend.traceorder default value.
65544 var modules = layoutOut._modules;
65545 for(i = 0; i < modules.length; i++) {
65546 _module = modules[i];
65547
65548 if(_module.supplyLayoutDefaults) {
65549 _module.supplyLayoutDefaults(layoutIn, layoutOut, fullData);
65550 }
65551 }
65552
65553 // transform module layout defaults
65554 var transformModules = layoutOut._transformModules;
65555 for(i = 0; i < transformModules.length; i++) {
65556 _module = transformModules[i];
65557
65558 if(_module.supplyLayoutDefaults) {
65559 _module.supplyLayoutDefaults(layoutIn, layoutOut, fullData, transitionData);
65560 }
65561 }
65562
65563 for(component in componentsRegistry) {
65564 _module = componentsRegistry[component];
65565
65566 if(_module.supplyLayoutDefaults) {
65567 _module.supplyLayoutDefaults(layoutIn, layoutOut, fullData);
65568 }
65569 }
65570};
65571
65572// Remove all plotly attributes from a div so it can be replotted fresh
65573// TODO: these really need to be encapsulated into a much smaller set...
65574plots.purge = function(gd) {
65575 // note: we DO NOT remove _context because it doesn't change when we insert
65576 // a new plot, and may have been set outside of our scope.
65577
65578 var fullLayout = gd._fullLayout || {};
65579 if(fullLayout._glcontainer !== undefined) {
65580 fullLayout._glcontainer.selectAll('.gl-canvas').remove();
65581 fullLayout._glcontainer.remove();
65582 fullLayout._glcanvas = null;
65583 }
65584
65585 // remove modebar
65586 if(fullLayout._modeBar) fullLayout._modeBar.destroy();
65587
65588 if(gd._transitionData) {
65589 // Ensure any dangling callbacks are simply dropped if the plot is purged.
65590 // This is more or less only actually important for testing.
65591 if(gd._transitionData._interruptCallbacks) {
65592 gd._transitionData._interruptCallbacks.length = 0;
65593 }
65594
65595 if(gd._transitionData._animationRaf) {
65596 window.cancelAnimationFrame(gd._transitionData._animationRaf);
65597 }
65598 }
65599
65600 // remove any planned throttles
65601 Lib.clearThrottle();
65602
65603 // remove responsive handler
65604 Lib.clearResponsive(gd);
65605
65606 // data and layout
65607 delete gd.data;
65608 delete gd.layout;
65609 delete gd._fullData;
65610 delete gd._fullLayout;
65611 delete gd.calcdata;
65612 delete gd.framework;
65613 delete gd.empty;
65614
65615 delete gd.fid;
65616
65617 delete gd.undoqueue; // action queue
65618 delete gd.undonum;
65619 delete gd.autoplay; // are we doing an action that doesn't go in undo queue?
65620 delete gd.changed;
65621
65622 // these get recreated on Plotly.plot anyway, but just to be safe
65623 // (and to have a record of them...)
65624 delete gd._promises;
65625 delete gd._redrawTimer;
65626 delete gd._hmlumcount;
65627 delete gd._hmpixcount;
65628 delete gd._transitionData;
65629 delete gd._transitioning;
65630 delete gd._initialAutoSize;
65631 delete gd._transitioningWithDuration;
65632
65633 // created during certain events, that *should* clean them up
65634 // themselves, but may not if there was an error
65635 delete gd._dragging;
65636 delete gd._dragged;
65637 delete gd._dragdata;
65638 delete gd._hoverdata;
65639 delete gd._snapshotInProgress;
65640 delete gd._editing;
65641 delete gd._mouseDownTime;
65642 delete gd._legendMouseDownTime;
65643
65644 // remove all event listeners
65645 if(gd.removeAllListeners) gd.removeAllListeners();
65646};
65647
65648plots.style = function(gd) {
65649 var _modules = gd._fullLayout._visibleModules;
65650 var styleModules = [];
65651 var i;
65652
65653 // some trace modules reuse the same style method,
65654 // make sure to not unnecessary call them multiple times.
65655
65656 for(i = 0; i < _modules.length; i++) {
65657 var _module = _modules[i];
65658 if(_module.style) {
65659 Lib.pushUnique(styleModules, _module.style);
65660 }
65661 }
65662
65663 for(i = 0; i < styleModules.length; i++) {
65664 styleModules[i](gd);
65665 }
65666};
65667
65668plots.sanitizeMargins = function(fullLayout) {
65669 // polar doesn't do margins...
65670 if(!fullLayout || !fullLayout.margin) return;
65671
65672 var width = fullLayout.width;
65673 var height = fullLayout.height;
65674 var margin = fullLayout.margin;
65675 var plotWidth = width - (margin.l + margin.r);
65676 var plotHeight = height - (margin.t + margin.b);
65677 var correction;
65678
65679 // if margin.l + margin.r = 0 then plotWidth > 0
65680 // as width >= 10 by supplyDefaults
65681 // similarly for margin.t + margin.b
65682
65683 if(plotWidth < 0) {
65684 correction = (width - 1) / (margin.l + margin.r);
65685 margin.l = Math.floor(correction * margin.l);
65686 margin.r = Math.floor(correction * margin.r);
65687 }
65688
65689 if(plotHeight < 0) {
65690 correction = (height - 1) / (margin.t + margin.b);
65691 margin.t = Math.floor(correction * margin.t);
65692 margin.b = Math.floor(correction * margin.b);
65693 }
65694};
65695
65696plots.clearAutoMarginIds = function(gd) {
65697 gd._fullLayout._pushmarginIds = {};
65698};
65699
65700plots.allowAutoMargin = function(gd, id) {
65701 gd._fullLayout._pushmarginIds[id] = 1;
65702};
65703
65704function initMargins(fullLayout) {
65705 var margin = fullLayout.margin;
65706
65707 if(!fullLayout._size) {
65708 var gs = fullLayout._size = {
65709 l: Math.round(margin.l),
65710 r: Math.round(margin.r),
65711 t: Math.round(margin.t),
65712 b: Math.round(margin.b),
65713 p: Math.round(margin.pad)
65714 };
65715 gs.w = Math.round(fullLayout.width) - gs.l - gs.r;
65716 gs.h = Math.round(fullLayout.height) - gs.t - gs.b;
65717 }
65718 if(!fullLayout._pushmargin) fullLayout._pushmargin = {};
65719 if(!fullLayout._pushmarginIds) fullLayout._pushmarginIds = {};
65720}
65721
65722/**
65723 * autoMargin: called by components that may need to expand the margins to
65724 * be rendered on-plot.
65725 *
65726 * @param {DOM element} gd
65727 * @param {string} id - an identifier unique (within this plot) to this object,
65728 * so we can remove a previous margin expansion from the same object.
65729 * @param {object} o - the margin requirements of this object, or omit to delete
65730 * this entry (like if it's hidden). Keys are:
65731 * x, y: plot fraction of the anchor point.
65732 * xl, xr, yt, yb: if the object has an extent defined in plot fraction,
65733 * you can specify both edges as plot fractions in each dimension
65734 * l, r, t, b: the pixels to pad past the plot fraction x[l|r] and y[t|b]
65735 * pad: extra pixels to add in all directions, default 12 (why?)
65736 */
65737plots.autoMargin = function(gd, id, o) {
65738 var fullLayout = gd._fullLayout;
65739
65740 var pushMargin = fullLayout._pushmargin;
65741 var pushMarginIds = fullLayout._pushmarginIds;
65742
65743 if(fullLayout.margin.autoexpand !== false) {
65744 if(!o) {
65745 delete pushMargin[id];
65746 delete pushMarginIds[id];
65747 } else {
65748 var pad = o.pad;
65749 if(pad === undefined) {
65750 var margin = fullLayout.margin;
65751 // if no explicit pad is given, use 12px unless there's a
65752 // specified margin that's smaller than that
65753 pad = Math.min(12, margin.l, margin.r, margin.t, margin.b);
65754 }
65755
65756 // if the item is too big, just give it enough automargin to
65757 // make sure you can still grab it and bring it back
65758 if(o.l + o.r > fullLayout.width * 0.5) {
65759 Lib.log('Margin push', id, 'is too big in x, dropping');
65760 o.l = o.r = 0;
65761 }
65762 if(o.b + o.t > fullLayout.height * 0.5) {
65763 Lib.log('Margin push', id, 'is too big in y, dropping');
65764 o.b = o.t = 0;
65765 }
65766
65767 var xl = o.xl !== undefined ? o.xl : o.x;
65768 var xr = o.xr !== undefined ? o.xr : o.x;
65769 var yt = o.yt !== undefined ? o.yt : o.y;
65770 var yb = o.yb !== undefined ? o.yb : o.y;
65771
65772 pushMargin[id] = {
65773 l: {val: xl, size: o.l + pad},
65774 r: {val: xr, size: o.r + pad},
65775 b: {val: yb, size: o.b + pad},
65776 t: {val: yt, size: o.t + pad}
65777 };
65778 pushMarginIds[id] = 1;
65779 }
65780
65781 if(!fullLayout._replotting) {
65782 return plots.doAutoMargin(gd);
65783 }
65784 }
65785};
65786
65787plots.doAutoMargin = function(gd) {
65788 var fullLayout = gd._fullLayout;
65789 if(!fullLayout._size) fullLayout._size = {};
65790 initMargins(fullLayout);
65791
65792 var gs = fullLayout._size;
65793 var margin = fullLayout.margin;
65794 var oldMargins = Lib.extendFlat({}, gs);
65795
65796 // adjust margins for outside components
65797 // fullLayout.margin is the requested margin,
65798 // fullLayout._size has margins and plotsize after adjustment
65799 var ml = margin.l;
65800 var mr = margin.r;
65801 var mt = margin.t;
65802 var mb = margin.b;
65803 var width = fullLayout.width;
65804 var height = fullLayout.height;
65805 var pushMargin = fullLayout._pushmargin;
65806 var pushMarginIds = fullLayout._pushmarginIds;
65807
65808 if(fullLayout.margin.autoexpand !== false) {
65809 for(var k in pushMargin) {
65810 if(!pushMarginIds[k]) delete pushMargin[k];
65811 }
65812
65813 // fill in the requested margins
65814 pushMargin.base = {
65815 l: {val: 0, size: ml},
65816 r: {val: 1, size: mr},
65817 t: {val: 1, size: mt},
65818 b: {val: 0, size: mb}
65819 };
65820
65821 // now cycle through all the combinations of l and r
65822 // (and t and b) to find the required margins
65823
65824 for(var k1 in pushMargin) {
65825 var pushleft = pushMargin[k1].l || {};
65826 var pushbottom = pushMargin[k1].b || {};
65827 var fl = pushleft.val;
65828 var pl = pushleft.size;
65829 var fb = pushbottom.val;
65830 var pb = pushbottom.size;
65831
65832 for(var k2 in pushMargin) {
65833 if(isNumeric(pl) && pushMargin[k2].r) {
65834 var fr = pushMargin[k2].r.val;
65835 var pr = pushMargin[k2].r.size;
65836
65837 if(fr > fl) {
65838 var newL = (pl * fr + (pr - width) * fl) / (fr - fl);
65839 var newR = (pr * (1 - fl) + (pl - width) * (1 - fr)) / (fr - fl);
65840 if(newL >= 0 && newR >= 0 && width - (newL + newR) > 0 && newL + newR > ml + mr) {
65841 ml = newL;
65842 mr = newR;
65843 }
65844 }
65845 }
65846
65847 if(isNumeric(pb) && pushMargin[k2].t) {
65848 var ft = pushMargin[k2].t.val;
65849 var pt = pushMargin[k2].t.size;
65850
65851 if(ft > fb) {
65852 var newB = (pb * ft + (pt - height) * fb) / (ft - fb);
65853 var newT = (pt * (1 - fb) + (pb - height) * (1 - ft)) / (ft - fb);
65854 if(newB >= 0 && newT >= 0 && height - (newT + newB) > 0 && newB + newT > mb + mt) {
65855 mb = newB;
65856 mt = newT;
65857 }
65858 }
65859 }
65860 }
65861 }
65862 }
65863
65864 gs.l = Math.round(ml);
65865 gs.r = Math.round(mr);
65866 gs.t = Math.round(mt);
65867 gs.b = Math.round(mb);
65868 gs.p = Math.round(margin.pad);
65869 gs.w = Math.round(width) - gs.l - gs.r;
65870 gs.h = Math.round(height) - gs.t - gs.b;
65871
65872 // if things changed and we're not already redrawing, trigger a redraw
65873 if(!fullLayout._replotting && plots.didMarginChange(oldMargins, gs)) {
65874 if('_redrawFromAutoMarginCount' in fullLayout) {
65875 fullLayout._redrawFromAutoMarginCount++;
65876 } else {
65877 fullLayout._redrawFromAutoMarginCount = 1;
65878 }
65879
65880 // Always allow at least one redraw and give each margin-push
65881 // call 3 loops to converge. Of course, for most cases this way too many,
65882 // but let's keep things on the safe side until we fix our
65883 // auto-margin pipeline problems:
65884 // https://github.com/plotly/plotly.js/issues/2704
65885 var maxNumberOfRedraws = 3 * (1 + Object.keys(pushMarginIds).length);
65886
65887 if(fullLayout._redrawFromAutoMarginCount < maxNumberOfRedraws) {
65888 return Registry.call('plot', gd);
65889 } else {
65890 Lib.warn('Too many auto-margin redraws.');
65891 }
65892 }
65893};
65894
65895var marginKeys = ['l', 'r', 't', 'b', 'p', 'w', 'h'];
65896
65897plots.didMarginChange = function(margin0, margin1) {
65898 for(var i = 0; i < marginKeys.length; i++) {
65899 var k = marginKeys[i];
65900 var m0 = margin0[k];
65901 var m1 = margin1[k];
65902 // use 1px tolerance in case we old/new differ only
65903 // by rounding errors, which can lead to infinite loops
65904 if(!isNumeric(m0) || Math.abs(m1 - m0) > 1) {
65905 return true;
65906 }
65907 }
65908 return false;
65909};
65910
65911/**
65912 * JSONify the graph data and layout
65913 *
65914 * This function needs to recurse because some src can be inside
65915 * sub-objects.
65916 *
65917 * It also strips out functions and private (starts with _) elements.
65918 * Therefore, we can add temporary things to data and layout that don't
65919 * get saved.
65920 *
65921 * @param gd The graphDiv
65922 * @param {Boolean} dataonly If true, don't return layout.
65923 * @param {'keepref'|'keepdata'|'keepall'} [mode='keepref'] Filter what's kept
65924 * keepref: remove data for which there's a src present
65925 * eg if there's xsrc present (and xsrc is well-formed,
65926 * ie has : and some chars before it), strip out x
65927 * keepdata: remove all src tags, don't remove the data itself
65928 * keepall: keep data and src
65929 * @param {String} output If you specify 'object', the result will not be stringified
65930 * @param {Boolean} useDefaults If truthy, use _fullLayout and _fullData
65931 * @param {Boolean} includeConfig If truthy, include _context
65932 * @returns {Object|String}
65933 */
65934plots.graphJson = function(gd, dataonly, mode, output, useDefaults, includeConfig) {
65935 // if the defaults aren't supplied yet, we need to do that...
65936 if((useDefaults && dataonly && !gd._fullData) ||
65937 (useDefaults && !dataonly && !gd._fullLayout)) {
65938 plots.supplyDefaults(gd);
65939 }
65940
65941 var data = (useDefaults) ? gd._fullData : gd.data;
65942 var layout = (useDefaults) ? gd._fullLayout : gd.layout;
65943 var frames = (gd._transitionData || {})._frames;
65944
65945 function stripObj(d, keepFunction) {
65946 if(typeof d === 'function') {
65947 return keepFunction ? '_function_' : null;
65948 }
65949 if(Lib.isPlainObject(d)) {
65950 var o = {};
65951 var src;
65952 Object.keys(d).sort().forEach(function(v) {
65953 // remove private elements and functions
65954 // _ is for private, [ is a mistake ie [object Object]
65955 if(['_', '['].indexOf(v.charAt(0)) !== -1) return;
65956
65957 // if a function, add if necessary then move on
65958 if(typeof d[v] === 'function') {
65959 if(keepFunction) o[v] = '_function';
65960 return;
65961 }
65962
65963 // look for src/data matches and remove the appropriate one
65964 if(mode === 'keepdata') {
65965 // keepdata: remove all ...src tags
65966 if(v.substr(v.length - 3) === 'src') {
65967 return;
65968 }
65969 } else if(mode === 'keepstream') {
65970 // keep sourced data if it's being streamed.
65971 // similar to keepref, but if the 'stream' object exists
65972 // in a trace, we will keep the data array.
65973 src = d[v + 'src'];
65974 if(typeof src === 'string' && src.indexOf(':') > 0) {
65975 if(!Lib.isPlainObject(d.stream)) {
65976 return;
65977 }
65978 }
65979 } else if(mode !== 'keepall') {
65980 // keepref: remove sourced data but only
65981 // if the source tag is well-formed
65982 src = d[v + 'src'];
65983 if(typeof src === 'string' && src.indexOf(':') > 0) {
65984 return;
65985 }
65986 }
65987
65988 // OK, we're including this... recurse into it
65989 o[v] = stripObj(d[v], keepFunction);
65990 });
65991 return o;
65992 }
65993
65994 if(Array.isArray(d)) {
65995 return d.map(function(x) {return stripObj(x, keepFunction);});
65996 }
65997
65998 if(Lib.isTypedArray(d)) {
65999 return Lib.simpleMap(d, Lib.identity);
66000 }
66001
66002 // convert native dates to date strings...
66003 // mostly for external users exporting to plotly
66004 if(Lib.isJSDate(d)) return Lib.ms2DateTimeLocal(+d);
66005
66006 return d;
66007 }
66008
66009 var obj = {
66010 data: (data || []).map(function(v) {
66011 var d = stripObj(v);
66012 // fit has some little arrays in it that don't contain data,
66013 // just fit params and meta
66014 if(dataonly) { delete d.fit; }
66015 return d;
66016 })
66017 };
66018 if(!dataonly) { obj.layout = stripObj(layout); }
66019
66020 if(gd.framework && gd.framework.isPolar) obj = gd.framework.getConfig();
66021
66022 if(frames) obj.frames = stripObj(frames);
66023
66024 if(includeConfig) obj.config = stripObj(gd._context, true);
66025
66026 return (output === 'object') ? obj : JSON.stringify(obj);
66027};
66028
66029/**
66030 * Modify a keyframe using a list of operations:
66031 *
66032 * @param {array of objects} operations
66033 * Sequence of operations to be performed on the keyframes
66034 */
66035plots.modifyFrames = function(gd, operations) {
66036 var i, op, frame;
66037 var _frames = gd._transitionData._frames;
66038 var _frameHash = gd._transitionData._frameHash;
66039
66040 for(i = 0; i < operations.length; i++) {
66041 op = operations[i];
66042
66043 switch(op.type) {
66044 // No reason this couldn't exist, but is currently unused/untested:
66045 /* case 'rename':
66046 frame = _frames[op.index];
66047 delete _frameHash[frame.name];
66048 _frameHash[op.name] = frame;
66049 frame.name = op.name;
66050 break;*/
66051 case 'replace':
66052 frame = op.value;
66053 var oldName = (_frames[op.index] || {}).name;
66054 var newName = frame.name;
66055 _frames[op.index] = _frameHash[newName] = frame;
66056
66057 if(newName !== oldName) {
66058 // If name has changed in addition to replacement, then update
66059 // the lookup table:
66060 delete _frameHash[oldName];
66061 _frameHash[newName] = frame;
66062 }
66063
66064 break;
66065 case 'insert':
66066 frame = op.value;
66067 _frameHash[frame.name] = frame;
66068 _frames.splice(op.index, 0, frame);
66069 break;
66070 case 'delete':
66071 frame = _frames[op.index];
66072 delete _frameHash[frame.name];
66073 _frames.splice(op.index, 1);
66074 break;
66075 }
66076 }
66077
66078 return Promise.resolve();
66079};
66080
66081/*
66082 * Compute a keyframe. Merge a keyframe into its base frame(s) and
66083 * expand properties.
66084 *
66085 * @param {object} frameLookup
66086 * An object containing frames keyed by name (i.e. gd._transitionData._frameHash)
66087 * @param {string} frame
66088 * The name of the keyframe to be computed
66089 *
66090 * Returns: a new object with the merged content
66091 */
66092plots.computeFrame = function(gd, frameName) {
66093 var frameLookup = gd._transitionData._frameHash;
66094 var i, traceIndices, traceIndex, destIndex;
66095
66096 // Null or undefined will fail on .toString(). We'll allow numbers since we
66097 // make it clear frames must be given string names, but we'll allow numbers
66098 // here since they're otherwise fine for looking up frames as long as they're
66099 // properly cast to strings. We really just want to ensure here that this
66100 // 1) doesn't fail, and
66101 // 2) doens't give an incorrect answer (which String(frameName) would)
66102 if(!frameName) {
66103 throw new Error('computeFrame must be given a string frame name');
66104 }
66105
66106 var framePtr = frameLookup[frameName.toString()];
66107
66108 // Return false if the name is invalid:
66109 if(!framePtr) {
66110 return false;
66111 }
66112
66113 var frameStack = [framePtr];
66114 var frameNameStack = [framePtr.name];
66115
66116 // Follow frame pointers:
66117 while(framePtr.baseframe && (framePtr = frameLookup[framePtr.baseframe.toString()])) {
66118 // Avoid infinite loops:
66119 if(frameNameStack.indexOf(framePtr.name) !== -1) break;
66120
66121 frameStack.push(framePtr);
66122 frameNameStack.push(framePtr.name);
66123 }
66124
66125 // A new object for the merged result:
66126 var result = {};
66127
66128 // Merge, starting with the last and ending with the desired frame:
66129 while((framePtr = frameStack.pop())) {
66130 if(framePtr.layout) {
66131 result.layout = plots.extendLayout(result.layout, framePtr.layout);
66132 }
66133
66134 if(framePtr.data) {
66135 if(!result.data) {
66136 result.data = [];
66137 }
66138 traceIndices = framePtr.traces;
66139
66140 if(!traceIndices) {
66141 // If not defined, assume serial order starting at zero
66142 traceIndices = [];
66143 for(i = 0; i < framePtr.data.length; i++) {
66144 traceIndices[i] = i;
66145 }
66146 }
66147
66148 if(!result.traces) {
66149 result.traces = [];
66150 }
66151
66152 for(i = 0; i < framePtr.data.length; i++) {
66153 // Loop through this frames data, find out where it should go,
66154 // and merge it!
66155 traceIndex = traceIndices[i];
66156 if(traceIndex === undefined || traceIndex === null) {
66157 continue;
66158 }
66159
66160 destIndex = result.traces.indexOf(traceIndex);
66161 if(destIndex === -1) {
66162 destIndex = result.data.length;
66163 result.traces[destIndex] = traceIndex;
66164 }
66165
66166 result.data[destIndex] = plots.extendTrace(result.data[destIndex], framePtr.data[i]);
66167 }
66168 }
66169 }
66170
66171 return result;
66172};
66173
66174/*
66175 * Recompute the lookup table that maps frame name -> frame object. addFrames/
66176 * deleteFrames already manages this data one at a time, so the only time this
66177 * is necessary is if you poke around manually in `gd._transitionData._frames`
66178 * and create and haven't updated the lookup table.
66179 */
66180plots.recomputeFrameHash = function(gd) {
66181 var hash = gd._transitionData._frameHash = {};
66182 var frames = gd._transitionData._frames;
66183 for(var i = 0; i < frames.length; i++) {
66184 var frame = frames[i];
66185 if(frame && frame.name) {
66186 hash[frame.name] = frame;
66187 }
66188 }
66189};
66190
66191/**
66192 * Extend an object, treating container arrays very differently by extracting
66193 * their contents and merging them separately.
66194 *
66195 * This exists so that we can extendDeepNoArrays and avoid stepping into data
66196 * arrays without knowledge of the plot schema, but so that we may also manually
66197 * recurse into known container arrays, such as transforms.
66198 *
66199 * See extendTrace and extendLayout below for usage.
66200 */
66201plots.extendObjectWithContainers = function(dest, src, containerPaths) {
66202 var containerProp, containerVal, i, j, srcProp, destProp, srcContainer, destContainer;
66203 var copy = Lib.extendDeepNoArrays({}, src || {});
66204 var expandedObj = Lib.expandObjectPaths(copy);
66205 var containerObj = {};
66206
66207 // Step through and extract any container properties. Otherwise extendDeepNoArrays
66208 // will clobber any existing properties with an empty array and then supplyDefaults
66209 // will reset everything to defaults.
66210 if(containerPaths && containerPaths.length) {
66211 for(i = 0; i < containerPaths.length; i++) {
66212 containerProp = Lib.nestedProperty(expandedObj, containerPaths[i]);
66213 containerVal = containerProp.get();
66214
66215 if(containerVal === undefined) {
66216 Lib.nestedProperty(containerObj, containerPaths[i]).set(null);
66217 } else {
66218 containerProp.set(null);
66219 Lib.nestedProperty(containerObj, containerPaths[i]).set(containerVal);
66220 }
66221 }
66222 }
66223
66224 dest = Lib.extendDeepNoArrays(dest || {}, expandedObj);
66225
66226 if(containerPaths && containerPaths.length) {
66227 for(i = 0; i < containerPaths.length; i++) {
66228 srcProp = Lib.nestedProperty(containerObj, containerPaths[i]);
66229 srcContainer = srcProp.get();
66230
66231 if(!srcContainer) continue;
66232
66233 destProp = Lib.nestedProperty(dest, containerPaths[i]);
66234 destContainer = destProp.get();
66235
66236 if(!Array.isArray(destContainer)) {
66237 destContainer = [];
66238 destProp.set(destContainer);
66239 }
66240
66241 for(j = 0; j < srcContainer.length; j++) {
66242 var srcObj = srcContainer[j];
66243
66244 if(srcObj === null) destContainer[j] = null;
66245 else {
66246 destContainer[j] = plots.extendObjectWithContainers(destContainer[j], srcObj);
66247 }
66248 }
66249
66250 destProp.set(destContainer);
66251 }
66252 }
66253
66254 return dest;
66255};
66256
66257plots.dataArrayContainers = ['transforms', 'dimensions'];
66258plots.layoutArrayContainers = Registry.layoutArrayContainers;
66259
66260/*
66261 * Extend a trace definition. This method:
66262 *
66263 * 1. directly transfers any array references
66264 * 2. manually recurses into container arrays like transforms
66265 *
66266 * The result is the original object reference with the new contents merged in.
66267 */
66268plots.extendTrace = function(destTrace, srcTrace) {
66269 return plots.extendObjectWithContainers(destTrace, srcTrace, plots.dataArrayContainers);
66270};
66271
66272/*
66273 * Extend a layout definition. This method:
66274 *
66275 * 1. directly transfers any array references (not critically important for
66276 * layout since there aren't really data arrays)
66277 * 2. manually recurses into container arrays like annotations
66278 *
66279 * The result is the original object reference with the new contents merged in.
66280 */
66281plots.extendLayout = function(destLayout, srcLayout) {
66282 return plots.extendObjectWithContainers(destLayout, srcLayout, plots.layoutArrayContainers);
66283};
66284
66285/**
66286 * Transition to a set of new data and layout properties from Plotly.animate
66287 *
66288 * @param {DOM element} gd
66289 * @param {Object[]} data
66290 * an array of data objects following the normal Plotly data definition format
66291 * @param {Object} layout
66292 * a layout object, following normal Plotly layout format
66293 * @param {Number[]} traces
66294 * indices of the corresponding traces specified in `data`
66295 * @param {Object} frameOpts
66296 * options for the frame (i.e. whether to redraw post-transition)
66297 * @param {Object} transitionOpts
66298 * options for the transition
66299 */
66300plots.transition = function(gd, data, layout, traces, frameOpts, transitionOpts) {
66301 var opts = {redraw: frameOpts.redraw};
66302 var transitionedTraces = {};
66303 var axEdits = [];
66304
66305 opts.prepareFn = function() {
66306 var dataLength = Array.isArray(data) ? data.length : 0;
66307 var traceIndices = traces.slice(0, dataLength);
66308
66309 for(var i = 0; i < traceIndices.length; i++) {
66310 var traceIdx = traceIndices[i];
66311 var trace = gd._fullData[traceIdx];
66312 var _module = trace._module;
66313
66314 // There's nothing to do if this module is not defined:
66315 if(!_module) continue;
66316
66317 // Don't register the trace as transitioned if it doesn't know what to do.
66318 // If it *is* registered, it will receive a callback that it's responsible
66319 // for calling in order to register the transition as having completed.
66320 if(_module.animatable) {
66321 var n = _module.basePlotModule.name;
66322 if(!transitionedTraces[n]) transitionedTraces[n] = [];
66323 transitionedTraces[n].push(traceIdx);
66324 }
66325
66326 gd.data[traceIndices[i]] = plots.extendTrace(gd.data[traceIndices[i]], data[i]);
66327 }
66328
66329 // Follow the same procedure. Clone it so we don't mangle the input, then
66330 // expand any object paths so we can merge deep into gd.layout:
66331 var layoutUpdate = Lib.expandObjectPaths(Lib.extendDeepNoArrays({}, layout));
66332
66333 // Before merging though, we need to modify the incoming layout. We only
66334 // know how to *transition* layout ranges, so it's imperative that a new
66335 // range not be sent to the layout before the transition has started. So
66336 // we must remove the things we can transition:
66337 var axisAttrRe = /^[xy]axis[0-9]*$/;
66338 for(var attr in layoutUpdate) {
66339 if(!axisAttrRe.test(attr)) continue;
66340 delete layoutUpdate[attr].range;
66341 }
66342
66343 plots.extendLayout(gd.layout, layoutUpdate);
66344
66345 // Supply defaults after applying the incoming properties. Note that any attempt
66346 // to simplify this step and reduce the amount of work resulted in the reconstruction
66347 // of essentially the whole supplyDefaults step, so that it seems sensible to just use
66348 // supplyDefaults even though it's heavier than would otherwise be desired for
66349 // transitions:
66350
66351 // first delete calcdata so supplyDefaults knows a calc step is coming
66352 delete gd.calcdata;
66353
66354 plots.supplyDefaults(gd);
66355 plots.doCalcdata(gd);
66356
66357 var newLayout = Lib.expandObjectPaths(layout);
66358
66359 if(newLayout) {
66360 var subplots = gd._fullLayout._plots;
66361
66362 for(var k in subplots) {
66363 var plotinfo = subplots[k];
66364 var xa = plotinfo.xaxis;
66365 var ya = plotinfo.yaxis;
66366 var xr0 = xa.range.slice();
66367 var yr0 = ya.range.slice();
66368
66369 var xr1 = null;
66370 var yr1 = null;
66371 var editX = null;
66372 var editY = null;
66373
66374 if(Array.isArray(newLayout[xa._name + '.range'])) {
66375 xr1 = newLayout[xa._name + '.range'].slice();
66376 } else if(Array.isArray((newLayout[xa._name] || {}).range)) {
66377 xr1 = newLayout[xa._name].range.slice();
66378 }
66379 if(Array.isArray(newLayout[ya._name + '.range'])) {
66380 yr1 = newLayout[ya._name + '.range'].slice();
66381 } else if(Array.isArray((newLayout[ya._name] || {}).range)) {
66382 yr1 = newLayout[ya._name].range.slice();
66383 }
66384
66385 if(xr0 && xr1 &&
66386 (xa.r2l(xr0[0]) !== xa.r2l(xr1[0]) || xa.r2l(xr0[1]) !== xa.r2l(xr1[1]))
66387 ) {
66388 editX = {xr0: xr0, xr1: xr1};
66389 }
66390 if(yr0 && yr1 &&
66391 (ya.r2l(yr0[0]) !== ya.r2l(yr1[0]) || ya.r2l(yr0[1]) !== ya.r2l(yr1[1]))
66392 ) {
66393 editY = {yr0: yr0, yr1: yr1};
66394 }
66395
66396 if(editX || editY) {
66397 axEdits.push(Lib.extendFlat({plotinfo: plotinfo}, editX, editY));
66398 }
66399 }
66400 }
66401
66402 return Promise.resolve();
66403 };
66404
66405 opts.runFn = function(makeCallback) {
66406 var traceTransitionOpts;
66407 var basePlotModules = gd._fullLayout._basePlotModules;
66408 var hasAxisTransition = axEdits.length;
66409 var i;
66410
66411 if(layout) {
66412 for(i = 0; i < basePlotModules.length; i++) {
66413 if(basePlotModules[i].transitionAxes) {
66414 basePlotModules[i].transitionAxes(gd, axEdits, transitionOpts, makeCallback);
66415 }
66416 }
66417 }
66418
66419 // Here handle the exception that we refuse to animate scales and axes at the same
66420 // time. In other words, if there's an axis transition, then set the data transition
66421 // to instantaneous.
66422 if(hasAxisTransition) {
66423 traceTransitionOpts = Lib.extendFlat({}, transitionOpts);
66424 traceTransitionOpts.duration = 0;
66425 // This means do not transition cartesian traces,
66426 // this happens on layout-only (e.g. axis range) animations
66427 delete transitionedTraces.cartesian;
66428 } else {
66429 traceTransitionOpts = transitionOpts;
66430 }
66431
66432 // Note that we pass a callback to *create* the callback that must be invoked on completion.
66433 // This is since not all traces know about transitions, so it greatly simplifies matters if
66434 // the trace is responsible for creating a callback, if needed, and then executing it when
66435 // the time is right.
66436 for(var n in transitionedTraces) {
66437 var traceIndices = transitionedTraces[n];
66438 var _module = gd._fullData[traceIndices[0]]._module;
66439 _module.basePlotModule.plot(gd, traceIndices, traceTransitionOpts, makeCallback);
66440 }
66441 };
66442
66443 return _transition(gd, transitionOpts, opts);
66444};
66445
66446/**
66447 * Transition to a set of new data and layout properties from Plotly.react
66448 *
66449 * @param {DOM element} gd
66450 * @param {object} restyleFlags
66451 * - anim {'all'|'some'}
66452 * @param {object} relayoutFlags
66453 * - anim {'all'|'some'}
66454 * @param {object} oldFullLayout : old (pre Plotly.react) fullLayout
66455 */
66456plots.transitionFromReact = function(gd, restyleFlags, relayoutFlags, oldFullLayout) {
66457 var fullLayout = gd._fullLayout;
66458 var transitionOpts = fullLayout.transition;
66459 var opts = {};
66460 var axEdits = [];
66461
66462 opts.prepareFn = function() {
66463 var subplots = fullLayout._plots;
66464
66465 // no need to redraw at end of transition,
66466 // if all changes are animatable
66467 opts.redraw = false;
66468 if(restyleFlags.anim === 'some') opts.redraw = true;
66469 if(relayoutFlags.anim === 'some') opts.redraw = true;
66470
66471 for(var k in subplots) {
66472 var plotinfo = subplots[k];
66473 var xa = plotinfo.xaxis;
66474 var ya = plotinfo.yaxis;
66475 var xr0 = oldFullLayout[xa._name].range.slice();
66476 var yr0 = oldFullLayout[ya._name].range.slice();
66477 var xr1 = xa.range.slice();
66478 var yr1 = ya.range.slice();
66479
66480 xa.setScale();
66481 ya.setScale();
66482
66483 var editX = null;
66484 var editY = null;
66485
66486 if(xa.r2l(xr0[0]) !== xa.r2l(xr1[0]) || xa.r2l(xr0[1]) !== xa.r2l(xr1[1])) {
66487 editX = {xr0: xr0, xr1: xr1};
66488 }
66489 if(ya.r2l(yr0[0]) !== ya.r2l(yr1[0]) || ya.r2l(yr0[1]) !== ya.r2l(yr1[1])) {
66490 editY = {yr0: yr0, yr1: yr1};
66491 }
66492
66493 if(editX || editY) {
66494 axEdits.push(Lib.extendFlat({plotinfo: plotinfo}, editX, editY));
66495 }
66496 }
66497
66498 return Promise.resolve();
66499 };
66500
66501 opts.runFn = function(makeCallback) {
66502 var fullData = gd._fullData;
66503 var fullLayout = gd._fullLayout;
66504 var basePlotModules = fullLayout._basePlotModules;
66505
66506 var axisTransitionOpts;
66507 var traceTransitionOpts;
66508 var transitionedTraces;
66509
66510 var allTraceIndices = [];
66511 for(var i = 0; i < fullData.length; i++) {
66512 allTraceIndices.push(i);
66513 }
66514
66515 function transitionAxes() {
66516 for(var j = 0; j < basePlotModules.length; j++) {
66517 if(basePlotModules[j].transitionAxes) {
66518 basePlotModules[j].transitionAxes(gd, axEdits, axisTransitionOpts, makeCallback);
66519 }
66520 }
66521 }
66522
66523 function transitionTraces() {
66524 for(var j = 0; j < basePlotModules.length; j++) {
66525 basePlotModules[j].plot(gd, transitionedTraces, traceTransitionOpts, makeCallback);
66526 }
66527 }
66528
66529 if(axEdits.length && restyleFlags.anim) {
66530 if(transitionOpts.ordering === 'traces first') {
66531 axisTransitionOpts = Lib.extendFlat({}, transitionOpts, {duration: 0});
66532 transitionedTraces = allTraceIndices;
66533 traceTransitionOpts = transitionOpts;
66534 setTimeout(transitionAxes, transitionOpts.duration);
66535 transitionTraces();
66536 } else {
66537 axisTransitionOpts = transitionOpts;
66538 transitionedTraces = null;
66539 traceTransitionOpts = Lib.extendFlat({}, transitionOpts, {duration: 0});
66540 setTimeout(transitionTraces, axisTransitionOpts.duration);
66541 transitionAxes();
66542 }
66543 } else if(axEdits.length) {
66544 axisTransitionOpts = transitionOpts;
66545 transitionAxes();
66546 } else if(restyleFlags.anim) {
66547 transitionedTraces = allTraceIndices;
66548 traceTransitionOpts = transitionOpts;
66549 transitionTraces();
66550 }
66551 };
66552
66553 return _transition(gd, transitionOpts, opts);
66554};
66555
66556/**
66557 * trace/layout transition wrapper that works
66558 * for transitions initiated by Plotly.animate and Plotly.react.
66559 *
66560 * @param {DOM element} gd
66561 * @param {object} transitionOpts
66562 * @param {object} opts
66563 * - redraw {boolean}
66564 * - prepareFn {function} *should return a Promise*
66565 * - runFn {function} ran inside executeTransitions
66566 */
66567function _transition(gd, transitionOpts, opts) {
66568 var aborted = false;
66569
66570 function executeCallbacks(list) {
66571 var p = Promise.resolve();
66572 if(!list) return p;
66573 while(list.length) {
66574 p = p.then((list.shift()));
66575 }
66576 return p;
66577 }
66578
66579 function flushCallbacks(list) {
66580 if(!list) return;
66581 while(list.length) {
66582 list.shift();
66583 }
66584 }
66585
66586 function executeTransitions() {
66587 gd.emit('plotly_transitioning', []);
66588
66589 return new Promise(function(resolve) {
66590 // This flag is used to disabled things like autorange:
66591 gd._transitioning = true;
66592
66593 // When instantaneous updates are coming through quickly, it's too much to simply disable
66594 // all interaction, so store this flag so we can disambiguate whether mouse interactions
66595 // should be fully disabled or not:
66596 if(transitionOpts.duration > 0) {
66597 gd._transitioningWithDuration = true;
66598 }
66599
66600 // If another transition is triggered, this callback will be executed simply because it's
66601 // in the interruptCallbacks queue. If this transition completes, it will instead flush
66602 // that queue and forget about this callback.
66603 gd._transitionData._interruptCallbacks.push(function() {
66604 aborted = true;
66605 });
66606
66607 if(opts.redraw) {
66608 gd._transitionData._interruptCallbacks.push(function() {
66609 return Registry.call('redraw', gd);
66610 });
66611 }
66612
66613 // Emit this and make sure it happens last:
66614 gd._transitionData._interruptCallbacks.push(function() {
66615 gd.emit('plotly_transitioninterrupted', []);
66616 });
66617
66618 // Construct callbacks that are executed on transition end. This ensures the d3 transitions
66619 // are *complete* before anything else is done.
66620 var numCallbacks = 0;
66621 var numCompleted = 0;
66622 function makeCallback() {
66623 numCallbacks++;
66624 return function() {
66625 numCompleted++;
66626 // When all are complete, perform a redraw:
66627 if(!aborted && numCompleted === numCallbacks) {
66628 completeTransition(resolve);
66629 }
66630 };
66631 }
66632
66633 opts.runFn(makeCallback);
66634
66635 // If nothing else creates a callback, then this will trigger the completion in the next tick:
66636 setTimeout(makeCallback());
66637 });
66638 }
66639
66640 function completeTransition(callback) {
66641 // This a simple workaround for tests which purge the graph before animations
66642 // have completed. That's not a very common case, so this is the simplest
66643 // fix.
66644 if(!gd._transitionData) return;
66645
66646 flushCallbacks(gd._transitionData._interruptCallbacks);
66647
66648 return Promise.resolve().then(function() {
66649 if(opts.redraw) {
66650 return Registry.call('redraw', gd);
66651 }
66652 }).then(function() {
66653 // Set transitioning false again once the redraw has occurred. This is used, for example,
66654 // to prevent the trailing redraw from autoranging:
66655 gd._transitioning = false;
66656 gd._transitioningWithDuration = false;
66657
66658 gd.emit('plotly_transitioned', []);
66659 }).then(callback);
66660 }
66661
66662 function interruptPreviousTransitions() {
66663 // Fail-safe against purged plot:
66664 if(!gd._transitionData) return;
66665
66666 // If a transition is interrupted, set this to false. At the moment, the only thing that would
66667 // interrupt a transition is another transition, so that it will momentarily be set to true
66668 // again, but this determines whether autorange or dragbox work, so it's for the sake of
66669 // cleanliness:
66670 gd._transitioning = false;
66671
66672 return executeCallbacks(gd._transitionData._interruptCallbacks);
66673 }
66674
66675 var seq = [
66676 plots.previousPromises,
66677 interruptPreviousTransitions,
66678 opts.prepareFn,
66679 plots.rehover,
66680 executeTransitions
66681 ];
66682
66683 var transitionStarting = Lib.syncOrAsync(seq, gd);
66684
66685 if(!transitionStarting || !transitionStarting.then) {
66686 transitionStarting = Promise.resolve();
66687 }
66688
66689 return transitionStarting.then(function() { return gd; });
66690}
66691
66692plots.doCalcdata = function(gd, traces) {
66693 var axList = axisIDs.list(gd);
66694 var fullData = gd._fullData;
66695 var fullLayout = gd._fullLayout;
66696
66697 var trace, _module, i, j;
66698
66699 // XXX: Is this correct? Needs a closer look so that *some* traces can be recomputed without
66700 // *all* needing doCalcdata:
66701 var calcdata = new Array(fullData.length);
66702 var oldCalcdata = (gd.calcdata || []).slice();
66703 gd.calcdata = calcdata;
66704
66705 // extra helper variables
66706
66707 // how many box/violins plots do we have (in case they're grouped)
66708 fullLayout._numBoxes = 0;
66709 fullLayout._numViolins = 0;
66710
66711 // initialize violin per-scale-group stats container
66712 fullLayout._violinScaleGroupStats = {};
66713
66714 // for calculating avg luminosity of heatmaps
66715 gd._hmpixcount = 0;
66716 gd._hmlumcount = 0;
66717
66718 // for sharing colors across pies / sunbursts / treemap / funnelarea (and for legend)
66719 fullLayout._piecolormap = {};
66720 fullLayout._sunburstcolormap = {};
66721 fullLayout._treemapcolormap = {};
66722 fullLayout._funnelareacolormap = {};
66723
66724 // If traces were specified and this trace was not included,
66725 // then transfer it over from the old calcdata:
66726 for(i = 0; i < fullData.length; i++) {
66727 if(Array.isArray(traces) && traces.indexOf(i) === -1) {
66728 calcdata[i] = oldCalcdata[i];
66729 continue;
66730 }
66731 }
66732
66733 for(i = 0; i < fullData.length; i++) {
66734 trace = fullData[i];
66735
66736 trace._arrayAttrs = PlotSchema.findArrayAttributes(trace);
66737
66738 // keep track of trace extremes (for autorange) in here
66739 trace._extremes = {};
66740 }
66741
66742 // add polar axes to axis list
66743 var polarIds = fullLayout._subplots.polar || [];
66744 for(i = 0; i < polarIds.length; i++) {
66745 axList.push(
66746 fullLayout[polarIds[i]].radialaxis,
66747 fullLayout[polarIds[i]].angularaxis
66748 );
66749 }
66750
66751 // clear relinked cmin/cmax values in shared axes to start aggregation from scratch
66752 for(var k in fullLayout._colorAxes) {
66753 var cOpts = fullLayout[k];
66754 if(cOpts.cauto !== false) {
66755 delete cOpts.cmin;
66756 delete cOpts.cmax;
66757 }
66758 }
66759
66760 var hasCalcTransform = false;
66761
66762 function transformCalci(i) {
66763 trace = fullData[i];
66764 _module = trace._module;
66765
66766 if(trace.visible === true && trace.transforms) {
66767 // we need one round of trace module calc before
66768 // the calc transform to 'fill in' the categories list
66769 // used for example in the data-to-coordinate method
66770 if(_module && _module.calc) {
66771 var cdi = _module.calc(gd, trace);
66772
66773 // must clear scene 'batches', so that 2nd
66774 // _module.calc call starts from scratch
66775 if(cdi[0] && cdi[0].t && cdi[0].t._scene) {
66776 delete cdi[0].t._scene.dirty;
66777 }
66778 }
66779
66780 for(j = 0; j < trace.transforms.length; j++) {
66781 var transform = trace.transforms[j];
66782
66783 _module = transformsRegistry[transform.type];
66784 if(_module && _module.calcTransform) {
66785 trace._hasCalcTransform = true;
66786 hasCalcTransform = true;
66787 _module.calcTransform(gd, trace, transform);
66788 }
66789 }
66790 }
66791 }
66792
66793 function calci(i, isContainer) {
66794 trace = fullData[i];
66795 _module = trace._module;
66796
66797 if(!!_module.isContainer !== isContainer) return;
66798
66799 var cd = [];
66800
66801 if(trace.visible === true && trace._length !== 0) {
66802 // clear existing ref in case it got relinked
66803 delete trace._indexToPoints;
66804 // keep ref of index-to-points map object of the *last* enabled transform,
66805 // this index-to-points map object is required to determine the calcdata indices
66806 // that correspond to input indices (e.g. from 'selectedpoints')
66807 var transforms = trace.transforms || [];
66808 for(j = transforms.length - 1; j >= 0; j--) {
66809 if(transforms[j].enabled) {
66810 trace._indexToPoints = transforms[j]._indexToPoints;
66811 break;
66812 }
66813 }
66814
66815 if(_module && _module.calc) {
66816 cd = _module.calc(gd, trace);
66817 }
66818 }
66819
66820 // Make sure there is a first point.
66821 //
66822 // This ensures there is a calcdata item for every trace,
66823 // even if cartesian logic doesn't handle it (for things like legends).
66824 if(!Array.isArray(cd) || !cd[0]) {
66825 cd = [{x: BADNUM, y: BADNUM}];
66826 }
66827
66828 // add the trace-wide properties to the first point,
66829 // per point properties to every point
66830 // t is the holder for trace-wide properties
66831 if(!cd[0].t) cd[0].t = {};
66832 cd[0].trace = trace;
66833
66834 calcdata[i] = cd;
66835 }
66836
66837 setupAxisCategories(axList, fullData, fullLayout);
66838
66839 // 'transform' loop - must calc container traces first
66840 // so that if their dependent traces can get transform properly
66841 for(i = 0; i < fullData.length; i++) calci(i, true);
66842 for(i = 0; i < fullData.length; i++) transformCalci(i);
66843
66844 // clear stuff that should recomputed in 'regular' loop
66845 if(hasCalcTransform) setupAxisCategories(axList, fullData, fullLayout);
66846
66847 // 'regular' loop - make sure container traces (eg carpet) calc before
66848 // contained traces (eg contourcarpet)
66849 for(i = 0; i < fullData.length; i++) calci(i, true);
66850 for(i = 0; i < fullData.length; i++) calci(i, false);
66851
66852 doCrossTraceCalc(gd);
66853
66854 // Sort axis categories per value if specified
66855 var sorted = sortAxisCategoriesByValue(axList, gd);
66856 if(sorted.length) {
66857 // how many box/violins plots do we have (in case they're grouped)
66858 fullLayout._numBoxes = 0;
66859 fullLayout._numViolins = 0;
66860 // If a sort operation was performed, run calc() again
66861 for(i = 0; i < sorted.length; i++) calci(sorted[i], true);
66862 for(i = 0; i < sorted.length; i++) calci(sorted[i], false);
66863 doCrossTraceCalc(gd);
66864 }
66865
66866 Registry.getComponentMethod('fx', 'calc')(gd);
66867 Registry.getComponentMethod('errorbars', 'calc')(gd);
66868};
66869
66870var sortAxisCategoriesByValueRegex = /(total|sum|min|max|mean|median) (ascending|descending)/;
66871
66872function sortAxisCategoriesByValue(axList, gd) {
66873 var affectedTraces = [];
66874 var i, j, k, l, o;
66875
66876 function zMapCategory(type, ax, value) {
66877 var axLetter = ax._id.charAt(0);
66878 if(type === 'histogram2dcontour') {
66879 var counterAxLetter = ax._counterAxes[0];
66880 var counterAx = axisIDs.getFromId(gd, counterAxLetter);
66881
66882 var xCategorical = axLetter === 'x' || (counterAxLetter === 'x' && counterAx.type === 'category');
66883 var yCategorical = axLetter === 'y' || (counterAxLetter === 'y' && counterAx.type === 'category');
66884
66885 return function(o, l) {
66886 if(o === 0 || l === 0) return -1; // Skip first row and column
66887 if(xCategorical && o === value[l].length - 1) return -1;
66888 if(yCategorical && l === value.length - 1) return -1;
66889
66890 return (axLetter === 'y' ? l : o) - 1;
66891 };
66892 } else {
66893 return function(o, l) {
66894 return axLetter === 'y' ? l : o;
66895 };
66896 }
66897 }
66898
66899 var aggFn = {
66900 'min': function(values) {return Lib.aggNums(Math.min, null, values);},
66901 'max': function(values) {return Lib.aggNums(Math.max, null, values);},
66902 'sum': function(values) {return Lib.aggNums(function(a, b) { return a + b;}, null, values);},
66903 'total': function(values) {return Lib.aggNums(function(a, b) { return a + b;}, null, values);},
66904 'mean': function(values) {return Lib.mean(values);},
66905 'median': function(values) {return Lib.median(values);}
66906 };
66907
66908 for(i = 0; i < axList.length; i++) {
66909 var ax = axList[i];
66910 if(ax.type !== 'category') continue;
66911
66912 // Order by value
66913 var match = ax.categoryorder.match(sortAxisCategoriesByValueRegex);
66914 if(match) {
66915 var aggregator = match[1];
66916 var order = match[2];
66917
66918 // Store values associated with each category
66919 var categoriesValue = [];
66920 for(j = 0; j < ax._categories.length; j++) {
66921 categoriesValue.push([ax._categories[j], []]);
66922 }
66923
66924 // Collect values across traces
66925 for(j = 0; j < ax._traceIndices.length; j++) {
66926 var traceIndex = ax._traceIndices[j];
66927 var fullTrace = gd._fullData[traceIndex];
66928 var axLetter = ax._id.charAt(0);
66929
66930 // Skip over invisible traces
66931 if(fullTrace.visible !== true) continue;
66932
66933 var type = fullTrace.type;
66934 if(Registry.traceIs(fullTrace, 'histogram')) {
66935 delete fullTrace._xautoBinFinished;
66936 delete fullTrace._yautoBinFinished;
66937 }
66938
66939 var cd = gd.calcdata[traceIndex];
66940 for(k = 0; k < cd.length; k++) {
66941 var cdi = cd[k];
66942 var cat, catIndex, value;
66943
66944 if(type === 'splom') {
66945 // If `splom`, collect values across dimensions
66946 // Find which dimension the current axis is representing
66947 var currentDimensionIndex = fullTrace._axesDim[ax._id];
66948
66949 // Apply logic to associated x axis if it's defined
66950 if(axLetter === 'y') {
66951 var associatedXAxisID = fullTrace._diag[currentDimensionIndex][0];
66952 if(associatedXAxisID) ax = gd._fullLayout[axisIDs.id2name(associatedXAxisID)];
66953 }
66954
66955 var categories = cdi.trace.dimensions[currentDimensionIndex].values;
66956 for(l = 0; l < categories.length; l++) {
66957 cat = categories[l];
66958 catIndex = ax._categoriesMap[cat];
66959
66960 // Collect associated values at index `l` over all other dimensions
66961 for(o = 0; o < cdi.trace.dimensions.length; o++) {
66962 if(o === currentDimensionIndex) continue;
66963 var dimension = cdi.trace.dimensions[o];
66964 categoriesValue[catIndex][1].push(dimension.values[l]);
66965 }
66966 }
66967 } else if(type === 'scattergl') {
66968 // If `scattergl`, collect all values stashed under cdi.t
66969 for(l = 0; l < cdi.t.x.length; l++) {
66970 if(axLetter === 'x') {
66971 cat = cdi.t.x[l];
66972 catIndex = cat;
66973 value = cdi.t.y[l];
66974 }
66975
66976 if(axLetter === 'y') {
66977 cat = cdi.t.y[l];
66978 catIndex = cat;
66979 value = cdi.t.x[l];
66980 }
66981 categoriesValue[catIndex][1].push(value);
66982 }
66983 // must clear scene 'batches', so that 2nd
66984 // _module.calc call starts from scratch
66985 if(cdi.t && cdi.t._scene) {
66986 delete cdi.t._scene.dirty;
66987 }
66988 } else if(cdi.hasOwnProperty('z')) {
66989 // If 2dMap, collect values in `z`
66990 value = cdi.z;
66991 var mapping = zMapCategory(fullTrace.type, ax, value);
66992
66993 for(l = 0; l < value.length; l++) {
66994 for(o = 0; o < value[l].length; o++) {
66995 catIndex = mapping(o, l);
66996 if(catIndex + 1) categoriesValue[catIndex][1].push(value[l][o]);
66997 }
66998 }
66999 } else {
67000 // For all other 2d cartesian traces
67001 if(axLetter === 'x') {
67002 cat = cdi.p + 1 ? cdi.p : cdi.x;
67003 value = cdi.s || cdi.v || cdi.y;
67004 } else if(axLetter === 'y') {
67005 cat = cdi.p + 1 ? cdi.p : cdi.y;
67006 value = cdi.s || cdi.v || cdi.x;
67007 }
67008 if(!Array.isArray(value)) value = [value];
67009 for(l = 0; l < value.length; l++) {
67010 categoriesValue[cat][1].push(value[l]);
67011 }
67012 }
67013 }
67014 }
67015
67016 ax._categoriesValue = categoriesValue;
67017
67018 var categoriesAggregatedValue = [];
67019 for(j = 0; j < categoriesValue.length; j++) {
67020 categoriesAggregatedValue.push([
67021 categoriesValue[j][0],
67022 aggFn[aggregator](categoriesValue[j][1])
67023 ]);
67024 }
67025
67026 // Sort by aggregated value
67027 categoriesAggregatedValue.sort(function(a, b) {
67028 return a[1] - b[1];
67029 });
67030
67031 ax._categoriesAggregatedValue = categoriesAggregatedValue;
67032
67033 // Set new category order
67034 ax._initialCategories = categoriesAggregatedValue.map(function(c) {
67035 return c[0];
67036 });
67037
67038 // Reverse if descending
67039 if(order === 'descending') {
67040 ax._initialCategories.reverse();
67041 }
67042
67043 // Sort all matching axes
67044 affectedTraces = affectedTraces.concat(ax.sortByInitialCategories());
67045 }
67046 }
67047 return affectedTraces;
67048}
67049
67050function setupAxisCategories(axList, fullData, fullLayout) {
67051 var axLookup = {};
67052 var i, ax, axId;
67053
67054 for(i = 0; i < axList.length; i++) {
67055 ax = axList[i];
67056 axId = ax._id;
67057
67058 ax.clearCalc();
67059 if(ax.type === 'multicategory') {
67060 ax.setupMultiCategory(fullData);
67061 }
67062
67063 axLookup[ax._id] = 1;
67064 }
67065
67066 // look into match groups for 'missing' axes
67067 var matchGroups = fullLayout._axisMatchGroups || [];
67068 for(i = 0; i < matchGroups.length; i++) {
67069 for(axId in matchGroups[i]) {
67070 if(!axLookup[axId]) {
67071 ax = fullLayout[axisIDs.id2name(axId)];
67072 ax.clearCalc();
67073 }
67074 }
67075 }
67076}
67077
67078function doCrossTraceCalc(gd) {
67079 var fullLayout = gd._fullLayout;
67080 var modules = fullLayout._visibleModules;
67081 var hash = {};
67082 var i, j, k;
67083
67084 // position and range calculations for traces that
67085 // depend on each other ie bars (stacked or grouped)
67086 // and boxes (grouped) push each other out of the way
67087
67088 for(j = 0; j < modules.length; j++) {
67089 var _module = modules[j];
67090 var fn = _module.crossTraceCalc;
67091 if(fn) {
67092 var spType = _module.basePlotModule.name;
67093 if(hash[spType]) {
67094 Lib.pushUnique(hash[spType], fn);
67095 } else {
67096 hash[spType] = [fn];
67097 }
67098 }
67099 }
67100
67101 for(k in hash) {
67102 var methods = hash[k];
67103 var subplots = fullLayout._subplots[k];
67104
67105 if(Array.isArray(subplots)) {
67106 for(i = 0; i < subplots.length; i++) {
67107 var sp = subplots[i];
67108 var spInfo = k === 'cartesian' ?
67109 fullLayout._plots[sp] :
67110 fullLayout[sp];
67111
67112 for(j = 0; j < methods.length; j++) {
67113 methods[j](gd, spInfo, sp);
67114 }
67115 }
67116 } else {
67117 for(j = 0; j < methods.length; j++) {
67118 methods[j](gd);
67119 }
67120 }
67121 }
67122}
67123
67124plots.rehover = function(gd) {
67125 if(gd._fullLayout._rehover) {
67126 gd._fullLayout._rehover();
67127 }
67128};
67129
67130plots.redrag = function(gd) {
67131 if(gd._fullLayout._redrag) {
67132 gd._fullLayout._redrag();
67133 }
67134};
67135
67136plots.generalUpdatePerTraceModule = function(gd, subplot, subplotCalcData, subplotLayout) {
67137 var traceHashOld = subplot.traceHash;
67138 var traceHash = {};
67139 var i;
67140
67141 // build up moduleName -> calcData hash
67142 for(i = 0; i < subplotCalcData.length; i++) {
67143 var calcTraces = subplotCalcData[i];
67144 var trace = calcTraces[0].trace;
67145
67146 // skip over visible === false traces
67147 // as they don't have `_module` ref
67148 if(trace.visible) {
67149 traceHash[trace.type] = traceHash[trace.type] || [];
67150 traceHash[trace.type].push(calcTraces);
67151 }
67152 }
67153
67154 // when a trace gets deleted, make sure that its module's
67155 // plot method is called so that it is properly
67156 // removed from the DOM.
67157 for(var moduleNameOld in traceHashOld) {
67158 if(!traceHash[moduleNameOld]) {
67159 var fakeCalcTrace = traceHashOld[moduleNameOld][0];
67160 var fakeTrace = fakeCalcTrace[0].trace;
67161
67162 fakeTrace.visible = false;
67163 traceHash[moduleNameOld] = [fakeCalcTrace];
67164 }
67165 }
67166
67167 // call module plot method
67168 for(var moduleName in traceHash) {
67169 var moduleCalcData = traceHash[moduleName];
67170 var _module = moduleCalcData[0][0].trace._module;
67171
67172 _module.plot(gd, subplot, Lib.filterVisible(moduleCalcData), subplotLayout);
67173 }
67174
67175 // update moduleName -> calcData hash
67176 subplot.traceHash = traceHash;
67177};
67178
67179plots.plotBasePlot = function(desiredType, gd, traces, transitionOpts, makeOnCompleteCallback) {
67180 var _module = Registry.getModule(desiredType);
67181 var cdmodule = getModuleCalcData(gd.calcdata, _module)[0];
67182 _module.plot(gd, cdmodule, transitionOpts, makeOnCompleteCallback);
67183};
67184
67185plots.cleanBasePlot = function(desiredType, newFullData, newFullLayout, oldFullData, oldFullLayout) {
67186 var had = (oldFullLayout._has && oldFullLayout._has(desiredType));
67187 var has = (newFullLayout._has && newFullLayout._has(desiredType));
67188
67189 if(had && !has) {
67190 oldFullLayout['_' + desiredType + 'layer'].selectAll('g.trace').remove();
67191 }
67192};
67193
67194},{"../components/color":52,"../constants/numerical":158,"../lib":178,"../plot_api/plot_schema":211,"../plot_api/plot_template":212,"../plots/get_data":252,"../registry":269,"./animation_attributes":217,"./attributes":219,"./cartesian/axis_ids":225,"./cartesian/handle_outline":232,"./command":248,"./font_attributes":250,"./frame_attributes":251,"./layout_attributes":254,"d3":16,"fast-isnumeric":18}],257:[function(_dereq_,module,exports){
67195/**
67196* Copyright 2012-2020, Plotly, Inc.
67197* All rights reserved.
67198*
67199* This source code is licensed under the MIT license found in the
67200* LICENSE file in the root directory of this source tree.
67201*/
67202
67203'use strict';
67204
67205var scatterAttrs = _dereq_('../../../traces/scatter/attributes');
67206var scatterMarkerAttrs = scatterAttrs.marker;
67207var extendFlat = _dereq_('../../../lib/extend').extendFlat;
67208
67209var deprecationWarning = [
67210 'Area traces are deprecated!',
67211 'Please switch to the *barpolar* trace type.'
67212].join(' ');
67213
67214module.exports = {
67215 r: extendFlat({}, scatterAttrs.r, {
67216
67217 }),
67218 t: extendFlat({}, scatterAttrs.t, {
67219
67220 }),
67221 marker: {
67222 color: extendFlat({}, scatterMarkerAttrs.color, {
67223
67224 }),
67225 size: extendFlat({}, scatterMarkerAttrs.size, {
67226
67227 }),
67228 symbol: extendFlat({}, scatterMarkerAttrs.symbol, {
67229
67230 }),
67231 opacity: extendFlat({}, scatterMarkerAttrs.opacity, {
67232
67233 }),
67234 editType: 'calc'
67235 }
67236};
67237
67238},{"../../../lib/extend":173,"../../../traces/scatter/attributes":389}],258:[function(_dereq_,module,exports){
67239/**
67240* Copyright 2012-2020, Plotly, Inc.
67241* All rights reserved.
67242*
67243* This source code is licensed under the MIT license found in the
67244* LICENSE file in the root directory of this source tree.
67245*/
67246
67247
67248'use strict';
67249
67250var axesAttrs = _dereq_('../../cartesian/layout_attributes');
67251var extendFlat = _dereq_('../../../lib/extend').extendFlat;
67252var overrideAll = _dereq_('../../../plot_api/edit_types').overrideAll;
67253
67254var deprecationWarning = [
67255 'Legacy polar charts are deprecated!',
67256 'Please switch to *polar* subplots.'
67257].join(' ');
67258
67259var domainAttr = extendFlat({}, axesAttrs.domain, {
67260
67261});
67262
67263function mergeAttrs(axisName, nonCommonAttrs) {
67264 var commonAttrs = {
67265 showline: {
67266 valType: 'boolean',
67267
67268
67269 },
67270 showticklabels: {
67271 valType: 'boolean',
67272
67273
67274 },
67275 tickorientation: {
67276 valType: 'enumerated',
67277 values: ['horizontal', 'vertical'],
67278
67279
67280 },
67281 ticklen: {
67282 valType: 'number',
67283 min: 0,
67284
67285
67286 },
67287 tickcolor: {
67288 valType: 'color',
67289
67290
67291 },
67292 ticksuffix: {
67293 valType: 'string',
67294
67295
67296 },
67297 endpadding: {
67298 valType: 'number',
67299
67300 description: deprecationWarning,
67301 },
67302 visible: {
67303 valType: 'boolean',
67304
67305
67306 }
67307 };
67308
67309 return extendFlat({}, nonCommonAttrs, commonAttrs);
67310}
67311
67312module.exports = overrideAll({
67313 radialaxis: mergeAttrs('radial', {
67314 range: {
67315 valType: 'info_array',
67316
67317 items: [
67318 { valType: 'number' },
67319 { valType: 'number' }
67320 ],
67321
67322 },
67323 domain: domainAttr,
67324 orientation: {
67325 valType: 'number',
67326
67327
67328 }
67329 }),
67330
67331 angularaxis: mergeAttrs('angular', {
67332 range: {
67333 valType: 'info_array',
67334
67335 items: [
67336 { valType: 'number', dflt: 0 },
67337 { valType: 'number', dflt: 360 }
67338 ],
67339
67340 },
67341 domain: domainAttr
67342 }),
67343
67344 // attributes that appear at layout root
67345 layout: {
67346 direction: {
67347 valType: 'enumerated',
67348 values: ['clockwise', 'counterclockwise'],
67349
67350
67351 },
67352 orientation: {
67353 valType: 'angle',
67354
67355
67356 }
67357 }
67358}, 'plot', 'nested');
67359
67360},{"../../../lib/extend":173,"../../../plot_api/edit_types":205,"../../cartesian/layout_attributes":236}],259:[function(_dereq_,module,exports){
67361/**
67362* Copyright 2012-2020, Plotly, Inc.
67363* All rights reserved.
67364*
67365* This source code is licensed under the MIT license found in the
67366* LICENSE file in the root directory of this source tree.
67367*/
67368
67369'use strict';
67370
67371var Polar = module.exports = _dereq_('./micropolar');
67372
67373Polar.manager = _dereq_('./micropolar_manager');
67374
67375},{"./micropolar":260,"./micropolar_manager":261}],260:[function(_dereq_,module,exports){
67376/**
67377* Copyright 2012-2020, Plotly, Inc.
67378* All rights reserved.
67379*
67380* This source code is licensed under the MIT license found in the
67381* LICENSE file in the root directory of this source tree.
67382*/
67383
67384var d3 = _dereq_('d3');
67385var Lib = _dereq_('../../../lib');
67386var extendDeepAll = Lib.extendDeepAll;
67387var MID_SHIFT = _dereq_('../../../constants/alignment').MID_SHIFT;
67388
67389var µ = module.exports = { version: '0.2.2' };
67390
67391µ.Axis = function module() {
67392 var config = {
67393 data: [],
67394 layout: {}
67395 }, inputConfig = {}, liveConfig = {};
67396 var svg, container, dispatch = d3.dispatch('hover'), radialScale, angularScale;
67397 var exports = {};
67398 function render(_container) {
67399 container = _container || container;
67400 var data = config.data;
67401 var axisConfig = config.layout;
67402 if (typeof container == 'string' || container.nodeName) container = d3.select(container);
67403 container.datum(data).each(function(_data, _index) {
67404 var dataOriginal = _data.slice();
67405 liveConfig = {
67406 data: µ.util.cloneJson(dataOriginal),
67407 layout: µ.util.cloneJson(axisConfig)
67408 };
67409 var colorIndex = 0;
67410 dataOriginal.forEach(function(d, i) {
67411 if (!d.color) {
67412 d.color = axisConfig.defaultColorRange[colorIndex];
67413 colorIndex = (colorIndex + 1) % axisConfig.defaultColorRange.length;
67414 }
67415 if (!d.strokeColor) {
67416 d.strokeColor = d.geometry === 'LinePlot' ? d.color : d3.rgb(d.color).darker().toString();
67417 }
67418 liveConfig.data[i].color = d.color;
67419 liveConfig.data[i].strokeColor = d.strokeColor;
67420 liveConfig.data[i].strokeDash = d.strokeDash;
67421 liveConfig.data[i].strokeSize = d.strokeSize;
67422 });
67423 var data = dataOriginal.filter(function(d, i) {
67424 var visible = d.visible;
67425 return typeof visible === 'undefined' || visible === true;
67426 });
67427 var isStacked = false;
67428 var dataWithGroupId = data.map(function(d, i) {
67429 isStacked = isStacked || typeof d.groupId !== 'undefined';
67430 return d;
67431 });
67432 if (isStacked) {
67433 var grouped = d3.nest().key(function(d, i) {
67434 return typeof d.groupId != 'undefined' ? d.groupId : 'unstacked';
67435 }).entries(dataWithGroupId);
67436 var dataYStack = [];
67437 var stacked = grouped.map(function(d, i) {
67438 if (d.key === 'unstacked') return d.values; else {
67439 var prevArray = d.values[0].r.map(function(d, i) {
67440 return 0;
67441 });
67442 d.values.forEach(function(d, i, a) {
67443 d.yStack = [ prevArray ];
67444 dataYStack.push(prevArray);
67445 prevArray = µ.util.sumArrays(d.r, prevArray);
67446 });
67447 return d.values;
67448 }
67449 });
67450 data = d3.merge(stacked);
67451 }
67452 data.forEach(function(d, i) {
67453 d.t = Array.isArray(d.t[0]) ? d.t : [ d.t ];
67454 d.r = Array.isArray(d.r[0]) ? d.r : [ d.r ];
67455 });
67456 var radius = Math.min(axisConfig.width - axisConfig.margin.left - axisConfig.margin.right, axisConfig.height - axisConfig.margin.top - axisConfig.margin.bottom) / 2;
67457 radius = Math.max(10, radius);
67458 var chartCenter = [ axisConfig.margin.left + radius, axisConfig.margin.top + radius ];
67459 var extent;
67460 if (isStacked) {
67461 var highestStackedValue = d3.max(µ.util.sumArrays(µ.util.arrayLast(data).r[0], µ.util.arrayLast(dataYStack)));
67462 extent = [ 0, highestStackedValue ];
67463 } else extent = d3.extent(µ.util.flattenArray(data.map(function(d, i) {
67464 return d.r;
67465 })));
67466 if (axisConfig.radialAxis.domain != µ.DATAEXTENT) extent[0] = 0;
67467 radialScale = d3.scale.linear().domain(axisConfig.radialAxis.domain != µ.DATAEXTENT && axisConfig.radialAxis.domain ? axisConfig.radialAxis.domain : extent).range([ 0, radius ]);
67468 liveConfig.layout.radialAxis.domain = radialScale.domain();
67469 var angularDataMerged = µ.util.flattenArray(data.map(function(d, i) {
67470 return d.t;
67471 }));
67472 var isOrdinal = typeof angularDataMerged[0] === 'string';
67473 var ticks;
67474 if (isOrdinal) {
67475 angularDataMerged = µ.util.deduplicate(angularDataMerged);
67476 ticks = angularDataMerged.slice();
67477 angularDataMerged = d3.range(angularDataMerged.length);
67478 data = data.map(function(d, i) {
67479 var result = d;
67480 d.t = [ angularDataMerged ];
67481 if (isStacked) result.yStack = d.yStack;
67482 return result;
67483 });
67484 }
67485 var hasOnlyLineOrDotPlot = data.filter(function(d, i) {
67486 return d.geometry === 'LinePlot' || d.geometry === 'DotPlot';
67487 }).length === data.length;
67488 var needsEndSpacing = axisConfig.needsEndSpacing === null ? isOrdinal || !hasOnlyLineOrDotPlot : axisConfig.needsEndSpacing;
67489 var useProvidedDomain = axisConfig.angularAxis.domain && axisConfig.angularAxis.domain != µ.DATAEXTENT && !isOrdinal && axisConfig.angularAxis.domain[0] >= 0;
67490 var angularDomain = useProvidedDomain ? axisConfig.angularAxis.domain : d3.extent(angularDataMerged);
67491 var angularDomainStep = Math.abs(angularDataMerged[1] - angularDataMerged[0]);
67492 if (hasOnlyLineOrDotPlot && !isOrdinal) angularDomainStep = 0;
67493 var angularDomainWithPadding = angularDomain.slice();
67494 if (needsEndSpacing && isOrdinal) angularDomainWithPadding[1] += angularDomainStep;
67495 var tickCount = axisConfig.angularAxis.ticksCount || 4;
67496 if (tickCount > 8) tickCount = tickCount / (tickCount / 8) + tickCount % 8;
67497 if (axisConfig.angularAxis.ticksStep) {
67498 tickCount = (angularDomainWithPadding[1] - angularDomainWithPadding[0]) / tickCount;
67499 }
67500 var angularTicksStep = axisConfig.angularAxis.ticksStep || (angularDomainWithPadding[1] - angularDomainWithPadding[0]) / (tickCount * (axisConfig.minorTicks + 1));
67501 if (ticks) angularTicksStep = Math.max(Math.round(angularTicksStep), 1);
67502 if (!angularDomainWithPadding[2]) angularDomainWithPadding[2] = angularTicksStep;
67503 var angularAxisRange = d3.range.apply(this, angularDomainWithPadding);
67504 angularAxisRange = angularAxisRange.map(function(d, i) {
67505 return parseFloat(d.toPrecision(12));
67506 });
67507 angularScale = d3.scale.linear().domain(angularDomainWithPadding.slice(0, 2)).range(axisConfig.direction === 'clockwise' ? [ 0, 360 ] : [ 360, 0 ]);
67508 liveConfig.layout.angularAxis.domain = angularScale.domain();
67509 liveConfig.layout.angularAxis.endPadding = needsEndSpacing ? angularDomainStep : 0;
67510 svg = d3.select(this).select('svg.chart-root');
67511 if (typeof svg === 'undefined' || svg.empty()) {
67512 var skeleton = "<svg xmlns='http://www.w3.org/2000/svg' class='chart-root'>' + '<g class='outer-group'>' + '<g class='chart-group'>' + '<circle class='background-circle'></circle>' + '<g class='geometry-group'></g>' + '<g class='radial axis-group'>' + '<circle class='outside-circle'></circle>' + '</g>' + '<g class='angular axis-group'></g>' + '<g class='guides-group'><line></line><circle r='0'></circle></g>' + '</g>' + '<g class='legend-group'></g>' + '<g class='tooltips-group'></g>' + '<g class='title-group'><text></text></g>' + '</g>' + '</svg>";
67513 var doc = new DOMParser().parseFromString(skeleton, 'application/xml');
67514 var newSvg = this.appendChild(this.ownerDocument.importNode(doc.documentElement, true));
67515 svg = d3.select(newSvg);
67516 }
67517 svg.select('.guides-group').style({
67518 'pointer-events': 'none'
67519 });
67520 svg.select('.angular.axis-group').style({
67521 'pointer-events': 'none'
67522 });
67523 svg.select('.radial.axis-group').style({
67524 'pointer-events': 'none'
67525 });
67526 var chartGroup = svg.select('.chart-group');
67527 var lineStyle = {
67528 fill: 'none',
67529 stroke: axisConfig.tickColor
67530 };
67531 var fontStyle = {
67532 'font-size': axisConfig.font.size,
67533 'font-family': axisConfig.font.family,
67534 fill: axisConfig.font.color,
67535 'text-shadow': [ '-1px 0px', '1px -1px', '-1px 1px', '1px 1px' ].map(function(d, i) {
67536 return ' ' + d + ' 0 ' + axisConfig.font.outlineColor;
67537 }).join(',')
67538 };
67539 var legendContainer;
67540 if (axisConfig.showLegend) {
67541 legendContainer = svg.select('.legend-group').attr({
67542 transform: 'translate(' + [ radius, axisConfig.margin.top ] + ')'
67543 }).style({
67544 display: 'block'
67545 });
67546 var elements = data.map(function(d, i) {
67547 var datumClone = µ.util.cloneJson(d);
67548 datumClone.symbol = d.geometry === 'DotPlot' ? d.dotType || 'circle' : d.geometry != 'LinePlot' ? 'square' : 'line';
67549 datumClone.visibleInLegend = typeof d.visibleInLegend === 'undefined' || d.visibleInLegend;
67550 datumClone.color = d.geometry === 'LinePlot' ? d.strokeColor : d.color;
67551 return datumClone;
67552 });
67553
67554 µ.Legend().config({
67555 data: data.map(function(d, i) {
67556 return d.name || 'Element' + i;
67557 }),
67558 legendConfig: extendDeepAll({},
67559 µ.Legend.defaultConfig().legendConfig,
67560 {
67561 container: legendContainer,
67562 elements: elements,
67563 reverseOrder: axisConfig.legend.reverseOrder
67564 }
67565 )
67566 })();
67567
67568 var legendBBox = legendContainer.node().getBBox();
67569 radius = Math.min(axisConfig.width - legendBBox.width - axisConfig.margin.left - axisConfig.margin.right, axisConfig.height - axisConfig.margin.top - axisConfig.margin.bottom) / 2;
67570 radius = Math.max(10, radius);
67571 chartCenter = [ axisConfig.margin.left + radius, axisConfig.margin.top + radius ];
67572 radialScale.range([ 0, radius ]);
67573 liveConfig.layout.radialAxis.domain = radialScale.domain();
67574 legendContainer.attr('transform', 'translate(' + [ chartCenter[0] + radius, chartCenter[1] - radius ] + ')');
67575 } else {
67576 legendContainer = svg.select('.legend-group').style({
67577 display: 'none'
67578 });
67579 }
67580 svg.attr({
67581 width: axisConfig.width,
67582 height: axisConfig.height
67583 }).style({
67584 opacity: axisConfig.opacity
67585 });
67586 chartGroup.attr('transform', 'translate(' + chartCenter + ')').style({
67587 cursor: 'crosshair'
67588 });
67589 var centeringOffset = [ (axisConfig.width - (axisConfig.margin.left + axisConfig.margin.right + radius * 2 + (legendBBox ? legendBBox.width : 0))) / 2, (axisConfig.height - (axisConfig.margin.top + axisConfig.margin.bottom + radius * 2)) / 2 ];
67590 centeringOffset[0] = Math.max(0, centeringOffset[0]);
67591 centeringOffset[1] = Math.max(0, centeringOffset[1]);
67592 svg.select('.outer-group').attr('transform', 'translate(' + centeringOffset + ')');
67593 if (axisConfig.title && axisConfig.title.text) {
67594 var title = svg.select('g.title-group text').style(fontStyle).text(axisConfig.title.text);
67595 var titleBBox = title.node().getBBox();
67596 title.attr({
67597 x: chartCenter[0] - titleBBox.width / 2,
67598 y: chartCenter[1] - radius - 20
67599 });
67600 }
67601 var radialAxis = svg.select('.radial.axis-group');
67602 if (axisConfig.radialAxis.gridLinesVisible) {
67603 var gridCircles = radialAxis.selectAll('circle.grid-circle').data(radialScale.ticks(5));
67604 gridCircles.enter().append('circle').attr({
67605 'class': 'grid-circle'
67606 }).style(lineStyle);
67607 gridCircles.attr('r', radialScale);
67608 gridCircles.exit().remove();
67609 }
67610 radialAxis.select('circle.outside-circle').attr({
67611 r: radius
67612 }).style(lineStyle);
67613 var backgroundCircle = svg.select('circle.background-circle').attr({
67614 r: radius
67615 }).style({
67616 fill: axisConfig.backgroundColor,
67617 stroke: axisConfig.stroke
67618 });
67619 function currentAngle(d, i) {
67620 return angularScale(d) % 360 + axisConfig.orientation;
67621 }
67622 if (axisConfig.radialAxis.visible) {
67623 var axis = d3.svg.axis().scale(radialScale).ticks(5).tickSize(5);
67624 radialAxis.call(axis).attr({
67625 transform: 'rotate(' + axisConfig.radialAxis.orientation + ')'
67626 });
67627 radialAxis.selectAll('.domain').style(lineStyle);
67628 radialAxis.selectAll('g>text').text(function(d, i) {
67629 return this.textContent + axisConfig.radialAxis.ticksSuffix;
67630 }).style(fontStyle).style({
67631 'text-anchor': 'start'
67632 }).attr({
67633 x: 0,
67634 y: 0,
67635 dx: 0,
67636 dy: 0,
67637 transform: function(d, i) {
67638 if (axisConfig.radialAxis.tickOrientation === 'horizontal') {
67639 return 'rotate(' + -axisConfig.radialAxis.orientation + ') translate(' + [ 0, fontStyle['font-size'] ] + ')';
67640 } else return 'translate(' + [ 0, fontStyle['font-size'] ] + ')';
67641 }
67642 });
67643 radialAxis.selectAll('g>line').style({
67644 stroke: 'black'
67645 });
67646 }
67647 var angularAxis = svg.select('.angular.axis-group').selectAll('g.angular-tick').data(angularAxisRange);
67648 var angularAxisEnter = angularAxis.enter().append('g').classed('angular-tick', true);
67649 angularAxis.attr({
67650 transform: function(d, i) {
67651 return 'rotate(' + currentAngle(d, i) + ')';
67652 }
67653 }).style({
67654 display: axisConfig.angularAxis.visible ? 'block' : 'none'
67655 });
67656 angularAxis.exit().remove();
67657 angularAxisEnter.append('line').classed('grid-line', true).classed('major', function(d, i) {
67658 return i % (axisConfig.minorTicks + 1) == 0;
67659 }).classed('minor', function(d, i) {
67660 return !(i % (axisConfig.minorTicks + 1) == 0);
67661 }).style(lineStyle);
67662 angularAxisEnter.selectAll('.minor').style({
67663 stroke: axisConfig.minorTickColor
67664 });
67665 angularAxis.select('line.grid-line').attr({
67666 x1: axisConfig.tickLength ? radius - axisConfig.tickLength : 0,
67667 x2: radius
67668 }).style({
67669 display: axisConfig.angularAxis.gridLinesVisible ? 'block' : 'none'
67670 });
67671 angularAxisEnter.append('text').classed('axis-text', true).style(fontStyle);
67672 var ticksText = angularAxis.select('text.axis-text').attr({
67673 x: radius + axisConfig.labelOffset,
67674 dy: MID_SHIFT + 'em',
67675 transform: function(d, i) {
67676 var angle = currentAngle(d, i);
67677 var rad = radius + axisConfig.labelOffset;
67678 var orient = axisConfig.angularAxis.tickOrientation;
67679 if (orient == 'horizontal') return 'rotate(' + -angle + ' ' + rad + ' 0)'; else if (orient == 'radial') return angle < 270 && angle > 90 ? 'rotate(180 ' + rad + ' 0)' : null; else return 'rotate(' + (angle <= 180 && angle > 0 ? -90 : 90) + ' ' + rad + ' 0)';
67680 }
67681 }).style({
67682 'text-anchor': 'middle',
67683 display: axisConfig.angularAxis.labelsVisible ? 'block' : 'none'
67684 }).text(function(d, i) {
67685 if (i % (axisConfig.minorTicks + 1) != 0) return '';
67686 if (ticks) {
67687 return ticks[d] + axisConfig.angularAxis.ticksSuffix;
67688 } else return d + axisConfig.angularAxis.ticksSuffix;
67689 }).style(fontStyle);
67690 if (axisConfig.angularAxis.rewriteTicks) ticksText.text(function(d, i) {
67691 if (i % (axisConfig.minorTicks + 1) != 0) return '';
67692 return axisConfig.angularAxis.rewriteTicks(this.textContent, i);
67693 });
67694 var rightmostTickEndX = d3.max(chartGroup.selectAll('.angular-tick text')[0].map(function(d, i) {
67695 return d.getCTM().e + d.getBBox().width;
67696 }));
67697 legendContainer.attr({
67698 transform: 'translate(' + [ radius + rightmostTickEndX, axisConfig.margin.top ] + ')'
67699 });
67700 var hasGeometry = svg.select('g.geometry-group').selectAll('g').size() > 0;
67701 var geometryContainer = svg.select('g.geometry-group').selectAll('g.geometry').data(data);
67702 geometryContainer.enter().append('g').attr({
67703 'class': function(d, i) {
67704 return 'geometry geometry' + i;
67705 }
67706 });
67707 geometryContainer.exit().remove();
67708 if (data[0] || hasGeometry) {
67709 var geometryConfigs = [];
67710 data.forEach(function(d, i) {
67711 var geometryConfig = {};
67712 geometryConfig.radialScale = radialScale;
67713 geometryConfig.angularScale = angularScale;
67714 geometryConfig.container = geometryContainer.filter(function(dB, iB) {
67715 return iB == i;
67716 });
67717 geometryConfig.geometry = d.geometry;
67718 geometryConfig.orientation = axisConfig.orientation;
67719 geometryConfig.direction = axisConfig.direction;
67720 geometryConfig.index = i;
67721 geometryConfigs.push({
67722 data: d,
67723 geometryConfig: geometryConfig
67724 });
67725 });
67726 var geometryConfigsGrouped = d3.nest().key(function(d, i) {
67727 return typeof d.data.groupId != 'undefined' || 'unstacked';
67728 }).entries(geometryConfigs);
67729 var geometryConfigsGrouped2 = [];
67730 geometryConfigsGrouped.forEach(function(d, i) {
67731 if (d.key === 'unstacked') geometryConfigsGrouped2 = geometryConfigsGrouped2.concat(d.values.map(function(d, i) {
67732 return [ d ];
67733 })); else geometryConfigsGrouped2.push(d.values);
67734 });
67735 geometryConfigsGrouped2.forEach(function(d, i) {
67736 var geometry;
67737 if (Array.isArray(d)) geometry = d[0].geometryConfig.geometry; else geometry = d.geometryConfig.geometry;
67738 var finalGeometryConfig = d.map(function(dB, iB) {
67739 return extendDeepAll(µ[geometry].defaultConfig(), dB);
67740 });
67741 µ[geometry]().config(finalGeometryConfig)();
67742 });
67743 }
67744 var guides = svg.select('.guides-group');
67745 var tooltipContainer = svg.select('.tooltips-group');
67746 var angularTooltip = µ.tooltipPanel().config({
67747 container: tooltipContainer,
67748 fontSize: 8
67749 })();
67750 var radialTooltip = µ.tooltipPanel().config({
67751 container: tooltipContainer,
67752 fontSize: 8
67753 })();
67754 var geometryTooltip = µ.tooltipPanel().config({
67755 container: tooltipContainer,
67756 hasTick: true
67757 })();
67758 var angularValue, radialValue;
67759 if (!isOrdinal) {
67760 var angularGuideLine = guides.select('line').attr({
67761 x1: 0,
67762 y1: 0,
67763 y2: 0
67764 }).style({
67765 stroke: 'grey',
67766 'pointer-events': 'none'
67767 });
67768 chartGroup.on('mousemove.angular-guide', function(d, i) {
67769 var mouseAngle = µ.util.getMousePos(backgroundCircle).angle;
67770 angularGuideLine.attr({
67771 x2: -radius,
67772 transform: 'rotate(' + mouseAngle + ')'
67773 }).style({
67774 opacity: .5
67775 });
67776 var angleWithOriginOffset = (mouseAngle + 180 + 360 - axisConfig.orientation) % 360;
67777 angularValue = angularScale.invert(angleWithOriginOffset);
67778 var pos = µ.util.convertToCartesian(radius + 12, mouseAngle + 180);
67779 angularTooltip.text(µ.util.round(angularValue)).move([ pos[0] + chartCenter[0], pos[1] + chartCenter[1] ]);
67780 }).on('mouseout.angular-guide', function(d, i) {
67781 guides.select('line').style({
67782 opacity: 0
67783 });
67784 });
67785 }
67786 var angularGuideCircle = guides.select('circle').style({
67787 stroke: 'grey',
67788 fill: 'none'
67789 });
67790 chartGroup.on('mousemove.radial-guide', function(d, i) {
67791 var r = µ.util.getMousePos(backgroundCircle).radius;
67792 angularGuideCircle.attr({
67793 r: r
67794 }).style({
67795 opacity: .5
67796 });
67797 radialValue = radialScale.invert(µ.util.getMousePos(backgroundCircle).radius);
67798 var pos = µ.util.convertToCartesian(r, axisConfig.radialAxis.orientation);
67799 radialTooltip.text(µ.util.round(radialValue)).move([ pos[0] + chartCenter[0], pos[1] + chartCenter[1] ]);
67800 }).on('mouseout.radial-guide', function(d, i) {
67801 angularGuideCircle.style({
67802 opacity: 0
67803 });
67804 geometryTooltip.hide();
67805 angularTooltip.hide();
67806 radialTooltip.hide();
67807 });
67808 svg.selectAll('.geometry-group .mark').on('mouseover.tooltip', function(d, i) {
67809 var el = d3.select(this);
67810 var color = this.style.fill;
67811 var newColor = 'black';
67812 var opacity = this.style.opacity || 1;
67813 el.attr({
67814 'data-opacity': opacity
67815 });
67816 if (color && color !== 'none') {
67817 el.attr({
67818 'data-fill': color
67819 });
67820 newColor = d3.hsl(color).darker().toString();
67821 el.style({
67822 fill: newColor,
67823 opacity: 1
67824 });
67825 var textData = {
67826 t: µ.util.round(d[0]),
67827 r: µ.util.round(d[1])
67828 };
67829 if (isOrdinal) textData.t = ticks[d[0]];
67830 var text = 't: ' + textData.t + ', r: ' + textData.r;
67831 var bbox = this.getBoundingClientRect();
67832 var svgBBox = svg.node().getBoundingClientRect();
67833 var pos = [ bbox.left + bbox.width / 2 - centeringOffset[0] - svgBBox.left, bbox.top + bbox.height / 2 - centeringOffset[1] - svgBBox.top ];
67834 geometryTooltip.config({
67835 color: newColor
67836 }).text(text);
67837 geometryTooltip.move(pos);
67838 } else {
67839 color = this.style.stroke || 'black';
67840 el.attr({
67841 'data-stroke': color
67842 });
67843 newColor = d3.hsl(color).darker().toString();
67844 el.style({
67845 stroke: newColor,
67846 opacity: 1
67847 });
67848 }
67849 }).on('mousemove.tooltip', function(d, i) {
67850 if (d3.event.which != 0) return false;
67851 if (d3.select(this).attr('data-fill')) geometryTooltip.show();
67852 }).on('mouseout.tooltip', function(d, i) {
67853 geometryTooltip.hide();
67854 var el = d3.select(this);
67855 var fillColor = el.attr('data-fill');
67856 if (fillColor) el.style({
67857 fill: fillColor,
67858 opacity: el.attr('data-opacity')
67859 }); else el.style({
67860 stroke: el.attr('data-stroke'),
67861 opacity: el.attr('data-opacity')
67862 });
67863 });
67864 });
67865 return exports;
67866 }
67867 exports.render = function(_container) {
67868 render(_container);
67869 return this;
67870 };
67871 exports.config = function(_x) {
67872 if (!arguments.length) return config;
67873 var xClone = µ.util.cloneJson(_x);
67874 xClone.data.forEach(function(d, i) {
67875 if (!config.data[i]) config.data[i] = {};
67876 extendDeepAll(config.data[i], µ.Axis.defaultConfig().data[0]);
67877 extendDeepAll(config.data[i], d);
67878 });
67879 extendDeepAll(config.layout, µ.Axis.defaultConfig().layout);
67880 extendDeepAll(config.layout, xClone.layout);
67881 return this;
67882 };
67883 exports.getLiveConfig = function() {
67884 return liveConfig;
67885 };
67886 exports.getinputConfig = function() {
67887 return inputConfig;
67888 };
67889 exports.radialScale = function(_x) {
67890 return radialScale;
67891 };
67892 exports.angularScale = function(_x) {
67893 return angularScale;
67894 };
67895 exports.svg = function() {
67896 return svg;
67897 };
67898 d3.rebind(exports, dispatch, 'on');
67899 return exports;
67900};
67901
67902µ.Axis.defaultConfig = function(d, i) {
67903 var config = {
67904 data: [ {
67905 t: [ 1, 2, 3, 4 ],
67906 r: [ 10, 11, 12, 13 ],
67907 name: 'Line1',
67908 geometry: 'LinePlot',
67909 color: null,
67910 strokeDash: 'solid',
67911 strokeColor: null,
67912 strokeSize: '1',
67913 visibleInLegend: true,
67914 opacity: 1
67915 } ],
67916 layout: {
67917 defaultColorRange: d3.scale.category10().range(),
67918 title: null,
67919 height: 450,
67920 width: 500,
67921 margin: {
67922 top: 40,
67923 right: 40,
67924 bottom: 40,
67925 left: 40
67926 },
67927 font: {
67928 size: 12,
67929 color: 'gray',
67930 outlineColor: 'white',
67931 family: 'Tahoma, sans-serif'
67932 },
67933 direction: 'clockwise',
67934 orientation: 0,
67935 labelOffset: 10,
67936 radialAxis: {
67937 domain: null,
67938 orientation: -45,
67939 ticksSuffix: '',
67940 visible: true,
67941 gridLinesVisible: true,
67942 tickOrientation: 'horizontal',
67943 rewriteTicks: null
67944 },
67945 angularAxis: {
67946 domain: [ 0, 360 ],
67947 ticksSuffix: '',
67948 visible: true,
67949 gridLinesVisible: true,
67950 labelsVisible: true,
67951 tickOrientation: 'horizontal',
67952 rewriteTicks: null,
67953 ticksCount: null,
67954 ticksStep: null
67955 },
67956 minorTicks: 0,
67957 tickLength: null,
67958 tickColor: 'silver',
67959 minorTickColor: '#eee',
67960 backgroundColor: 'none',
67961 needsEndSpacing: null,
67962 showLegend: true,
67963 legend: {
67964 reverseOrder: false
67965 },
67966 opacity: 1
67967 }
67968 };
67969 return config;
67970};
67971
67972µ.util = {};
67973
67974µ.DATAEXTENT = 'dataExtent';
67975
67976µ.AREA = 'AreaChart';
67977
67978µ.LINE = 'LinePlot';
67979
67980µ.DOT = 'DotPlot';
67981
67982µ.BAR = 'BarChart';
67983
67984µ.util._override = function(_objA, _objB) {
67985 for (var x in _objA) if (x in _objB) _objB[x] = _objA[x];
67986};
67987
67988µ.util._extend = function(_objA, _objB) {
67989 for (var x in _objA) _objB[x] = _objA[x];
67990};
67991
67992µ.util._rndSnd = function() {
67993 return Math.random() * 2 - 1 + (Math.random() * 2 - 1) + (Math.random() * 2 - 1);
67994};
67995
67996µ.util.dataFromEquation2 = function(_equation, _step) {
67997 var step = _step || 6;
67998 var data = d3.range(0, 360 + step, step).map(function(deg, index) {
67999 var theta = deg * Math.PI / 180;
68000 var radius = _equation(theta);
68001 return [ deg, radius ];
68002 });
68003 return data;
68004};
68005
68006µ.util.dataFromEquation = function(_equation, _step, _name) {
68007 var step = _step || 6;
68008 var t = [], r = [];
68009 d3.range(0, 360 + step, step).forEach(function(deg, index) {
68010 var theta = deg * Math.PI / 180;
68011 var radius = _equation(theta);
68012 t.push(deg);
68013 r.push(radius);
68014 });
68015 var result = {
68016 t: t,
68017 r: r
68018 };
68019 if (_name) result.name = _name;
68020 return result;
68021};
68022
68023µ.util.ensureArray = function(_val, _count) {
68024 if (typeof _val === 'undefined') return null;
68025 var arr = [].concat(_val);
68026 return d3.range(_count).map(function(d, i) {
68027 return arr[i] || arr[0];
68028 });
68029};
68030
68031µ.util.fillArrays = function(_obj, _valueNames, _count) {
68032 _valueNames.forEach(function(d, i) {
68033 _obj[d] = µ.util.ensureArray(_obj[d], _count);
68034 });
68035 return _obj;
68036};
68037
68038µ.util.cloneJson = function(json) {
68039 return JSON.parse(JSON.stringify(json));
68040};
68041
68042µ.util.validateKeys = function(obj, keys) {
68043 if (typeof keys === 'string') keys = keys.split('.');
68044 var next = keys.shift();
68045 return obj[next] && (!keys.length || objHasKeys(obj[next], keys));
68046};
68047
68048µ.util.sumArrays = function(a, b) {
68049 return d3.zip(a, b).map(function(d, i) {
68050 return d3.sum(d);
68051 });
68052};
68053
68054µ.util.arrayLast = function(a) {
68055 return a[a.length - 1];
68056};
68057
68058µ.util.arrayEqual = function(a, b) {
68059 var i = Math.max(a.length, b.length, 1);
68060 while (i-- >= 0 && a[i] === b[i]) ;
68061 return i === -2;
68062};
68063
68064µ.util.flattenArray = function(arr) {
68065 var r = [];
68066 while (!µ.util.arrayEqual(r, arr)) {
68067 r = arr;
68068 arr = [].concat.apply([], arr);
68069 }
68070 return arr;
68071};
68072
68073µ.util.deduplicate = function(arr) {
68074 return arr.filter(function(v, i, a) {
68075 return a.indexOf(v) == i;
68076 });
68077};
68078
68079µ.util.convertToCartesian = function(radius, theta) {
68080 var thetaRadians = theta * Math.PI / 180;
68081 var x = radius * Math.cos(thetaRadians);
68082 var y = radius * Math.sin(thetaRadians);
68083 return [ x, y ];
68084};
68085
68086µ.util.round = function(_value, _digits) {
68087 var digits = _digits || 2;
68088 var mult = Math.pow(10, digits);
68089 return Math.round(_value * mult) / mult;
68090};
68091
68092µ.util.getMousePos = function(_referenceElement) {
68093 var mousePos = d3.mouse(_referenceElement.node());
68094 var mouseX = mousePos[0];
68095 var mouseY = mousePos[1];
68096 var mouse = {};
68097 mouse.x = mouseX;
68098 mouse.y = mouseY;
68099 mouse.pos = mousePos;
68100 mouse.angle = (Math.atan2(mouseY, mouseX) + Math.PI) * 180 / Math.PI;
68101 mouse.radius = Math.sqrt(mouseX * mouseX + mouseY * mouseY);
68102 return mouse;
68103};
68104
68105µ.util.duplicatesCount = function(arr) {
68106 var uniques = {}, val;
68107 var dups = {};
68108 for (var i = 0, len = arr.length; i < len; i++) {
68109 val = arr[i];
68110 if (val in uniques) {
68111 uniques[val]++;
68112 dups[val] = uniques[val];
68113 } else {
68114 uniques[val] = 1;
68115 }
68116 }
68117 return dups;
68118};
68119
68120µ.util.duplicates = function(arr) {
68121 return Object.keys(µ.util.duplicatesCount(arr));
68122};
68123
68124µ.util.translator = function(obj, sourceBranch, targetBranch, reverse) {
68125 if (reverse) {
68126 var targetBranchCopy = targetBranch.slice();
68127 targetBranch = sourceBranch;
68128 sourceBranch = targetBranchCopy;
68129 }
68130 var value = sourceBranch.reduce(function(previousValue, currentValue) {
68131 if (typeof previousValue != 'undefined') return previousValue[currentValue];
68132 }, obj);
68133 if (typeof value === 'undefined') return;
68134 sourceBranch.reduce(function(previousValue, currentValue, index) {
68135 if (typeof previousValue == 'undefined') return;
68136 if (index === sourceBranch.length - 1) delete previousValue[currentValue];
68137 return previousValue[currentValue];
68138 }, obj);
68139 targetBranch.reduce(function(previousValue, currentValue, index) {
68140 if (typeof previousValue[currentValue] === 'undefined') previousValue[currentValue] = {};
68141 if (index === targetBranch.length - 1) previousValue[currentValue] = value;
68142 return previousValue[currentValue];
68143 }, obj);
68144};
68145
68146µ.PolyChart = function module() {
68147 var config = [ µ.PolyChart.defaultConfig() ];
68148 var dispatch = d3.dispatch('hover');
68149 var dashArray = {
68150 solid: 'none',
68151 dash: [ 5, 2 ],
68152 dot: [ 2, 5 ]
68153 };
68154 var colorScale;
68155 function exports() {
68156 var geometryConfig = config[0].geometryConfig;
68157 var container = geometryConfig.container;
68158 if (typeof container == 'string') container = d3.select(container);
68159 container.datum(config).each(function(_config, _index) {
68160 var isStack = !!_config[0].data.yStack;
68161 var data = _config.map(function(d, i) {
68162 if (isStack) return d3.zip(d.data.t[0], d.data.r[0], d.data.yStack[0]); else return d3.zip(d.data.t[0], d.data.r[0]);
68163 });
68164 var angularScale = geometryConfig.angularScale;
68165 var domainMin = geometryConfig.radialScale.domain()[0];
68166 var generator = {};
68167 generator.bar = function(d, i, pI) {
68168 var dataConfig = _config[pI].data;
68169 var h = geometryConfig.radialScale(d[1]) - geometryConfig.radialScale(0);
68170 var stackTop = geometryConfig.radialScale(d[2] || 0);
68171 var w = dataConfig.barWidth;
68172 d3.select(this).attr({
68173 'class': 'mark bar',
68174 d: 'M' + [ [ h + stackTop, -w / 2 ], [ h + stackTop, w / 2 ], [ stackTop, w / 2 ], [ stackTop, -w / 2 ] ].join('L') + 'Z',
68175 transform: function(d, i) {
68176 return 'rotate(' + (geometryConfig.orientation + angularScale(d[0])) + ')';
68177 }
68178 });
68179 };
68180 generator.dot = function(d, i, pI) {
68181 var stackedData = d[2] ? [ d[0], d[1] + d[2] ] : d;
68182 var symbol = d3.svg.symbol().size(_config[pI].data.dotSize).type(_config[pI].data.dotType)(d, i);
68183 d3.select(this).attr({
68184 'class': 'mark dot',
68185 d: symbol,
68186 transform: function(d, i) {
68187 var coord = convertToCartesian(getPolarCoordinates(stackedData));
68188 return 'translate(' + [ coord.x, coord.y ] + ')';
68189 }
68190 });
68191 };
68192 var line = d3.svg.line.radial().interpolate(_config[0].data.lineInterpolation).radius(function(d) {
68193 return geometryConfig.radialScale(d[1]);
68194 }).angle(function(d) {
68195 return geometryConfig.angularScale(d[0]) * Math.PI / 180;
68196 });
68197 generator.line = function(d, i, pI) {
68198 var lineData = d[2] ? data[pI].map(function(d, i) {
68199 return [ d[0], d[1] + d[2] ];
68200 }) : data[pI];
68201 d3.select(this).each(generator['dot']).style({
68202 opacity: function(dB, iB) {
68203 return +_config[pI].data.dotVisible;
68204 },
68205 fill: markStyle.stroke(d, i, pI)
68206 }).attr({
68207 'class': 'mark dot'
68208 });
68209 if (i > 0) return;
68210 var lineSelection = d3.select(this.parentNode).selectAll('path.line').data([ 0 ]);
68211 lineSelection.enter().insert('path');
68212 lineSelection.attr({
68213 'class': 'line',
68214 d: line(lineData),
68215 transform: function(dB, iB) {
68216 return 'rotate(' + (geometryConfig.orientation + 90) + ')';
68217 },
68218 'pointer-events': 'none'
68219 }).style({
68220 fill: function(dB, iB) {
68221 return markStyle.fill(d, i, pI);
68222 },
68223 'fill-opacity': 0,
68224 stroke: function(dB, iB) {
68225 return markStyle.stroke(d, i, pI);
68226 },
68227 'stroke-width': function(dB, iB) {
68228 return markStyle['stroke-width'](d, i, pI);
68229 },
68230 'stroke-dasharray': function(dB, iB) {
68231 return markStyle['stroke-dasharray'](d, i, pI);
68232 },
68233 opacity: function(dB, iB) {
68234 return markStyle.opacity(d, i, pI);
68235 },
68236 display: function(dB, iB) {
68237 return markStyle.display(d, i, pI);
68238 }
68239 });
68240 };
68241 var angularRange = geometryConfig.angularScale.range();
68242 var triangleAngle = Math.abs(angularRange[1] - angularRange[0]) / data[0].length * Math.PI / 180;
68243 var arc = d3.svg.arc().startAngle(function(d) {
68244 return -triangleAngle / 2;
68245 }).endAngle(function(d) {
68246 return triangleAngle / 2;
68247 }).innerRadius(function(d) {
68248 return geometryConfig.radialScale(domainMin + (d[2] || 0));
68249 }).outerRadius(function(d) {
68250 return geometryConfig.radialScale(domainMin + (d[2] || 0)) + geometryConfig.radialScale(d[1]);
68251 });
68252 generator.arc = function(d, i, pI) {
68253 d3.select(this).attr({
68254 'class': 'mark arc',
68255 d: arc,
68256 transform: function(d, i) {
68257 return 'rotate(' + (geometryConfig.orientation + angularScale(d[0]) + 90) + ')';
68258 }
68259 });
68260 };
68261 var markStyle = {
68262 fill: function(d, i, pI) {
68263 return _config[pI].data.color;
68264 },
68265 stroke: function(d, i, pI) {
68266 return _config[pI].data.strokeColor;
68267 },
68268 'stroke-width': function(d, i, pI) {
68269 return _config[pI].data.strokeSize + 'px';
68270 },
68271 'stroke-dasharray': function(d, i, pI) {
68272 return dashArray[_config[pI].data.strokeDash];
68273 },
68274 opacity: function(d, i, pI) {
68275 return _config[pI].data.opacity;
68276 },
68277 display: function(d, i, pI) {
68278 return typeof _config[pI].data.visible === 'undefined' || _config[pI].data.visible ? 'block' : 'none';
68279 }
68280 };
68281 var geometryLayer = d3.select(this).selectAll('g.layer').data(data);
68282 geometryLayer.enter().append('g').attr({
68283 'class': 'layer'
68284 });
68285 var geometry = geometryLayer.selectAll('path.mark').data(function(d, i) {
68286 return d;
68287 });
68288 geometry.enter().append('path').attr({
68289 'class': 'mark'
68290 });
68291 geometry.style(markStyle).each(generator[geometryConfig.geometryType]);
68292 geometry.exit().remove();
68293 geometryLayer.exit().remove();
68294 function getPolarCoordinates(d, i) {
68295 var r = geometryConfig.radialScale(d[1]);
68296 var t = (geometryConfig.angularScale(d[0]) + geometryConfig.orientation) * Math.PI / 180;
68297 return {
68298 r: r,
68299 t: t
68300 };
68301 }
68302 function convertToCartesian(polarCoordinates) {
68303 var x = polarCoordinates.r * Math.cos(polarCoordinates.t);
68304 var y = polarCoordinates.r * Math.sin(polarCoordinates.t);
68305 return {
68306 x: x,
68307 y: y
68308 };
68309 }
68310 });
68311 }
68312 exports.config = function(_x) {
68313 if (!arguments.length) return config;
68314 _x.forEach(function(d, i) {
68315 if (!config[i]) config[i] = {};
68316 extendDeepAll(config[i], µ.PolyChart.defaultConfig());
68317 extendDeepAll(config[i], d);
68318 });
68319 return this;
68320 };
68321 exports.getColorScale = function() {
68322 return colorScale;
68323 };
68324 d3.rebind(exports, dispatch, 'on');
68325 return exports;
68326};
68327
68328µ.PolyChart.defaultConfig = function() {
68329 var config = {
68330 data: {
68331 name: 'geom1',
68332 t: [ [ 1, 2, 3, 4 ] ],
68333 r: [ [ 1, 2, 3, 4 ] ],
68334 dotType: 'circle',
68335 dotSize: 64,
68336 dotVisible: false,
68337 barWidth: 20,
68338 color: '#ffa500',
68339 strokeSize: 1,
68340 strokeColor: 'silver',
68341 strokeDash: 'solid',
68342 opacity: 1,
68343 index: 0,
68344 visible: true,
68345 visibleInLegend: true
68346 },
68347 geometryConfig: {
68348 geometry: 'LinePlot',
68349 geometryType: 'arc',
68350 direction: 'clockwise',
68351 orientation: 0,
68352 container: 'body',
68353 radialScale: null,
68354 angularScale: null,
68355 colorScale: d3.scale.category20()
68356 }
68357 };
68358 return config;
68359};
68360
68361µ.BarChart = function module() {
68362 return µ.PolyChart();
68363};
68364
68365µ.BarChart.defaultConfig = function() {
68366 var config = {
68367 geometryConfig: {
68368 geometryType: 'bar'
68369 }
68370 };
68371 return config;
68372};
68373
68374µ.AreaChart = function module() {
68375 return µ.PolyChart();
68376};
68377
68378µ.AreaChart.defaultConfig = function() {
68379 var config = {
68380 geometryConfig: {
68381 geometryType: 'arc'
68382 }
68383 };
68384 return config;
68385};
68386
68387µ.DotPlot = function module() {
68388 return µ.PolyChart();
68389};
68390
68391µ.DotPlot.defaultConfig = function() {
68392 var config = {
68393 geometryConfig: {
68394 geometryType: 'dot',
68395 dotType: 'circle'
68396 }
68397 };
68398 return config;
68399};
68400
68401µ.LinePlot = function module() {
68402 return µ.PolyChart();
68403};
68404
68405µ.LinePlot.defaultConfig = function() {
68406 var config = {
68407 geometryConfig: {
68408 geometryType: 'line'
68409 }
68410 };
68411 return config;
68412};
68413
68414µ.Legend = function module() {
68415 var config = µ.Legend.defaultConfig();
68416 var dispatch = d3.dispatch('hover');
68417 function exports() {
68418 var legendConfig = config.legendConfig;
68419 var flattenData = config.data.map(function(d, i) {
68420 return [].concat(d).map(function(dB, iB) {
68421 var element = extendDeepAll({}, legendConfig.elements[i]);
68422 element.name = dB;
68423 element.color = [].concat(legendConfig.elements[i].color)[iB];
68424 return element;
68425 });
68426 });
68427 var data = d3.merge(flattenData);
68428 data = data.filter(function(d, i) {
68429 return legendConfig.elements[i] && (legendConfig.elements[i].visibleInLegend || typeof legendConfig.elements[i].visibleInLegend === 'undefined');
68430 });
68431 if (legendConfig.reverseOrder) data = data.reverse();
68432 var container = legendConfig.container;
68433 if (typeof container == 'string' || container.nodeName) container = d3.select(container);
68434 var colors = data.map(function(d, i) {
68435 return d.color;
68436 });
68437 var lineHeight = legendConfig.fontSize;
68438 var isContinuous = legendConfig.isContinuous == null ? typeof data[0] === 'number' : legendConfig.isContinuous;
68439 var height = isContinuous ? legendConfig.height : lineHeight * data.length;
68440 var legendContainerGroup = container.classed('legend-group', true);
68441 var svg = legendContainerGroup.selectAll('svg').data([ 0 ]);
68442 var svgEnter = svg.enter().append('svg').attr({
68443 width: 300,
68444 height: height + lineHeight,
68445 xmlns: 'http://www.w3.org/2000/svg',
68446 'xmlns:xlink': 'http://www.w3.org/1999/xlink',
68447 version: '1.1'
68448 });
68449 svgEnter.append('g').classed('legend-axis', true);
68450 svgEnter.append('g').classed('legend-marks', true);
68451 var dataNumbered = d3.range(data.length);
68452 var colorScale = d3.scale[isContinuous ? 'linear' : 'ordinal']().domain(dataNumbered).range(colors);
68453 var dataScale = d3.scale[isContinuous ? 'linear' : 'ordinal']().domain(dataNumbered)[isContinuous ? 'range' : 'rangePoints']([ 0, height ]);
68454 var shapeGenerator = function(_type, _size) {
68455 var squareSize = _size * 3;
68456 if (_type === 'line') {
68457 return 'M' + [ [ -_size / 2, -_size / 12 ], [ _size / 2, -_size / 12 ], [ _size / 2, _size / 12 ], [ -_size / 2, _size / 12 ] ] + 'Z';
68458 } else if (d3.svg.symbolTypes.indexOf(_type) != -1) return d3.svg.symbol().type(_type).size(squareSize)(); else return d3.svg.symbol().type('square').size(squareSize)();
68459 };
68460 if (isContinuous) {
68461 var gradient = svg.select('.legend-marks').append('defs').append('linearGradient').attr({
68462 id: 'grad1',
68463 x1: '0%',
68464 y1: '0%',
68465 x2: '0%',
68466 y2: '100%'
68467 }).selectAll('stop').data(colors);
68468 gradient.enter().append('stop');
68469 gradient.attr({
68470 offset: function(d, i) {
68471 return i / (colors.length - 1) * 100 + '%';
68472 }
68473 }).style({
68474 'stop-color': function(d, i) {
68475 return d;
68476 }
68477 });
68478 svg.append('rect').classed('legend-mark', true).attr({
68479 height: legendConfig.height,
68480 width: legendConfig.colorBandWidth,
68481 fill: 'url(#grad1)'
68482 });
68483 } else {
68484 var legendElement = svg.select('.legend-marks').selectAll('path.legend-mark').data(data);
68485 legendElement.enter().append('path').classed('legend-mark', true);
68486 legendElement.attr({
68487 transform: function(d, i) {
68488 return 'translate(' + [ lineHeight / 2, dataScale(i) + lineHeight / 2 ] + ')';
68489 },
68490 d: function(d, i) {
68491 var symbolType = d.symbol;
68492 return shapeGenerator(symbolType, lineHeight);
68493 },
68494 fill: function(d, i) {
68495 return colorScale(i);
68496 }
68497 });
68498 legendElement.exit().remove();
68499 }
68500 var legendAxis = d3.svg.axis().scale(dataScale).orient('right');
68501 var axis = svg.select('g.legend-axis').attr({
68502 transform: 'translate(' + [ isContinuous ? legendConfig.colorBandWidth : lineHeight, lineHeight / 2 ] + ')'
68503 }).call(legendAxis);
68504 axis.selectAll('.domain').style({
68505 fill: 'none',
68506 stroke: 'none'
68507 });
68508 axis.selectAll('line').style({
68509 fill: 'none',
68510 stroke: isContinuous ? legendConfig.textColor : 'none'
68511 });
68512 axis.selectAll('text').style({
68513 fill: legendConfig.textColor,
68514 'font-size': legendConfig.fontSize
68515 }).text(function(d, i) {
68516 return data[i].name;
68517 });
68518 return exports;
68519 }
68520 exports.config = function(_x) {
68521 if (!arguments.length) return config;
68522 extendDeepAll(config, _x);
68523 return this;
68524 };
68525 d3.rebind(exports, dispatch, 'on');
68526 return exports;
68527};
68528
68529µ.Legend.defaultConfig = function(d, i) {
68530 var config = {
68531 data: [ 'a', 'b', 'c' ],
68532 legendConfig: {
68533 elements: [ {
68534 symbol: 'line',
68535 color: 'red'
68536 }, {
68537 symbol: 'square',
68538 color: 'yellow'
68539 }, {
68540 symbol: 'diamond',
68541 color: 'limegreen'
68542 } ],
68543 height: 150,
68544 colorBandWidth: 30,
68545 fontSize: 12,
68546 container: 'body',
68547 isContinuous: null,
68548 textColor: 'grey',
68549 reverseOrder: false
68550 }
68551 };
68552 return config;
68553};
68554
68555µ.tooltipPanel = function() {
68556 var tooltipEl, tooltipTextEl, backgroundEl;
68557 var config = {
68558 container: null,
68559 hasTick: false,
68560 fontSize: 12,
68561 color: 'white',
68562 padding: 5
68563 };
68564 var id = 'tooltip-' + µ.tooltipPanel.uid++;
68565 var tickSize = 10;
68566 var exports = function() {
68567 tooltipEl = config.container.selectAll('g.' + id).data([ 0 ]);
68568 var tooltipEnter = tooltipEl.enter().append('g').classed(id, true).style({
68569 'pointer-events': 'none',
68570 display: 'none'
68571 });
68572 backgroundEl = tooltipEnter.append('path').style({
68573 fill: 'white',
68574 'fill-opacity': .9
68575 }).attr({
68576 d: 'M0 0'
68577 });
68578 tooltipTextEl = tooltipEnter.append('text').attr({
68579 dx: config.padding + tickSize,
68580 dy: +config.fontSize * .3
68581 });
68582 return exports;
68583 };
68584 exports.text = function(_text) {
68585 var l = d3.hsl(config.color).l;
68586 var strokeColor = l >= .5 ? '#aaa' : 'white';
68587 var fillColor = l >= .5 ? 'black' : 'white';
68588 var text = _text || '';
68589 tooltipTextEl.style({
68590 fill: fillColor,
68591 'font-size': config.fontSize + 'px'
68592 }).text(text);
68593 var padding = config.padding;
68594 var bbox = tooltipTextEl.node().getBBox();
68595 var boxStyle = {
68596 fill: config.color,
68597 stroke: strokeColor,
68598 'stroke-width': '2px'
68599 };
68600 var backGroundW = bbox.width + padding * 2 + tickSize;
68601 var backGroundH = bbox.height + padding * 2;
68602 backgroundEl.attr({
68603 d: 'M' + [ [ tickSize, -backGroundH / 2 ], [ tickSize, -backGroundH / 4 ], [ config.hasTick ? 0 : tickSize, 0 ], [ tickSize, backGroundH / 4 ], [ tickSize, backGroundH / 2 ], [ backGroundW, backGroundH / 2 ], [ backGroundW, -backGroundH / 2 ] ].join('L') + 'Z'
68604 }).style(boxStyle);
68605 tooltipEl.attr({
68606 transform: 'translate(' + [ tickSize, -backGroundH / 2 + padding * 2 ] + ')'
68607 });
68608 tooltipEl.style({
68609 display: 'block'
68610 });
68611 return exports;
68612 };
68613 exports.move = function(_pos) {
68614 if (!tooltipEl) return;
68615 tooltipEl.attr({
68616 transform: 'translate(' + [ _pos[0], _pos[1] ] + ')'
68617 }).style({
68618 display: 'block'
68619 });
68620 return exports;
68621 };
68622 exports.hide = function() {
68623 if (!tooltipEl) return;
68624 tooltipEl.style({
68625 display: 'none'
68626 });
68627 return exports;
68628 };
68629 exports.show = function() {
68630 if (!tooltipEl) return;
68631 tooltipEl.style({
68632 display: 'block'
68633 });
68634 return exports;
68635 };
68636 exports.config = function(_x) {
68637 extendDeepAll(config, _x);
68638 return exports;
68639 };
68640 return exports;
68641};
68642
68643µ.tooltipPanel.uid = 1;
68644
68645µ.adapter = {};
68646
68647µ.adapter.plotly = function module() {
68648 var exports = {};
68649 exports.convert = function(_inputConfig, reverse) {
68650 var outputConfig = {};
68651 if (_inputConfig.data) {
68652 outputConfig.data = _inputConfig.data.map(function(d, i) {
68653 var r = extendDeepAll({}, d);
68654 var toTranslate = [
68655 [ r, [ 'marker', 'color' ], [ 'color' ] ],
68656 [ r, [ 'marker', 'opacity' ], [ 'opacity' ] ],
68657 [ r, [ 'marker', 'line', 'color' ], [ 'strokeColor' ] ],
68658 [ r, [ 'marker', 'line', 'dash' ], [ 'strokeDash' ] ],
68659 [ r, [ 'marker', 'line', 'width' ], [ 'strokeSize' ] ],
68660 [ r, [ 'marker', 'symbol' ], [ 'dotType' ] ],
68661 [ r, [ 'marker', 'size' ], [ 'dotSize' ] ],
68662 [ r, [ 'marker', 'barWidth' ], [ 'barWidth' ] ],
68663 [ r, [ 'line', 'interpolation' ], [ 'lineInterpolation' ] ],
68664 [ r, [ 'showlegend' ], [ 'visibleInLegend' ] ]
68665 ];
68666 toTranslate.forEach(function(d, i) {
68667 µ.util.translator.apply(null, d.concat(reverse));
68668 });
68669
68670 if (!reverse) delete r.marker;
68671 if (reverse) delete r.groupId;
68672 if (!reverse) {
68673 if (r.type === 'scatter') {
68674 if (r.mode === 'lines') r.geometry = 'LinePlot'; else if (r.mode === 'markers') r.geometry = 'DotPlot'; else if (r.mode === 'lines+markers') {
68675 r.geometry = 'LinePlot';
68676 r.dotVisible = true;
68677 }
68678 } else if (r.type === 'area') r.geometry = 'AreaChart'; else if (r.type === 'bar') r.geometry = 'BarChart';
68679 delete r.mode;
68680 delete r.type;
68681 } else {
68682 if (r.geometry === 'LinePlot') {
68683 r.type = 'scatter';
68684 if (r.dotVisible === true) {
68685 delete r.dotVisible;
68686 r.mode = 'lines+markers';
68687 } else r.mode = 'lines';
68688 } else if (r.geometry === 'DotPlot') {
68689 r.type = 'scatter';
68690 r.mode = 'markers';
68691 } else if (r.geometry === 'AreaChart') r.type = 'area'; else if (r.geometry === 'BarChart') r.type = 'bar';
68692 delete r.geometry;
68693 }
68694 return r;
68695 });
68696 if (!reverse && _inputConfig.layout && _inputConfig.layout.barmode === 'stack') {
68697 var duplicates = µ.util.duplicates(outputConfig.data.map(function(d, i) {
68698 return d.geometry;
68699 }));
68700 outputConfig.data.forEach(function(d, i) {
68701 var idx = duplicates.indexOf(d.geometry);
68702 if (idx != -1) outputConfig.data[i].groupId = idx;
68703 });
68704 }
68705 }
68706 if (_inputConfig.layout) {
68707 var r = extendDeepAll({}, _inputConfig.layout);
68708 var toTranslate = [
68709 [ r, [ 'plot_bgcolor' ], [ 'backgroundColor' ] ],
68710 [ r, [ 'showlegend' ], [ 'showLegend' ] ],
68711 [ r, [ 'radialaxis' ], [ 'radialAxis' ] ],
68712 [ r, [ 'angularaxis' ], [ 'angularAxis' ] ],
68713 [ r.angularaxis, [ 'showline' ], [ 'gridLinesVisible' ] ],
68714 [ r.angularaxis, [ 'showticklabels' ], [ 'labelsVisible' ] ],
68715 [ r.angularaxis, [ 'nticks' ], [ 'ticksCount' ] ],
68716 [ r.angularaxis, [ 'tickorientation' ], [ 'tickOrientation' ] ],
68717 [ r.angularaxis, [ 'ticksuffix' ], [ 'ticksSuffix' ] ],
68718 [ r.angularaxis, [ 'range' ], [ 'domain' ] ],
68719 [ r.angularaxis, [ 'endpadding' ], [ 'endPadding' ] ],
68720 [ r.radialaxis, [ 'showline' ], [ 'gridLinesVisible' ] ],
68721 [ r.radialaxis, [ 'tickorientation' ], [ 'tickOrientation' ] ],
68722 [ r.radialaxis, [ 'ticksuffix' ], [ 'ticksSuffix' ] ],
68723 [ r.radialaxis, [ 'range' ], [ 'domain' ] ],
68724 [ r.angularAxis, [ 'showline' ], [ 'gridLinesVisible' ] ],
68725 [ r.angularAxis, [ 'showticklabels' ], [ 'labelsVisible' ] ],
68726 [ r.angularAxis, [ 'nticks' ], [ 'ticksCount' ] ],
68727 [ r.angularAxis, [ 'tickorientation' ], [ 'tickOrientation' ] ],
68728 [ r.angularAxis, [ 'ticksuffix' ], [ 'ticksSuffix' ] ],
68729 [ r.angularAxis, [ 'range' ], [ 'domain' ] ],
68730 [ r.angularAxis, [ 'endpadding' ], [ 'endPadding' ] ],
68731 [ r.radialAxis, [ 'showline' ], [ 'gridLinesVisible' ] ],
68732 [ r.radialAxis, [ 'tickorientation' ], [ 'tickOrientation' ] ],
68733 [ r.radialAxis, [ 'ticksuffix' ], [ 'ticksSuffix' ] ],
68734 [ r.radialAxis, [ 'range' ], [ 'domain' ] ],
68735 [ r.font, [ 'outlinecolor' ], [ 'outlineColor' ] ],
68736 [ r.legend, [ 'traceorder' ], [ 'reverseOrder' ] ],
68737 [ r, [ 'labeloffset' ], [ 'labelOffset' ] ],
68738 [ r, [ 'defaultcolorrange' ], [ 'defaultColorRange' ] ]
68739 ];
68740 toTranslate.forEach(function(d, i) {
68741 µ.util.translator.apply(null, d.concat(reverse));
68742 });
68743
68744 if (!reverse) {
68745 if (r.angularAxis && typeof r.angularAxis.ticklen !== 'undefined') r.tickLength = r.angularAxis.ticklen;
68746 if (r.angularAxis && typeof r.angularAxis.tickcolor !== 'undefined') r.tickColor = r.angularAxis.tickcolor;
68747 } else {
68748 if (typeof r.tickLength !== 'undefined') {
68749 r.angularaxis.ticklen = r.tickLength;
68750 delete r.tickLength;
68751 }
68752 if (r.tickColor) {
68753 r.angularaxis.tickcolor = r.tickColor;
68754 delete r.tickColor;
68755 }
68756 }
68757 if (r.legend && typeof r.legend.reverseOrder != 'boolean') {
68758 r.legend.reverseOrder = r.legend.reverseOrder != 'normal';
68759 }
68760 if (r.legend && typeof r.legend.traceorder == 'boolean') {
68761 r.legend.traceorder = r.legend.traceorder ? 'reversed' : 'normal';
68762 delete r.legend.reverseOrder;
68763 }
68764 if (r.margin && typeof r.margin.t != 'undefined') {
68765 var source = [ 't', 'r', 'b', 'l', 'pad' ];
68766 var target = [ 'top', 'right', 'bottom', 'left', 'pad' ];
68767 var margin = {};
68768 d3.entries(r.margin).forEach(function(dB, iB) {
68769 margin[target[source.indexOf(dB.key)]] = dB.value;
68770 });
68771 r.margin = margin;
68772 }
68773 if (reverse) {
68774 delete r.needsEndSpacing;
68775 delete r.minorTickColor;
68776 delete r.minorTicks;
68777 delete r.angularaxis.ticksCount;
68778 delete r.angularaxis.ticksCount;
68779 delete r.angularaxis.ticksStep;
68780 delete r.angularaxis.rewriteTicks;
68781 delete r.angularaxis.nticks;
68782 delete r.radialaxis.ticksCount;
68783 delete r.radialaxis.ticksCount;
68784 delete r.radialaxis.ticksStep;
68785 delete r.radialaxis.rewriteTicks;
68786 delete r.radialaxis.nticks;
68787 }
68788 outputConfig.layout = r;
68789 }
68790 return outputConfig;
68791 };
68792 return exports;
68793};
68794
68795},{"../../../constants/alignment":154,"../../../lib":178,"d3":16}],261:[function(_dereq_,module,exports){
68796/**
68797* Copyright 2012-2020, Plotly, Inc.
68798* All rights reserved.
68799*
68800* This source code is licensed under the MIT license found in the
68801* LICENSE file in the root directory of this source tree.
68802*/
68803
68804/* eslint-disable new-cap */
68805
68806'use strict';
68807
68808var d3 = _dereq_('d3');
68809var Lib = _dereq_('../../../lib');
68810var Color = _dereq_('../../../components/color');
68811
68812var micropolar = _dereq_('./micropolar');
68813var UndoManager = _dereq_('./undo_manager');
68814var extendDeepAll = Lib.extendDeepAll;
68815
68816var manager = module.exports = {};
68817
68818manager.framework = function(_gd) {
68819 var config, previousConfigClone, plot, convertedInput, container;
68820 var undoManager = new UndoManager();
68821
68822 function exports(_inputConfig, _container) {
68823 if(_container) container = _container;
68824 d3.select(d3.select(container).node().parentNode).selectAll('.svg-container>*:not(.chart-root)').remove();
68825
68826 config = (!config) ?
68827 _inputConfig :
68828 extendDeepAll(config, _inputConfig);
68829
68830 if(!plot) plot = micropolar.Axis();
68831 convertedInput = micropolar.adapter.plotly().convert(config);
68832 plot.config(convertedInput).render(container);
68833 _gd.data = config.data;
68834 _gd.layout = config.layout;
68835 manager.fillLayout(_gd);
68836 return config;
68837 }
68838 exports.isPolar = true;
68839 exports.svg = function() { return plot.svg(); };
68840 exports.getConfig = function() { return config; };
68841 exports.getLiveConfig = function() {
68842 return micropolar.adapter.plotly().convert(plot.getLiveConfig(), true);
68843 };
68844 exports.getLiveScales = function() { return {t: plot.angularScale(), r: plot.radialScale()}; };
68845 exports.setUndoPoint = function() {
68846 var that = this;
68847 var configClone = micropolar.util.cloneJson(config);
68848 (function(_configClone, _previousConfigClone) {
68849 undoManager.add({
68850 undo: function() {
68851 if(_previousConfigClone) that(_previousConfigClone);
68852 },
68853 redo: function() {
68854 that(_configClone);
68855 }
68856 });
68857 })(configClone, previousConfigClone);
68858 previousConfigClone = micropolar.util.cloneJson(configClone);
68859 };
68860 exports.undo = function() { undoManager.undo(); };
68861 exports.redo = function() { undoManager.redo(); };
68862 return exports;
68863};
68864
68865manager.fillLayout = function(_gd) {
68866 var container = d3.select(_gd).selectAll('.plot-container');
68867 var paperDiv = container.selectAll('.svg-container');
68868 var paper = _gd.framework && _gd.framework.svg && _gd.framework.svg();
68869 var dflts = {
68870 width: 800,
68871 height: 600,
68872 paper_bgcolor: Color.background,
68873 _container: container,
68874 _paperdiv: paperDiv,
68875 _paper: paper
68876 };
68877
68878 _gd._fullLayout = extendDeepAll(dflts, _gd.layout);
68879};
68880
68881},{"../../../components/color":52,"../../../lib":178,"./micropolar":260,"./undo_manager":262,"d3":16}],262:[function(_dereq_,module,exports){
68882/**
68883* Copyright 2012-2020, Plotly, Inc.
68884* All rights reserved.
68885*
68886* This source code is licensed under the MIT license found in the
68887* LICENSE file in the root directory of this source tree.
68888*/
68889
68890'use strict';
68891
68892// Modified from https://github.com/ArthurClemens/Javascript-Undo-Manager
68893// Copyright (c) 2010-2013 Arthur Clemens, arthur@visiblearea.com
68894module.exports = function UndoManager() {
68895 var undoCommands = [];
68896 var index = -1;
68897 var isExecuting = false;
68898 var callback;
68899
68900 function execute(command, action) {
68901 if(!command) return this;
68902
68903 isExecuting = true;
68904 command[action]();
68905 isExecuting = false;
68906
68907 return this;
68908 }
68909
68910 return {
68911 add: function(command) {
68912 if(isExecuting) return this;
68913 undoCommands.splice(index + 1, undoCommands.length - index);
68914 undoCommands.push(command);
68915 index = undoCommands.length - 1;
68916 return this;
68917 },
68918 setCallback: function(callbackFunc) { callback = callbackFunc; },
68919 undo: function() {
68920 var command = undoCommands[index];
68921 if(!command) return this;
68922 execute(command, 'undo');
68923 index -= 1;
68924 if(callback) callback(command.undo);
68925 return this;
68926 },
68927 redo: function() {
68928 var command = undoCommands[index + 1];
68929 if(!command) return this;
68930 execute(command, 'redo');
68931 index += 1;
68932 if(callback) callback(command.redo);
68933 return this;
68934 },
68935 clear: function() {
68936 undoCommands = [];
68937 index = -1;
68938 },
68939 hasUndo: function() { return index !== -1; },
68940 hasRedo: function() { return index < (undoCommands.length - 1); },
68941 getCommands: function() { return undoCommands; },
68942 getPreviousCommand: function() { return undoCommands[index - 1]; },
68943 getIndex: function() { return index; }
68944 };
68945};
68946
68947},{}],263:[function(_dereq_,module,exports){
68948/**
68949* Copyright 2012-2020, Plotly, Inc.
68950* All rights reserved.
68951*
68952* This source code is licensed under the MIT license found in the
68953* LICENSE file in the root directory of this source tree.
68954*/
68955
68956
68957'use strict';
68958
68959var Lib = _dereq_('../lib');
68960var Template = _dereq_('../plot_api/plot_template');
68961var handleDomainDefaults = _dereq_('./domain').defaults;
68962
68963
68964/**
68965 * Find and supply defaults to all subplots of a given type
68966 * This handles subplots that are contained within one container - so
68967 * gl3d, geo, ternary... but not 2d axes which have separate x and y axes
68968 * finds subplots, coerces their `domain` attributes, then calls the
68969 * given handleDefaults function to fill in everything else.
68970 *
68971 * layoutIn: the complete user-supplied input layout
68972 * layoutOut: the complete finished layout
68973 * fullData: the finished data array, used only to find subplots
68974 * opts: {
68975 * type: subplot type string
68976 * attributes: subplot attributes object
68977 * partition: 'x' or 'y', which direction to divide domain space by default
68978 * (default 'x', ie side-by-side subplots)
68979 * TODO: this option is only here because 3D and geo made opposite
68980 * choices in this regard previously and I didn't want to change it.
68981 * Instead we should do:
68982 * - something consistent
68983 * - something more square (4 cuts 2x2, 5/6 cuts 2x3, etc.)
68984 * - something that includes all subplot types in one arrangement,
68985 * now that we can have them together!
68986 * handleDefaults: function of (subplotLayoutIn, subplotLayoutOut, coerce, opts)
68987 * this opts object is passed through to handleDefaults, so attach any
68988 * additional items needed by this function here as well
68989 * }
68990 */
68991module.exports = function handleSubplotDefaults(layoutIn, layoutOut, fullData, opts) {
68992 var subplotType = opts.type;
68993 var subplotAttributes = opts.attributes;
68994 var handleDefaults = opts.handleDefaults;
68995 var partition = opts.partition || 'x';
68996
68997 var ids = layoutOut._subplots[subplotType];
68998 var idsLength = ids.length;
68999
69000 var baseId = idsLength && ids[0].replace(/\d+$/, '');
69001
69002 var subplotLayoutIn, subplotLayoutOut;
69003
69004 function coerce(attr, dflt) {
69005 return Lib.coerce(subplotLayoutIn, subplotLayoutOut, subplotAttributes, attr, dflt);
69006 }
69007
69008 for(var i = 0; i < idsLength; i++) {
69009 var id = ids[i];
69010
69011 // ternary traces get a layout ternary for free!
69012 if(layoutIn[id]) subplotLayoutIn = layoutIn[id];
69013 else subplotLayoutIn = layoutIn[id] = {};
69014
69015 subplotLayoutOut = Template.newContainer(layoutOut, id, baseId);
69016
69017 // All subplot containers get a `uirevision` inheriting from the base.
69018 // Currently all subplots containers have some user interaction
69019 // attributes, but if we ever add one that doesn't, we would need an
69020 // option to skip this step.
69021 coerce('uirevision', layoutOut.uirevision);
69022
69023 var dfltDomains = {};
69024 dfltDomains[partition] = [i / idsLength, (i + 1) / idsLength];
69025 handleDomainDefaults(subplotLayoutOut, layoutOut, coerce, dfltDomains);
69026
69027 opts.id = id;
69028 handleDefaults(subplotLayoutIn, subplotLayoutOut, coerce, opts);
69029 }
69030};
69031
69032},{"../lib":178,"../plot_api/plot_template":212,"./domain":249}],264:[function(_dereq_,module,exports){
69033/**
69034* Copyright 2012-2020, Plotly, Inc.
69035* All rights reserved.
69036*
69037* This source code is licensed under the MIT license found in the
69038* LICENSE file in the root directory of this source tree.
69039*/
69040
69041'use strict';
69042
69043var FORMAT_LINK = _dereq_('../constants/docs').FORMAT_LINK;
69044var DATE_FORMAT_LINK = _dereq_('../constants/docs').DATE_FORMAT_LINK;
69045
69046var templateFormatStringDescription = [
69047 'Variables are inserted using %{variable}, for example "y: %{y}".',
69048 'Numbers are formatted using d3-format\'s syntax %{variable:d3-format}, for example "Price: %{y:$.2f}".',
69049 FORMAT_LINK,
69050 'for details on the formatting syntax.',
69051 'Dates are formatted using d3-time-format\'s syntax %{variable|d3-time-format}, for example "Day: %{2019-01-01|%A}".',
69052 DATE_FORMAT_LINK,
69053 'for details on the date formatting syntax.'
69054].join(' ');
69055
69056function describeVariables(extra) {
69057 var descPart = extra.description ? ' ' + extra.description : '';
69058 var keys = extra.keys || [];
69059 if(keys.length > 0) {
69060 var quotedKeys = [];
69061 for(var i = 0; i < keys.length; i++) {
69062 quotedKeys[i] = '`' + keys[i] + '`';
69063 }
69064 descPart = descPart + 'Finally, the template string has access to ';
69065 if(keys.length === 1) {
69066 descPart = 'variable ' + quotedKeys[0];
69067 } else {
69068 descPart = 'variables ' + quotedKeys.slice(0, -1).join(', ') + ' and ' + quotedKeys.slice(-1) + '.';
69069 }
69070 }
69071 return descPart;
69072}
69073
69074exports.hovertemplateAttrs = function(opts, extra) {
69075 opts = opts || {};
69076 extra = extra || {};
69077
69078 var descPart = describeVariables(extra);
69079
69080 var hovertemplate = {
69081 valType: 'string',
69082
69083 dflt: '',
69084 editType: opts.editType || 'none',
69085
69086 };
69087
69088 if(opts.arrayOk !== false) {
69089 hovertemplate.arrayOk = true;
69090 }
69091
69092 return hovertemplate;
69093};
69094
69095exports.texttemplateAttrs = function(opts, extra) {
69096 opts = opts || {};
69097 extra = extra || {};
69098
69099 var descPart = describeVariables(extra);
69100
69101 var texttemplate = {
69102 valType: 'string',
69103
69104 dflt: '',
69105 editType: opts.editType || 'calc',
69106
69107 };
69108
69109 if(opts.arrayOk !== false) {
69110 texttemplate.arrayOk = true;
69111 }
69112 return texttemplate;
69113};
69114
69115},{"../constants/docs":155}],265:[function(_dereq_,module,exports){
69116/**
69117* Copyright 2012-2020, Plotly, Inc.
69118* All rights reserved.
69119*
69120* This source code is licensed under the MIT license found in the
69121* LICENSE file in the root directory of this source tree.
69122*/
69123
69124
69125'use strict';
69126
69127var Ternary = _dereq_('./ternary');
69128
69129var getSubplotCalcData = _dereq_('../../plots/get_data').getSubplotCalcData;
69130var counterRegex = _dereq_('../../lib').counterRegex;
69131var TERNARY = 'ternary';
69132
69133exports.name = TERNARY;
69134
69135var attr = exports.attr = 'subplot';
69136
69137exports.idRoot = TERNARY;
69138
69139exports.idRegex = exports.attrRegex = counterRegex(TERNARY);
69140
69141var attributes = exports.attributes = {};
69142attributes[attr] = {
69143 valType: 'subplotid',
69144
69145 dflt: 'ternary',
69146 editType: 'calc',
69147
69148};
69149
69150exports.layoutAttributes = _dereq_('./layout_attributes');
69151
69152exports.supplyLayoutDefaults = _dereq_('./layout_defaults');
69153
69154exports.plot = function plot(gd) {
69155 var fullLayout = gd._fullLayout;
69156 var calcData = gd.calcdata;
69157 var ternaryIds = fullLayout._subplots[TERNARY];
69158
69159 for(var i = 0; i < ternaryIds.length; i++) {
69160 var ternaryId = ternaryIds[i];
69161 var ternaryCalcData = getSubplotCalcData(calcData, TERNARY, ternaryId);
69162 var ternary = fullLayout[ternaryId]._subplot;
69163
69164 // If ternary is not instantiated, create one!
69165 if(!ternary) {
69166 ternary = new Ternary({
69167 id: ternaryId,
69168 graphDiv: gd,
69169 container: fullLayout._ternarylayer.node()
69170 },
69171 fullLayout
69172 );
69173
69174 fullLayout[ternaryId]._subplot = ternary;
69175 }
69176
69177 ternary.plot(ternaryCalcData, fullLayout, gd._promises);
69178 }
69179};
69180
69181exports.clean = function(newFullData, newFullLayout, oldFullData, oldFullLayout) {
69182 var oldTernaryKeys = oldFullLayout._subplots[TERNARY] || [];
69183
69184 for(var i = 0; i < oldTernaryKeys.length; i++) {
69185 var oldTernaryKey = oldTernaryKeys[i];
69186 var oldTernary = oldFullLayout[oldTernaryKey]._subplot;
69187
69188 if(!newFullLayout[oldTernaryKey] && !!oldTernary) {
69189 oldTernary.plotContainer.remove();
69190 oldTernary.clipDef.remove();
69191 oldTernary.clipDefRelative.remove();
69192 oldTernary.layers['a-title'].remove();
69193 oldTernary.layers['b-title'].remove();
69194 oldTernary.layers['c-title'].remove();
69195 }
69196 }
69197};
69198
69199},{"../../lib":178,"../../plots/get_data":252,"./layout_attributes":266,"./layout_defaults":267,"./ternary":268}],266:[function(_dereq_,module,exports){
69200/**
69201* Copyright 2012-2020, Plotly, Inc.
69202* All rights reserved.
69203*
69204* This source code is licensed under the MIT license found in the
69205* LICENSE file in the root directory of this source tree.
69206*/
69207
69208'use strict';
69209
69210var colorAttrs = _dereq_('../../components/color/attributes');
69211var domainAttrs = _dereq_('../domain').attributes;
69212var axesAttrs = _dereq_('../cartesian/layout_attributes');
69213
69214var overrideAll = _dereq_('../../plot_api/edit_types').overrideAll;
69215var extendFlat = _dereq_('../../lib/extend').extendFlat;
69216
69217var ternaryAxesAttrs = {
69218 title: {
69219 text: axesAttrs.title.text,
69220 font: axesAttrs.title.font
69221 // TODO does standoff here make sense?
69222 },
69223 color: axesAttrs.color,
69224 // ticks
69225 tickmode: axesAttrs.tickmode,
69226 nticks: extendFlat({}, axesAttrs.nticks, {dflt: 6, min: 1}),
69227 tick0: axesAttrs.tick0,
69228 dtick: axesAttrs.dtick,
69229 tickvals: axesAttrs.tickvals,
69230 ticktext: axesAttrs.ticktext,
69231 ticks: axesAttrs.ticks,
69232 ticklen: axesAttrs.ticklen,
69233 tickwidth: axesAttrs.tickwidth,
69234 tickcolor: axesAttrs.tickcolor,
69235 showticklabels: axesAttrs.showticklabels,
69236 showtickprefix: axesAttrs.showtickprefix,
69237 tickprefix: axesAttrs.tickprefix,
69238 showticksuffix: axesAttrs.showticksuffix,
69239 ticksuffix: axesAttrs.ticksuffix,
69240 showexponent: axesAttrs.showexponent,
69241 exponentformat: axesAttrs.exponentformat,
69242 separatethousands: axesAttrs.separatethousands,
69243 tickfont: axesAttrs.tickfont,
69244 tickangle: axesAttrs.tickangle,
69245 tickformat: axesAttrs.tickformat,
69246 tickformatstops: axesAttrs.tickformatstops,
69247 hoverformat: axesAttrs.hoverformat,
69248 // lines and grids
69249 showline: extendFlat({}, axesAttrs.showline, {dflt: true}),
69250 linecolor: axesAttrs.linecolor,
69251 linewidth: axesAttrs.linewidth,
69252 showgrid: extendFlat({}, axesAttrs.showgrid, {dflt: true}),
69253 gridcolor: axesAttrs.gridcolor,
69254 gridwidth: axesAttrs.gridwidth,
69255 layer: axesAttrs.layer,
69256 // range
69257 min: {
69258 valType: 'number',
69259 dflt: 0,
69260
69261 min: 0,
69262
69263 },
69264 _deprecated: {
69265 title: axesAttrs._deprecated.title,
69266 titlefont: axesAttrs._deprecated.titlefont
69267 }
69268};
69269
69270var attrs = module.exports = overrideAll({
69271 domain: domainAttrs({name: 'ternary'}),
69272
69273 bgcolor: {
69274 valType: 'color',
69275
69276 dflt: colorAttrs.background,
69277
69278 },
69279 sum: {
69280 valType: 'number',
69281
69282 dflt: 1,
69283 min: 0,
69284
69285 },
69286 aaxis: ternaryAxesAttrs,
69287 baxis: ternaryAxesAttrs,
69288 caxis: ternaryAxesAttrs
69289}, 'plot', 'from-root');
69290
69291// set uirevisions outside of `overrideAll` so we can get `editType: none`
69292attrs.uirevision = {
69293 valType: 'any',
69294
69295 editType: 'none',
69296
69297};
69298
69299attrs.aaxis.uirevision = attrs.baxis.uirevision = attrs.caxis.uirevision = {
69300 valType: 'any',
69301
69302 editType: 'none',
69303
69304};
69305
69306},{"../../components/color/attributes":51,"../../lib/extend":173,"../../plot_api/edit_types":205,"../cartesian/layout_attributes":236,"../domain":249}],267:[function(_dereq_,module,exports){
69307/**
69308* Copyright 2012-2020, Plotly, Inc.
69309* All rights reserved.
69310*
69311* This source code is licensed under the MIT license found in the
69312* LICENSE file in the root directory of this source tree.
69313*/
69314
69315'use strict';
69316
69317var Color = _dereq_('../../components/color');
69318var Template = _dereq_('../../plot_api/plot_template');
69319var Lib = _dereq_('../../lib');
69320
69321var handleSubplotDefaults = _dereq_('../subplot_defaults');
69322var handleTickLabelDefaults = _dereq_('../cartesian/tick_label_defaults');
69323var handleTickMarkDefaults = _dereq_('../cartesian/tick_mark_defaults');
69324var handleTickValueDefaults = _dereq_('../cartesian/tick_value_defaults');
69325var handleLineGridDefaults = _dereq_('../cartesian/line_grid_defaults');
69326var layoutAttributes = _dereq_('./layout_attributes');
69327
69328var axesNames = ['aaxis', 'baxis', 'caxis'];
69329
69330module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) {
69331 handleSubplotDefaults(layoutIn, layoutOut, fullData, {
69332 type: 'ternary',
69333 attributes: layoutAttributes,
69334 handleDefaults: handleTernaryDefaults,
69335 font: layoutOut.font,
69336 paper_bgcolor: layoutOut.paper_bgcolor
69337 });
69338};
69339
69340function handleTernaryDefaults(ternaryLayoutIn, ternaryLayoutOut, coerce, options) {
69341 var bgColor = coerce('bgcolor');
69342 var sum = coerce('sum');
69343 options.bgColor = Color.combine(bgColor, options.paper_bgcolor);
69344 var axName, containerIn, containerOut;
69345
69346 // TODO: allow most (if not all) axis attributes to be set
69347 // in the outer container and used as defaults in the individual axes?
69348
69349 for(var j = 0; j < axesNames.length; j++) {
69350 axName = axesNames[j];
69351 containerIn = ternaryLayoutIn[axName] || {};
69352 containerOut = Template.newContainer(ternaryLayoutOut, axName);
69353 containerOut._name = axName;
69354
69355 handleAxisDefaults(containerIn, containerOut, options, ternaryLayoutOut);
69356 }
69357
69358 // if the min values contradict each other, set them all to default (0)
69359 // and delete *all* the inputs so the user doesn't get confused later by
69360 // changing one and having them all change.
69361 var aaxis = ternaryLayoutOut.aaxis;
69362 var baxis = ternaryLayoutOut.baxis;
69363 var caxis = ternaryLayoutOut.caxis;
69364 if(aaxis.min + baxis.min + caxis.min >= sum) {
69365 aaxis.min = 0;
69366 baxis.min = 0;
69367 caxis.min = 0;
69368 if(ternaryLayoutIn.aaxis) delete ternaryLayoutIn.aaxis.min;
69369 if(ternaryLayoutIn.baxis) delete ternaryLayoutIn.baxis.min;
69370 if(ternaryLayoutIn.caxis) delete ternaryLayoutIn.caxis.min;
69371 }
69372}
69373
69374function handleAxisDefaults(containerIn, containerOut, options, ternaryLayoutOut) {
69375 var axAttrs = layoutAttributes[containerOut._name];
69376
69377 function coerce(attr, dflt) {
69378 return Lib.coerce(containerIn, containerOut, axAttrs, attr, dflt);
69379 }
69380
69381 coerce('uirevision', ternaryLayoutOut.uirevision);
69382
69383 containerOut.type = 'linear'; // no other types allowed for ternary
69384
69385 var dfltColor = coerce('color');
69386 // if axis.color was provided, use it for fonts too; otherwise,
69387 // inherit from global font color in case that was provided.
69388 var dfltFontColor = (dfltColor !== axAttrs.color.dflt) ? dfltColor : options.font.color;
69389
69390 var axName = containerOut._name;
69391 var letterUpper = axName.charAt(0).toUpperCase();
69392 var dfltTitle = 'Component ' + letterUpper;
69393
69394 var title = coerce('title.text', dfltTitle);
69395 containerOut._hovertitle = title === dfltTitle ? title : letterUpper;
69396
69397 Lib.coerceFont(coerce, 'title.font', {
69398 family: options.font.family,
69399 size: Math.round(options.font.size * 1.2),
69400 color: dfltFontColor
69401 });
69402
69403 // range is just set by 'min' - max is determined by the other axes mins
69404 coerce('min');
69405
69406 handleTickValueDefaults(containerIn, containerOut, coerce, 'linear');
69407 handleTickLabelDefaults(containerIn, containerOut, coerce, 'linear', {});
69408 handleTickMarkDefaults(containerIn, containerOut, coerce,
69409 { outerTicks: true });
69410
69411 var showTickLabels = coerce('showticklabels');
69412 if(showTickLabels) {
69413 Lib.coerceFont(coerce, 'tickfont', {
69414 family: options.font.family,
69415 size: options.font.size,
69416 color: dfltFontColor
69417 });
69418 coerce('tickangle');
69419 coerce('tickformat');
69420 }
69421
69422 handleLineGridDefaults(containerIn, containerOut, coerce, {
69423 dfltColor: dfltColor,
69424 bgColor: options.bgColor,
69425 // default grid color is darker here (60%, vs cartesian default ~91%)
69426 // because the grid is not square so the eye needs heavier cues to follow
69427 blend: 60,
69428 showLine: true,
69429 showGrid: true,
69430 noZeroLine: true,
69431 attributes: axAttrs
69432 });
69433
69434 coerce('hoverformat');
69435 coerce('layer');
69436}
69437
69438},{"../../components/color":52,"../../lib":178,"../../plot_api/plot_template":212,"../cartesian/line_grid_defaults":238,"../cartesian/tick_label_defaults":243,"../cartesian/tick_mark_defaults":244,"../cartesian/tick_value_defaults":245,"../subplot_defaults":263,"./layout_attributes":266}],268:[function(_dereq_,module,exports){
69439/**
69440* Copyright 2012-2020, Plotly, Inc.
69441* All rights reserved.
69442*
69443* This source code is licensed under the MIT license found in the
69444* LICENSE file in the root directory of this source tree.
69445*/
69446
69447
69448'use strict';
69449
69450var d3 = _dereq_('d3');
69451var tinycolor = _dereq_('tinycolor2');
69452
69453var Registry = _dereq_('../../registry');
69454var Lib = _dereq_('../../lib');
69455var _ = Lib._;
69456var Color = _dereq_('../../components/color');
69457var Drawing = _dereq_('../../components/drawing');
69458var setConvert = _dereq_('../cartesian/set_convert');
69459var extendFlat = _dereq_('../../lib/extend').extendFlat;
69460var Plots = _dereq_('../plots');
69461var Axes = _dereq_('../cartesian/axes');
69462var dragElement = _dereq_('../../components/dragelement');
69463var Fx = _dereq_('../../components/fx');
69464var dragHelpers = _dereq_('../../components/dragelement/helpers');
69465var freeMode = dragHelpers.freeMode;
69466var rectMode = dragHelpers.rectMode;
69467var Titles = _dereq_('../../components/titles');
69468var prepSelect = _dereq_('../cartesian/select').prepSelect;
69469var selectOnClick = _dereq_('../cartesian/select').selectOnClick;
69470var clearSelect = _dereq_('../cartesian/select').clearSelect;
69471var clearSelectionsCache = _dereq_('../cartesian/select').clearSelectionsCache;
69472var constants = _dereq_('../cartesian/constants');
69473
69474function Ternary(options, fullLayout) {
69475 this.id = options.id;
69476 this.graphDiv = options.graphDiv;
69477 this.init(fullLayout);
69478 this.makeFramework(fullLayout);
69479
69480 // unfortunately, we have to keep track of some axis tick settings
69481 // as ternary subplots do not implement the 'ticks' editType
69482 this.aTickLayout = null;
69483 this.bTickLayout = null;
69484 this.cTickLayout = null;
69485}
69486
69487module.exports = Ternary;
69488
69489var proto = Ternary.prototype;
69490
69491proto.init = function(fullLayout) {
69492 this.container = fullLayout._ternarylayer;
69493 this.defs = fullLayout._defs;
69494 this.layoutId = fullLayout._uid;
69495 this.traceHash = {};
69496 this.layers = {};
69497};
69498
69499proto.plot = function(ternaryCalcData, fullLayout) {
69500 var _this = this;
69501 var ternaryLayout = fullLayout[_this.id];
69502 var graphSize = fullLayout._size;
69503
69504 _this._hasClipOnAxisFalse = false;
69505 for(var i = 0; i < ternaryCalcData.length; i++) {
69506 var trace = ternaryCalcData[i][0].trace;
69507
69508 if(trace.cliponaxis === false) {
69509 _this._hasClipOnAxisFalse = true;
69510 break;
69511 }
69512 }
69513
69514 _this.updateLayers(ternaryLayout);
69515 _this.adjustLayout(ternaryLayout, graphSize);
69516 Plots.generalUpdatePerTraceModule(_this.graphDiv, _this, ternaryCalcData, ternaryLayout);
69517 _this.layers.plotbg.select('path').call(Color.fill, ternaryLayout.bgcolor);
69518};
69519
69520proto.makeFramework = function(fullLayout) {
69521 var _this = this;
69522 var gd = _this.graphDiv;
69523 var ternaryLayout = fullLayout[_this.id];
69524
69525 var clipId = _this.clipId = 'clip' + _this.layoutId + _this.id;
69526 var clipIdRelative = _this.clipIdRelative = 'clip-relative' + _this.layoutId + _this.id;
69527
69528 // clippath for this ternary subplot
69529 _this.clipDef = Lib.ensureSingleById(fullLayout._clips, 'clipPath', clipId, function(s) {
69530 s.append('path').attr('d', 'M0,0Z');
69531 });
69532
69533 // 'relative' clippath (i.e. no translation) for this ternary subplot
69534 _this.clipDefRelative = Lib.ensureSingleById(fullLayout._clips, 'clipPath', clipIdRelative, function(s) {
69535 s.append('path').attr('d', 'M0,0Z');
69536 });
69537
69538 // container for everything in this ternary subplot
69539 _this.plotContainer = Lib.ensureSingle(_this.container, 'g', _this.id);
69540 _this.updateLayers(ternaryLayout);
69541
69542 Drawing.setClipUrl(_this.layers.backplot, clipId, gd);
69543 Drawing.setClipUrl(_this.layers.grids, clipId, gd);
69544};
69545
69546proto.updateLayers = function(ternaryLayout) {
69547 var _this = this;
69548 var layers = _this.layers;
69549
69550 // inside that container, we have one container for the data, and
69551 // one each for the three axes around it.
69552
69553 var plotLayers = ['draglayer', 'plotbg', 'backplot', 'grids'];
69554
69555 if(ternaryLayout.aaxis.layer === 'below traces') {
69556 plotLayers.push('aaxis', 'aline');
69557 }
69558 if(ternaryLayout.baxis.layer === 'below traces') {
69559 plotLayers.push('baxis', 'bline');
69560 }
69561 if(ternaryLayout.caxis.layer === 'below traces') {
69562 plotLayers.push('caxis', 'cline');
69563 }
69564
69565 plotLayers.push('frontplot');
69566
69567 if(ternaryLayout.aaxis.layer === 'above traces') {
69568 plotLayers.push('aaxis', 'aline');
69569 }
69570 if(ternaryLayout.baxis.layer === 'above traces') {
69571 plotLayers.push('baxis', 'bline');
69572 }
69573 if(ternaryLayout.caxis.layer === 'above traces') {
69574 plotLayers.push('caxis', 'cline');
69575 }
69576
69577 var toplevel = _this.plotContainer.selectAll('g.toplevel')
69578 .data(plotLayers, String);
69579
69580 var grids = ['agrid', 'bgrid', 'cgrid'];
69581
69582 toplevel.enter().append('g')
69583 .attr('class', function(d) { return 'toplevel ' + d; })
69584 .each(function(d) {
69585 var s = d3.select(this);
69586 layers[d] = s;
69587
69588 // containers for different trace types.
69589 // NOTE - this is different from cartesian, where all traces
69590 // are in front of grids. Here I'm putting maps behind the grids
69591 // so the grids will always be visible if they're requested.
69592 // Perhaps we want that for cartesian too?
69593 if(d === 'frontplot') {
69594 s.append('g').classed('scatterlayer', true);
69595 } else if(d === 'backplot') {
69596 s.append('g').classed('maplayer', true);
69597 } else if(d === 'plotbg') {
69598 s.append('path').attr('d', 'M0,0Z');
69599 } else if(d === 'aline' || d === 'bline' || d === 'cline') {
69600 s.append('path');
69601 } else if(d === 'grids') {
69602 grids.forEach(function(d) {
69603 layers[d] = s.append('g').classed('grid ' + d, true);
69604 });
69605 }
69606 });
69607
69608 toplevel.order();
69609};
69610
69611var whRatio = Math.sqrt(4 / 3);
69612
69613proto.adjustLayout = function(ternaryLayout, graphSize) {
69614 var _this = this;
69615 var domain = ternaryLayout.domain;
69616 var xDomainCenter = (domain.x[0] + domain.x[1]) / 2;
69617 var yDomainCenter = (domain.y[0] + domain.y[1]) / 2;
69618 var xDomain = domain.x[1] - domain.x[0];
69619 var yDomain = domain.y[1] - domain.y[0];
69620 var wmax = xDomain * graphSize.w;
69621 var hmax = yDomain * graphSize.h;
69622 var sum = ternaryLayout.sum;
69623 var amin = ternaryLayout.aaxis.min;
69624 var bmin = ternaryLayout.baxis.min;
69625 var cmin = ternaryLayout.caxis.min;
69626
69627 var x0, y0, w, h, xDomainFinal, yDomainFinal;
69628
69629 if(wmax > whRatio * hmax) {
69630 h = hmax;
69631 w = h * whRatio;
69632 } else {
69633 w = wmax;
69634 h = w / whRatio;
69635 }
69636
69637 xDomainFinal = xDomain * w / wmax;
69638 yDomainFinal = yDomain * h / hmax;
69639
69640 x0 = graphSize.l + graphSize.w * xDomainCenter - w / 2;
69641 y0 = graphSize.t + graphSize.h * (1 - yDomainCenter) - h / 2;
69642
69643 _this.x0 = x0;
69644 _this.y0 = y0;
69645 _this.w = w;
69646 _this.h = h;
69647 _this.sum = sum;
69648
69649 // set up the x and y axis objects we'll use to lay out the points
69650 _this.xaxis = {
69651 type: 'linear',
69652 range: [amin + 2 * cmin - sum, sum - amin - 2 * bmin],
69653 domain: [
69654 xDomainCenter - xDomainFinal / 2,
69655 xDomainCenter + xDomainFinal / 2
69656 ],
69657 _id: 'x'
69658 };
69659 setConvert(_this.xaxis, _this.graphDiv._fullLayout);
69660 _this.xaxis.setScale();
69661 _this.xaxis.isPtWithinRange = function(d) {
69662 return (
69663 d.a >= _this.aaxis.range[0] &&
69664 d.a <= _this.aaxis.range[1] &&
69665 d.b >= _this.baxis.range[1] &&
69666 d.b <= _this.baxis.range[0] &&
69667 d.c >= _this.caxis.range[1] &&
69668 d.c <= _this.caxis.range[0]
69669 );
69670 };
69671
69672 _this.yaxis = {
69673 type: 'linear',
69674 range: [amin, sum - bmin - cmin],
69675 domain: [
69676 yDomainCenter - yDomainFinal / 2,
69677 yDomainCenter + yDomainFinal / 2
69678 ],
69679 _id: 'y'
69680 };
69681 setConvert(_this.yaxis, _this.graphDiv._fullLayout);
69682 _this.yaxis.setScale();
69683 _this.yaxis.isPtWithinRange = function() { return true; };
69684
69685 // set up the modified axes for tick drawing
69686 var yDomain0 = _this.yaxis.domain[0];
69687
69688 // aaxis goes up the left side. Set it up as a y axis, but with
69689 // fictitious angles and domain, but then rotate and translate
69690 // it into place at the end
69691 var aaxis = _this.aaxis = extendFlat({}, ternaryLayout.aaxis, {
69692 range: [amin, sum - bmin - cmin],
69693 side: 'left',
69694 // tickangle = 'auto' means 0 anyway for a y axis, need to coerce to 0 here
69695 // so we can shift by 30.
69696 tickangle: (+ternaryLayout.aaxis.tickangle || 0) - 30,
69697 domain: [yDomain0, yDomain0 + yDomainFinal * whRatio],
69698 anchor: 'free',
69699 position: 0,
69700 _id: 'y',
69701 _length: w
69702 });
69703 setConvert(aaxis, _this.graphDiv._fullLayout);
69704 aaxis.setScale();
69705
69706 // baxis goes across the bottom (backward). We can set it up as an x axis
69707 // without any enclosing transformation.
69708 var baxis = _this.baxis = extendFlat({}, ternaryLayout.baxis, {
69709 range: [sum - amin - cmin, bmin],
69710 side: 'bottom',
69711 domain: _this.xaxis.domain,
69712 anchor: 'free',
69713 position: 0,
69714 _id: 'x',
69715 _length: w
69716 });
69717 setConvert(baxis, _this.graphDiv._fullLayout);
69718 baxis.setScale();
69719
69720 // caxis goes down the right side. Set it up as a y axis, with
69721 // post-transformation similar to aaxis
69722 var caxis = _this.caxis = extendFlat({}, ternaryLayout.caxis, {
69723 range: [sum - amin - bmin, cmin],
69724 side: 'right',
69725 tickangle: (+ternaryLayout.caxis.tickangle || 0) + 30,
69726 domain: [yDomain0, yDomain0 + yDomainFinal * whRatio],
69727 anchor: 'free',
69728 position: 0,
69729 _id: 'y',
69730 _length: w
69731 });
69732 setConvert(caxis, _this.graphDiv._fullLayout);
69733 caxis.setScale();
69734
69735 var triangleClip = 'M' + x0 + ',' + (y0 + h) + 'h' + w + 'l-' + (w / 2) + ',-' + h + 'Z';
69736 _this.clipDef.select('path').attr('d', triangleClip);
69737 _this.layers.plotbg.select('path').attr('d', triangleClip);
69738
69739 var triangleClipRelative = 'M0,' + h + 'h' + w + 'l-' + (w / 2) + ',-' + h + 'Z';
69740 _this.clipDefRelative.select('path').attr('d', triangleClipRelative);
69741
69742 var plotTransform = 'translate(' + x0 + ',' + y0 + ')';
69743 _this.plotContainer.selectAll('.scatterlayer,.maplayer')
69744 .attr('transform', plotTransform);
69745
69746 _this.clipDefRelative.select('path').attr('transform', null);
69747
69748 // TODO: shift axes to accommodate linewidth*sin(30) tick mark angle
69749
69750 // TODO: there's probably an easier way to handle these translations/offsets now...
69751 var bTransform = 'translate(' + (x0 - baxis._offset) + ',' + (y0 + h) + ')';
69752
69753 _this.layers.baxis.attr('transform', bTransform);
69754 _this.layers.bgrid.attr('transform', bTransform);
69755
69756 var aTransform = 'translate(' + (x0 + w / 2) + ',' + y0 +
69757 ')rotate(30)translate(0,' + -aaxis._offset + ')';
69758 _this.layers.aaxis.attr('transform', aTransform);
69759 _this.layers.agrid.attr('transform', aTransform);
69760
69761 var cTransform = 'translate(' + (x0 + w / 2) + ',' + y0 +
69762 ')rotate(-30)translate(0,' + -caxis._offset + ')';
69763 _this.layers.caxis.attr('transform', cTransform);
69764 _this.layers.cgrid.attr('transform', cTransform);
69765
69766 _this.drawAxes(true);
69767
69768 _this.layers.aline.select('path')
69769 .attr('d', aaxis.showline ?
69770 'M' + x0 + ',' + (y0 + h) + 'l' + (w / 2) + ',-' + h : 'M0,0')
69771 .call(Color.stroke, aaxis.linecolor || '#000')
69772 .style('stroke-width', (aaxis.linewidth || 0) + 'px');
69773 _this.layers.bline.select('path')
69774 .attr('d', baxis.showline ?
69775 'M' + x0 + ',' + (y0 + h) + 'h' + w : 'M0,0')
69776 .call(Color.stroke, baxis.linecolor || '#000')
69777 .style('stroke-width', (baxis.linewidth || 0) + 'px');
69778 _this.layers.cline.select('path')
69779 .attr('d', caxis.showline ?
69780 'M' + (x0 + w / 2) + ',' + y0 + 'l' + (w / 2) + ',' + h : 'M0,0')
69781 .call(Color.stroke, caxis.linecolor || '#000')
69782 .style('stroke-width', (caxis.linewidth || 0) + 'px');
69783
69784 if(!_this.graphDiv._context.staticPlot) {
69785 _this.initInteractions();
69786 }
69787
69788 Drawing.setClipUrl(
69789 _this.layers.frontplot,
69790 _this._hasClipOnAxisFalse ? null : _this.clipId,
69791 _this.graphDiv
69792 );
69793};
69794
69795proto.drawAxes = function(doTitles) {
69796 var _this = this;
69797 var gd = _this.graphDiv;
69798 var titlesuffix = _this.id.substr(7) + 'title';
69799 var layers = _this.layers;
69800 var aaxis = _this.aaxis;
69801 var baxis = _this.baxis;
69802 var caxis = _this.caxis;
69803
69804 _this.drawAx(aaxis);
69805 _this.drawAx(baxis);
69806 _this.drawAx(caxis);
69807
69808 if(doTitles) {
69809 var apad = Math.max(aaxis.showticklabels ? aaxis.tickfont.size / 2 : 0,
69810 (caxis.showticklabels ? caxis.tickfont.size * 0.75 : 0) +
69811 (caxis.ticks === 'outside' ? caxis.ticklen * 0.87 : 0));
69812 var bpad = (baxis.showticklabels ? baxis.tickfont.size : 0) +
69813 (baxis.ticks === 'outside' ? baxis.ticklen : 0) + 3;
69814
69815 layers['a-title'] = Titles.draw(gd, 'a' + titlesuffix, {
69816 propContainer: aaxis,
69817 propName: _this.id + '.aaxis.title',
69818 placeholder: _(gd, 'Click to enter Component A title'),
69819 attributes: {
69820 x: _this.x0 + _this.w / 2,
69821 y: _this.y0 - aaxis.title.font.size / 3 - apad,
69822 'text-anchor': 'middle'
69823 }
69824 });
69825 layers['b-title'] = Titles.draw(gd, 'b' + titlesuffix, {
69826 propContainer: baxis,
69827 propName: _this.id + '.baxis.title',
69828 placeholder: _(gd, 'Click to enter Component B title'),
69829 attributes: {
69830 x: _this.x0 - bpad,
69831 y: _this.y0 + _this.h + baxis.title.font.size * 0.83 + bpad,
69832 'text-anchor': 'middle'
69833 }
69834 });
69835 layers['c-title'] = Titles.draw(gd, 'c' + titlesuffix, {
69836 propContainer: caxis,
69837 propName: _this.id + '.caxis.title',
69838 placeholder: _(gd, 'Click to enter Component C title'),
69839 attributes: {
69840 x: _this.x0 + _this.w + bpad,
69841 y: _this.y0 + _this.h + caxis.title.font.size * 0.83 + bpad,
69842 'text-anchor': 'middle'
69843 }
69844 });
69845 }
69846};
69847
69848proto.drawAx = function(ax) {
69849 var _this = this;
69850 var gd = _this.graphDiv;
69851 var axName = ax._name;
69852 var axLetter = axName.charAt(0);
69853 var axId = ax._id;
69854 var axLayer = _this.layers[axName];
69855 var counterAngle = 30;
69856
69857 var stashKey = axLetter + 'tickLayout';
69858 var newTickLayout = strTickLayout(ax);
69859 if(_this[stashKey] !== newTickLayout) {
69860 axLayer.selectAll('.' + axId + 'tick').remove();
69861 _this[stashKey] = newTickLayout;
69862 }
69863
69864 ax.setScale();
69865
69866 var vals = Axes.calcTicks(ax);
69867 var valsClipped = Axes.clipEnds(ax, vals);
69868 var transFn = Axes.makeTransFn(ax);
69869 var tickSign = Axes.getTickSigns(ax)[2];
69870
69871 var caRad = Lib.deg2rad(counterAngle);
69872 var pad = tickSign * (ax.linewidth || 1) / 2;
69873 var len = tickSign * ax.ticklen;
69874 var w = _this.w;
69875 var h = _this.h;
69876
69877 var tickPath = axLetter === 'b' ?
69878 'M0,' + pad + 'l' + (Math.sin(caRad) * len) + ',' + (Math.cos(caRad) * len) :
69879 'M' + pad + ',0l' + (Math.cos(caRad) * len) + ',' + (-Math.sin(caRad) * len);
69880
69881 var gridPath = {
69882 a: 'M0,0l' + h + ',-' + (w / 2),
69883 b: 'M0,0l-' + (w / 2) + ',-' + h,
69884 c: 'M0,0l-' + h + ',' + (w / 2)
69885 }[axLetter];
69886
69887 Axes.drawTicks(gd, ax, {
69888 vals: ax.ticks === 'inside' ? valsClipped : vals,
69889 layer: axLayer,
69890 path: tickPath,
69891 transFn: transFn,
69892 crisp: false
69893 });
69894
69895 Axes.drawGrid(gd, ax, {
69896 vals: valsClipped,
69897 layer: _this.layers[axLetter + 'grid'],
69898 path: gridPath,
69899 transFn: transFn,
69900 crisp: false
69901 });
69902
69903 Axes.drawLabels(gd, ax, {
69904 vals: vals,
69905 layer: axLayer,
69906 transFn: transFn,
69907 labelFns: Axes.makeLabelFns(ax, 0, counterAngle)
69908 });
69909};
69910
69911function strTickLayout(axLayout) {
69912 return axLayout.ticks + String(axLayout.ticklen) + String(axLayout.showticklabels);
69913}
69914
69915// hard coded paths for zoom corners
69916// uses the same sizing as cartesian, length is MINZOOM/2, width is 3px
69917var CLEN = constants.MINZOOM / 2 + 0.87;
69918var BLPATH = 'm-0.87,.5h' + CLEN + 'v3h-' + (CLEN + 5.2) +
69919 'l' + (CLEN / 2 + 2.6) + ',-' + (CLEN * 0.87 + 4.5) +
69920 'l2.6,1.5l-' + (CLEN / 2) + ',' + (CLEN * 0.87) + 'Z';
69921var BRPATH = 'm0.87,.5h-' + CLEN + 'v3h' + (CLEN + 5.2) +
69922 'l-' + (CLEN / 2 + 2.6) + ',-' + (CLEN * 0.87 + 4.5) +
69923 'l-2.6,1.5l' + (CLEN / 2) + ',' + (CLEN * 0.87) + 'Z';
69924var TOPPATH = 'm0,1l' + (CLEN / 2) + ',' + (CLEN * 0.87) +
69925 'l2.6,-1.5l-' + (CLEN / 2 + 2.6) + ',-' + (CLEN * 0.87 + 4.5) +
69926 'l-' + (CLEN / 2 + 2.6) + ',' + (CLEN * 0.87 + 4.5) +
69927 'l2.6,1.5l' + (CLEN / 2) + ',-' + (CLEN * 0.87) + 'Z';
69928var STARTMARKER = 'm0.5,0.5h5v-2h-5v-5h-2v5h-5v2h5v5h2Z';
69929
69930// I guess this could be shared with cartesian... but for now it's separate.
69931var SHOWZOOMOUTTIP = true;
69932
69933proto.clearSelect = function() {
69934 clearSelectionsCache(this.dragOptions);
69935 clearSelect(this.dragOptions.gd);
69936};
69937
69938proto.initInteractions = function() {
69939 var _this = this;
69940 var dragger = _this.layers.plotbg.select('path').node();
69941 var gd = _this.graphDiv;
69942 var zoomLayer = gd._fullLayout._zoomlayer;
69943
69944 // use plotbg for the main interactions
69945 this.dragOptions = {
69946 element: dragger,
69947 gd: gd,
69948 plotinfo: {
69949 id: _this.id,
69950 domain: gd._fullLayout[_this.id].domain,
69951 xaxis: _this.xaxis,
69952 yaxis: _this.yaxis
69953 },
69954 subplot: _this.id,
69955 prepFn: function(e, startX, startY) {
69956 // these aren't available yet when initInteractions
69957 // is called
69958 _this.dragOptions.xaxes = [_this.xaxis];
69959 _this.dragOptions.yaxes = [_this.yaxis];
69960
69961 var dragModeNow = _this.dragOptions.dragmode = gd._fullLayout.dragmode;
69962
69963 if(freeMode(dragModeNow)) _this.dragOptions.minDrag = 1;
69964 else _this.dragOptions.minDrag = undefined;
69965
69966 if(dragModeNow === 'zoom') {
69967 _this.dragOptions.moveFn = zoomMove;
69968 _this.dragOptions.clickFn = clickZoomPan;
69969 _this.dragOptions.doneFn = zoomDone;
69970 zoomPrep(e, startX, startY);
69971 } else if(dragModeNow === 'pan') {
69972 _this.dragOptions.moveFn = plotDrag;
69973 _this.dragOptions.clickFn = clickZoomPan;
69974 _this.dragOptions.doneFn = dragDone;
69975 panPrep();
69976 _this.clearSelect(gd);
69977 } else if(rectMode(dragModeNow) || freeMode(dragModeNow)) {
69978 prepSelect(e, startX, startY, _this.dragOptions, dragModeNow);
69979 }
69980 }
69981 };
69982
69983 var x0, y0, mins0, span0, mins, lum, path0, dimmed, zb, corners;
69984
69985 function makeUpdate(_mins) {
69986 var attrs = {};
69987 attrs[_this.id + '.aaxis.min'] = _mins.a;
69988 attrs[_this.id + '.baxis.min'] = _mins.b;
69989 attrs[_this.id + '.caxis.min'] = _mins.c;
69990 return attrs;
69991 }
69992
69993 function clickZoomPan(numClicks, evt) {
69994 var clickMode = gd._fullLayout.clickmode;
69995
69996 removeZoombox(gd);
69997
69998 if(numClicks === 2) {
69999 gd.emit('plotly_doubleclick', null);
70000 Registry.call('_guiRelayout', gd, makeUpdate({a: 0, b: 0, c: 0}));
70001 }
70002
70003 if(clickMode.indexOf('select') > -1 && numClicks === 1) {
70004 selectOnClick(evt, gd, [_this.xaxis], [_this.yaxis], _this.id, _this.dragOptions);
70005 }
70006
70007 if(clickMode.indexOf('event') > -1) {
70008 Fx.click(gd, evt, _this.id);
70009 }
70010 }
70011
70012 function zoomPrep(e, startX, startY) {
70013 var dragBBox = dragger.getBoundingClientRect();
70014 x0 = startX - dragBBox.left;
70015 y0 = startY - dragBBox.top;
70016 mins0 = {
70017 a: _this.aaxis.range[0],
70018 b: _this.baxis.range[1],
70019 c: _this.caxis.range[1]
70020 };
70021 mins = mins0;
70022 span0 = _this.aaxis.range[1] - mins0.a;
70023 lum = tinycolor(_this.graphDiv._fullLayout[_this.id].bgcolor).getLuminance();
70024 path0 = 'M0,' + _this.h + 'L' + (_this.w / 2) + ', 0L' + _this.w + ',' + _this.h + 'Z';
70025 dimmed = false;
70026
70027 zb = zoomLayer.append('path')
70028 .attr('class', 'zoombox')
70029 .attr('transform', 'translate(' + _this.x0 + ', ' + _this.y0 + ')')
70030 .style({
70031 'fill': lum > 0.2 ? 'rgba(0,0,0,0)' : 'rgba(255,255,255,0)',
70032 'stroke-width': 0
70033 })
70034 .attr('d', path0);
70035
70036 corners = zoomLayer.append('path')
70037 .attr('class', 'zoombox-corners')
70038 .attr('transform', 'translate(' + _this.x0 + ', ' + _this.y0 + ')')
70039 .style({
70040 fill: Color.background,
70041 stroke: Color.defaultLine,
70042 'stroke-width': 1,
70043 opacity: 0
70044 })
70045 .attr('d', 'M0,0Z');
70046
70047 _this.clearSelect(gd);
70048 }
70049
70050 function getAFrac(x, y) { return 1 - (y / _this.h); }
70051 function getBFrac(x, y) { return 1 - ((x + (_this.h - y) / Math.sqrt(3)) / _this.w); }
70052 function getCFrac(x, y) { return ((x - (_this.h - y) / Math.sqrt(3)) / _this.w); }
70053
70054 function zoomMove(dx0, dy0) {
70055 var x1 = x0 + dx0;
70056 var y1 = y0 + dy0;
70057 var afrac = Math.max(0, Math.min(1, getAFrac(x0, y0), getAFrac(x1, y1)));
70058 var bfrac = Math.max(0, Math.min(1, getBFrac(x0, y0), getBFrac(x1, y1)));
70059 var cfrac = Math.max(0, Math.min(1, getCFrac(x0, y0), getCFrac(x1, y1)));
70060 var xLeft = ((afrac / 2) + cfrac) * _this.w;
70061 var xRight = (1 - (afrac / 2) - bfrac) * _this.w;
70062 var xCenter = (xLeft + xRight) / 2;
70063 var xSpan = xRight - xLeft;
70064 var yBottom = (1 - afrac) * _this.h;
70065 var yTop = yBottom - xSpan / whRatio;
70066
70067 if(xSpan < constants.MINZOOM) {
70068 mins = mins0;
70069 zb.attr('d', path0);
70070 corners.attr('d', 'M0,0Z');
70071 } else {
70072 mins = {
70073 a: mins0.a + afrac * span0,
70074 b: mins0.b + bfrac * span0,
70075 c: mins0.c + cfrac * span0
70076 };
70077 zb.attr('d', path0 + 'M' + xLeft + ',' + yBottom +
70078 'H' + xRight + 'L' + xCenter + ',' + yTop +
70079 'L' + xLeft + ',' + yBottom + 'Z');
70080 corners.attr('d', 'M' + x0 + ',' + y0 + STARTMARKER +
70081 'M' + xLeft + ',' + yBottom + BLPATH +
70082 'M' + xRight + ',' + yBottom + BRPATH +
70083 'M' + xCenter + ',' + yTop + TOPPATH);
70084 }
70085
70086 if(!dimmed) {
70087 zb.transition()
70088 .style('fill', lum > 0.2 ? 'rgba(0,0,0,0.4)' :
70089 'rgba(255,255,255,0.3)')
70090 .duration(200);
70091 corners.transition()
70092 .style('opacity', 1)
70093 .duration(200);
70094 dimmed = true;
70095 }
70096
70097 gd.emit('plotly_relayouting', makeUpdate(mins));
70098 }
70099
70100 function zoomDone() {
70101 removeZoombox(gd);
70102
70103 if(mins === mins0) return;
70104
70105 Registry.call('_guiRelayout', gd, makeUpdate(mins));
70106
70107 if(SHOWZOOMOUTTIP && gd.data && gd._context.showTips) {
70108 Lib.notifier(_(gd, 'Double-click to zoom back out'), 'long');
70109 SHOWZOOMOUTTIP = false;
70110 }
70111 }
70112
70113 function panPrep() {
70114 mins0 = {
70115 a: _this.aaxis.range[0],
70116 b: _this.baxis.range[1],
70117 c: _this.caxis.range[1]
70118 };
70119 mins = mins0;
70120 }
70121
70122 function plotDrag(dx, dy) {
70123 var dxScaled = dx / _this.xaxis._m;
70124 var dyScaled = dy / _this.yaxis._m;
70125 mins = {
70126 a: mins0.a - dyScaled,
70127 b: mins0.b + (dxScaled + dyScaled) / 2,
70128 c: mins0.c - (dxScaled - dyScaled) / 2
70129 };
70130 var minsorted = [mins.a, mins.b, mins.c].sort();
70131 var minindices = {
70132 a: minsorted.indexOf(mins.a),
70133 b: minsorted.indexOf(mins.b),
70134 c: minsorted.indexOf(mins.c)
70135 };
70136 if(minsorted[0] < 0) {
70137 if(minsorted[1] + minsorted[0] / 2 < 0) {
70138 minsorted[2] += minsorted[0] + minsorted[1];
70139 minsorted[0] = minsorted[1] = 0;
70140 } else {
70141 minsorted[2] += minsorted[0] / 2;
70142 minsorted[1] += minsorted[0] / 2;
70143 minsorted[0] = 0;
70144 }
70145 mins = {
70146 a: minsorted[minindices.a],
70147 b: minsorted[minindices.b],
70148 c: minsorted[minindices.c]
70149 };
70150 dy = (mins0.a - mins.a) * _this.yaxis._m;
70151 dx = (mins0.c - mins.c - mins0.b + mins.b) * _this.xaxis._m;
70152 }
70153
70154 // move the data (translate, don't redraw)
70155 var plotTransform = 'translate(' + (_this.x0 + dx) + ',' + (_this.y0 + dy) + ')';
70156 _this.plotContainer.selectAll('.scatterlayer,.maplayer')
70157 .attr('transform', plotTransform);
70158
70159 var plotTransform2 = 'translate(' + -dx + ',' + -dy + ')';
70160 _this.clipDefRelative.select('path').attr('transform', plotTransform2);
70161
70162 // move the ticks
70163 _this.aaxis.range = [mins.a, _this.sum - mins.b - mins.c];
70164 _this.baxis.range = [_this.sum - mins.a - mins.c, mins.b];
70165 _this.caxis.range = [_this.sum - mins.a - mins.b, mins.c];
70166
70167 _this.drawAxes(false);
70168
70169 if(_this._hasClipOnAxisFalse) {
70170 _this.plotContainer
70171 .select('.scatterlayer').selectAll('.trace')
70172 .call(Drawing.hideOutsideRangePoints, _this);
70173 }
70174
70175 gd.emit('plotly_relayouting', makeUpdate(mins));
70176 }
70177
70178 function dragDone() {
70179 Registry.call('_guiRelayout', gd, makeUpdate(mins));
70180 }
70181
70182 // finally, set up hover and click
70183 // these event handlers must already be set before dragElement.init
70184 // so it can stash them and override them.
70185 dragger.onmousemove = function(evt) {
70186 Fx.hover(gd, evt, _this.id);
70187 gd._fullLayout._lasthover = dragger;
70188 gd._fullLayout._hoversubplot = _this.id;
70189 };
70190
70191 dragger.onmouseout = function(evt) {
70192 if(gd._dragging) return;
70193
70194 dragElement.unhover(gd, evt);
70195 };
70196
70197 dragElement.init(this.dragOptions);
70198};
70199
70200function removeZoombox(gd) {
70201 d3.select(gd)
70202 .selectAll('.zoombox,.js-zoombox-backdrop,.js-zoombox-menu,.zoombox-corners')
70203 .remove();
70204}
70205
70206},{"../../components/color":52,"../../components/dragelement":71,"../../components/dragelement/helpers":70,"../../components/drawing":74,"../../components/fx":92,"../../components/titles":147,"../../lib":178,"../../lib/extend":173,"../../registry":269,"../cartesian/axes":222,"../cartesian/constants":228,"../cartesian/select":241,"../cartesian/set_convert":242,"../plots":256,"d3":16,"tinycolor2":35}],269:[function(_dereq_,module,exports){
70207/**
70208* Copyright 2012-2020, Plotly, Inc.
70209* All rights reserved.
70210*
70211* This source code is licensed under the MIT license found in the
70212* LICENSE file in the root directory of this source tree.
70213*/
70214
70215'use strict';
70216
70217var Loggers = _dereq_('./lib/loggers');
70218var noop = _dereq_('./lib/noop');
70219var pushUnique = _dereq_('./lib/push_unique');
70220var isPlainObject = _dereq_('./lib/is_plain_object');
70221var addStyleRule = _dereq_('./lib/dom').addStyleRule;
70222var ExtendModule = _dereq_('./lib/extend');
70223
70224var basePlotAttributes = _dereq_('./plots/attributes');
70225var baseLayoutAttributes = _dereq_('./plots/layout_attributes');
70226
70227var extendFlat = ExtendModule.extendFlat;
70228var extendDeepAll = ExtendModule.extendDeepAll;
70229
70230exports.modules = {};
70231exports.allCategories = {};
70232exports.allTypes = [];
70233exports.subplotsRegistry = {};
70234exports.transformsRegistry = {};
70235exports.componentsRegistry = {};
70236exports.layoutArrayContainers = [];
70237exports.layoutArrayRegexes = [];
70238exports.traceLayoutAttributes = {};
70239exports.localeRegistry = {};
70240exports.apiMethodRegistry = {};
70241exports.collectableSubplotTypes = null;
70242
70243/**
70244 * Top-level register routine, exported as Plotly.register
70245 *
70246 * @param {object array or array of objects} _modules :
70247 * module object or list of module object to register.
70248 *
70249 * A valid `moduleType: 'trace'` module has fields:
70250 * - name {string} : the trace type
70251 * - categories {array} : categories associated with this trace type,
70252 * tested with Register.traceIs()
70253 * - meta {object} : meta info (mostly for plot-schema)
70254 *
70255 * A valid `moduleType: 'locale'` module has fields:
70256 * - name {string} : the locale name. Should be a 2-digit language string ('en', 'de')
70257 * optionally with a country/region code ('en-GB', 'de-CH'). If a country
70258 * code is used but the base language locale has not yet been supplied,
70259 * we will use this locale for the base as well.
70260 * - dictionary {object} : the dictionary mapping input strings to localized strings
70261 * generally the keys should be the literal input strings, but
70262 * if default translations are provided you can use any string as a key.
70263 * - format {object} : a `d3.locale` format specifier for this locale
70264 * any omitted keys we'll fall back on en-US.
70265 *
70266 * A valid `moduleType: 'transform'` module has fields:
70267 * - name {string} : transform name
70268 * - transform {function} : default-level transform function
70269 * - calcTransform {function} : calc-level transform function
70270 * - attributes {object} : transform attributes declarations
70271 * - supplyDefaults {function} : attributes default-supply function
70272 *
70273 * A valid `moduleType: 'component'` module has fields:
70274 * - name {string} : the component name, used it with Register.getComponentMethod()
70275 * to employ component method.
70276 *
70277 * A valid `moduleType: 'apiMethod'` module has fields:
70278 * - name {string} : the api method name.
70279 * - fn {function} : the api method called with Register.call();
70280 *
70281 */
70282exports.register = function register(_modules) {
70283 exports.collectableSubplotTypes = null;
70284
70285 if(!_modules) {
70286 throw new Error('No argument passed to Plotly.register.');
70287 } else if(_modules && !Array.isArray(_modules)) {
70288 _modules = [_modules];
70289 }
70290
70291 for(var i = 0; i < _modules.length; i++) {
70292 var newModule = _modules[i];
70293
70294 if(!newModule) {
70295 throw new Error('Invalid module was attempted to be registered!');
70296 }
70297
70298 switch(newModule.moduleType) {
70299 case 'trace':
70300 registerTraceModule(newModule);
70301 break;
70302 case 'transform':
70303 registerTransformModule(newModule);
70304 break;
70305 case 'component':
70306 registerComponentModule(newModule);
70307 break;
70308 case 'locale':
70309 registerLocale(newModule);
70310 break;
70311 case 'apiMethod':
70312 var name = newModule.name;
70313 exports.apiMethodRegistry[name] = newModule.fn;
70314 break;
70315 default:
70316 throw new Error('Invalid module was attempted to be registered!');
70317 }
70318 }
70319};
70320
70321/**
70322 * Get registered module using trace object or trace type
70323 *
70324 * @param {object||string} trace
70325 * trace object with prop 'type' or trace type as a string
70326 * @return {object}
70327 * module object corresponding to trace type
70328 */
70329exports.getModule = function(trace) {
70330 var _module = exports.modules[getTraceType(trace)];
70331 if(!_module) return false;
70332 return _module._module;
70333};
70334
70335/**
70336 * Determine if this trace type is in a given category
70337 *
70338 * @param {object||string} traceType
70339 * a trace (object) or trace type (string)
70340 * @param {string} category
70341 * category in question
70342 * @return {boolean}
70343 */
70344exports.traceIs = function(traceType, category) {
70345 traceType = getTraceType(traceType);
70346
70347 // old Chart Studio Cloud workspace hack, nothing to see here
70348 if(traceType === 'various') return false;
70349
70350 var _module = exports.modules[traceType];
70351
70352 if(!_module) {
70353 if(traceType && traceType !== 'area') {
70354 Loggers.log('Unrecognized trace type ' + traceType + '.');
70355 }
70356
70357 _module = exports.modules[basePlotAttributes.type.dflt];
70358 }
70359
70360 return !!_module.categories[category];
70361};
70362
70363/**
70364 * Determine if this trace has a transform of the given type and return
70365 * array of matching indices.
70366 *
70367 * @param {object} data
70368 * a trace object (member of data or fullData)
70369 * @param {string} type
70370 * type of trace to test
70371 * @return {array}
70372 * array of matching indices. If none found, returns []
70373 */
70374exports.getTransformIndices = function(data, type) {
70375 var indices = [];
70376 var transforms = data.transforms || [];
70377 for(var i = 0; i < transforms.length; i++) {
70378 if(transforms[i].type === type) {
70379 indices.push(i);
70380 }
70381 }
70382 return indices;
70383};
70384
70385/**
70386 * Determine if this trace has a transform of the given type
70387 *
70388 * @param {object} data
70389 * a trace object (member of data or fullData)
70390 * @param {string} type
70391 * type of trace to test
70392 * @return {boolean}
70393 */
70394exports.hasTransform = function(data, type) {
70395 var transforms = data.transforms || [];
70396 for(var i = 0; i < transforms.length; i++) {
70397 if(transforms[i].type === type) {
70398 return true;
70399 }
70400 }
70401 return false;
70402};
70403
70404/**
70405 * Retrieve component module method. Falls back on noop if either the
70406 * module or the method is missing, so the result can always be safely called
70407 *
70408 * @param {string} name
70409 * name of component (as declared in component module)
70410 * @param {string} method
70411 * name of component module method
70412 * @return {function}
70413 */
70414exports.getComponentMethod = function(name, method) {
70415 var _module = exports.componentsRegistry[name];
70416
70417 if(!_module) return noop;
70418 return _module[method] || noop;
70419};
70420
70421/**
70422 * Call registered api method.
70423 *
70424 * @param {string} name : api method name
70425 * @param {...array} args : arguments passed to api method
70426 * @return {any} : returns api method output
70427 */
70428exports.call = function() {
70429 var name = arguments[0];
70430 var args = [].slice.call(arguments, 1);
70431 return exports.apiMethodRegistry[name].apply(null, args);
70432};
70433
70434function registerTraceModule(_module) {
70435 var thisType = _module.name;
70436 var categoriesIn = _module.categories;
70437 var meta = _module.meta;
70438
70439 if(exports.modules[thisType]) {
70440 Loggers.log('Type ' + thisType + ' already registered');
70441 return;
70442 }
70443
70444 if(!exports.subplotsRegistry[_module.basePlotModule.name]) {
70445 registerSubplot(_module.basePlotModule);
70446 }
70447
70448 var categoryObj = {};
70449 for(var i = 0; i < categoriesIn.length; i++) {
70450 categoryObj[categoriesIn[i]] = true;
70451 exports.allCategories[categoriesIn[i]] = true;
70452 }
70453
70454 exports.modules[thisType] = {
70455 _module: _module,
70456 categories: categoryObj
70457 };
70458
70459 if(meta && Object.keys(meta).length) {
70460 exports.modules[thisType].meta = meta;
70461 }
70462
70463 exports.allTypes.push(thisType);
70464
70465 for(var componentName in exports.componentsRegistry) {
70466 mergeComponentAttrsToTrace(componentName, thisType);
70467 }
70468
70469 /*
70470 * Collect all trace layout attributes in one place for easier lookup later
70471 * but don't merge them into the base schema as it would confuse the docs
70472 * (at least after https://github.com/plotly/documentation/issues/202 gets done!)
70473 */
70474 if(_module.layoutAttributes) {
70475 extendFlat(exports.traceLayoutAttributes, _module.layoutAttributes);
70476 }
70477
70478 var basePlotModule = _module.basePlotModule;
70479 var bpmName = basePlotModule.name;
70480
70481 // add mapbox-gl CSS here to avoid console warning on instantiation
70482 if(bpmName === 'mapbox') {
70483 var styleRules = basePlotModule.constants.styleRules;
70484 for(var k in styleRules) {
70485 addStyleRule('.js-plotly-plot .plotly .mapboxgl-' + k, styleRules[k]);
70486 }
70487 }
70488
70489 // if `plotly-geo-assets.js` is not included,
70490 // add `PlotlyGeoAssets` global to stash references to all fetched
70491 // topojson / geojson data
70492 if((bpmName === 'geo' || bpmName === 'mapbox') &&
70493 (typeof window !== undefined && window.PlotlyGeoAssets === undefined)
70494 ) {
70495 window.PlotlyGeoAssets = {topojson: {}};
70496 }
70497}
70498
70499function registerSubplot(_module) {
70500 var plotType = _module.name;
70501
70502 if(exports.subplotsRegistry[plotType]) {
70503 Loggers.log('Plot type ' + plotType + ' already registered.');
70504 return;
70505 }
70506
70507 // relayout array handling will look for component module methods with this
70508 // name and won't find them because this is a subplot module... but that
70509 // should be fine, it will just fall back on redrawing the plot.
70510 findArrayRegexps(_module);
70511
70512 // not sure what's best for the 'cartesian' type at this point
70513 exports.subplotsRegistry[plotType] = _module;
70514
70515 for(var componentName in exports.componentsRegistry) {
70516 mergeComponentAttrsToSubplot(componentName, _module.name);
70517 }
70518}
70519
70520function registerComponentModule(_module) {
70521 if(typeof _module.name !== 'string') {
70522 throw new Error('Component module *name* must be a string.');
70523 }
70524
70525 var name = _module.name;
70526 exports.componentsRegistry[name] = _module;
70527
70528 if(_module.layoutAttributes) {
70529 if(_module.layoutAttributes._isLinkedToArray) {
70530 pushUnique(exports.layoutArrayContainers, name);
70531 }
70532 findArrayRegexps(_module);
70533 }
70534
70535 for(var traceType in exports.modules) {
70536 mergeComponentAttrsToTrace(name, traceType);
70537 }
70538
70539 for(var subplotName in exports.subplotsRegistry) {
70540 mergeComponentAttrsToSubplot(name, subplotName);
70541 }
70542
70543 for(var transformType in exports.transformsRegistry) {
70544 mergeComponentAttrsToTransform(name, transformType);
70545 }
70546
70547 if(_module.schema && _module.schema.layout) {
70548 extendDeepAll(baseLayoutAttributes, _module.schema.layout);
70549 }
70550}
70551
70552function registerTransformModule(_module) {
70553 if(typeof _module.name !== 'string') {
70554 throw new Error('Transform module *name* must be a string.');
70555 }
70556
70557 var prefix = 'Transform module ' + _module.name;
70558 var hasTransform = typeof _module.transform === 'function';
70559 var hasCalcTransform = typeof _module.calcTransform === 'function';
70560
70561 if(!hasTransform && !hasCalcTransform) {
70562 throw new Error(prefix + ' is missing a *transform* or *calcTransform* method.');
70563 }
70564 if(hasTransform && hasCalcTransform) {
70565 Loggers.log([
70566 prefix + ' has both a *transform* and *calcTransform* methods.',
70567 'Please note that all *transform* methods are executed',
70568 'before all *calcTransform* methods.'
70569 ].join(' '));
70570 }
70571 if(!isPlainObject(_module.attributes)) {
70572 Loggers.log(prefix + ' registered without an *attributes* object.');
70573 }
70574 if(typeof _module.supplyDefaults !== 'function') {
70575 Loggers.log(prefix + ' registered without a *supplyDefaults* method.');
70576 }
70577
70578 exports.transformsRegistry[_module.name] = _module;
70579
70580 for(var componentName in exports.componentsRegistry) {
70581 mergeComponentAttrsToTransform(componentName, _module.name);
70582 }
70583}
70584
70585function registerLocale(_module) {
70586 var locale = _module.name;
70587 var baseLocale = locale.split('-')[0];
70588
70589 var newDict = _module.dictionary;
70590 var newFormat = _module.format;
70591 var hasDict = newDict && Object.keys(newDict).length;
70592 var hasFormat = newFormat && Object.keys(newFormat).length;
70593
70594 var locales = exports.localeRegistry;
70595
70596 var localeObj = locales[locale];
70597 if(!localeObj) locales[locale] = localeObj = {};
70598
70599 // Should we use this dict for the base locale?
70600 // In case we're overwriting a previous dict for this locale, check
70601 // whether the base matches the full locale dict now. If we're not
70602 // overwriting, locales[locale] is undefined so this just checks if
70603 // baseLocale already had a dict or not.
70604 // Same logic for dateFormats
70605 if(baseLocale !== locale) {
70606 var baseLocaleObj = locales[baseLocale];
70607 if(!baseLocaleObj) locales[baseLocale] = baseLocaleObj = {};
70608
70609 if(hasDict && baseLocaleObj.dictionary === localeObj.dictionary) {
70610 baseLocaleObj.dictionary = newDict;
70611 }
70612 if(hasFormat && baseLocaleObj.format === localeObj.format) {
70613 baseLocaleObj.format = newFormat;
70614 }
70615 }
70616
70617 if(hasDict) localeObj.dictionary = newDict;
70618 if(hasFormat) localeObj.format = newFormat;
70619}
70620
70621function findArrayRegexps(_module) {
70622 if(_module.layoutAttributes) {
70623 var arrayAttrRegexps = _module.layoutAttributes._arrayAttrRegexps;
70624 if(arrayAttrRegexps) {
70625 for(var i = 0; i < arrayAttrRegexps.length; i++) {
70626 pushUnique(exports.layoutArrayRegexes, arrayAttrRegexps[i]);
70627 }
70628 }
70629 }
70630}
70631
70632function mergeComponentAttrsToTrace(componentName, traceType) {
70633 var componentSchema = exports.componentsRegistry[componentName].schema;
70634 if(!componentSchema || !componentSchema.traces) return;
70635
70636 var traceAttrs = componentSchema.traces[traceType];
70637 if(traceAttrs) {
70638 extendDeepAll(exports.modules[traceType]._module.attributes, traceAttrs);
70639 }
70640}
70641
70642function mergeComponentAttrsToTransform(componentName, transformType) {
70643 var componentSchema = exports.componentsRegistry[componentName].schema;
70644 if(!componentSchema || !componentSchema.transforms) return;
70645
70646 var transformAttrs = componentSchema.transforms[transformType];
70647 if(transformAttrs) {
70648 extendDeepAll(exports.transformsRegistry[transformType].attributes, transformAttrs);
70649 }
70650}
70651
70652function mergeComponentAttrsToSubplot(componentName, subplotName) {
70653 var componentSchema = exports.componentsRegistry[componentName].schema;
70654 if(!componentSchema || !componentSchema.subplots) return;
70655
70656 var subplotModule = exports.subplotsRegistry[subplotName];
70657 var subplotAttrs = subplotModule.layoutAttributes;
70658 var subplotAttr = subplotModule.attr === 'subplot' ? subplotModule.name : subplotModule.attr;
70659 if(Array.isArray(subplotAttr)) subplotAttr = subplotAttr[0];
70660
70661 var componentLayoutAttrs = componentSchema.subplots[subplotAttr];
70662 if(subplotAttrs && componentLayoutAttrs) {
70663 extendDeepAll(subplotAttrs, componentLayoutAttrs);
70664 }
70665}
70666
70667function getTraceType(traceType) {
70668 if(typeof traceType === 'object') traceType = traceType.type;
70669 return traceType;
70670}
70671
70672},{"./lib/dom":171,"./lib/extend":173,"./lib/is_plain_object":179,"./lib/loggers":182,"./lib/noop":187,"./lib/push_unique":191,"./plots/attributes":219,"./plots/layout_attributes":254}],270:[function(_dereq_,module,exports){
70673/**
70674* Copyright 2012-2020, Plotly, Inc.
70675* All rights reserved.
70676*
70677* This source code is licensed under the MIT license found in the
70678* LICENSE file in the root directory of this source tree.
70679*/
70680
70681'use strict';
70682
70683var Registry = _dereq_('../registry');
70684var Lib = _dereq_('../lib');
70685
70686var extendFlat = Lib.extendFlat;
70687var extendDeep = Lib.extendDeep;
70688
70689// Put default plotTile layouts here
70690function cloneLayoutOverride(tileClass) {
70691 var override;
70692
70693 switch(tileClass) {
70694 case 'themes__thumb':
70695 override = {
70696 autosize: true,
70697 width: 150,
70698 height: 150,
70699 title: {text: ''},
70700 showlegend: false,
70701 margin: {l: 5, r: 5, t: 5, b: 5, pad: 0},
70702 annotations: []
70703 };
70704 break;
70705
70706 case 'thumbnail':
70707 override = {
70708 title: {text: ''},
70709 hidesources: true,
70710 showlegend: false,
70711 borderwidth: 0,
70712 bordercolor: '',
70713 margin: {l: 1, r: 1, t: 1, b: 1, pad: 0},
70714 annotations: []
70715 };
70716 break;
70717
70718 default:
70719 override = {};
70720 }
70721
70722
70723 return override;
70724}
70725
70726function keyIsAxis(keyName) {
70727 var types = ['xaxis', 'yaxis', 'zaxis'];
70728 return (types.indexOf(keyName.slice(0, 5)) > -1);
70729}
70730
70731
70732module.exports = function clonePlot(graphObj, options) {
70733 // Polar plot compatibility
70734 if(graphObj.framework && graphObj.framework.isPolar) {
70735 graphObj = graphObj.framework.getConfig();
70736 }
70737
70738 var i;
70739 var oldData = graphObj.data;
70740 var oldLayout = graphObj.layout;
70741 var newData = extendDeep([], oldData);
70742 var newLayout = extendDeep({}, oldLayout, cloneLayoutOverride(options.tileClass));
70743 var context = graphObj._context || {};
70744
70745 if(options.width) newLayout.width = options.width;
70746 if(options.height) newLayout.height = options.height;
70747
70748 if(options.tileClass === 'thumbnail' || options.tileClass === 'themes__thumb') {
70749 // kill annotations
70750 newLayout.annotations = [];
70751 var keys = Object.keys(newLayout);
70752
70753 for(i = 0; i < keys.length; i++) {
70754 if(keyIsAxis(keys[i])) {
70755 newLayout[keys[i]].title = {text: ''};
70756 }
70757 }
70758
70759 // kill colorbar and pie labels
70760 for(i = 0; i < newData.length; i++) {
70761 var trace = newData[i];
70762 trace.showscale = false;
70763 if(trace.marker) trace.marker.showscale = false;
70764 if(Registry.traceIs(trace, 'pie-like')) trace.textposition = 'none';
70765 }
70766 }
70767
70768 if(Array.isArray(options.annotations)) {
70769 for(i = 0; i < options.annotations.length; i++) {
70770 newLayout.annotations.push(options.annotations[i]);
70771 }
70772 }
70773
70774 // TODO: does this scene modification really belong here?
70775 // If we still need it, can it move into the gl3d module?
70776 var sceneIds = Object.keys(newLayout).filter(function(key) {
70777 return key.match(/^scene\d*$/);
70778 });
70779 if(sceneIds.length) {
70780 var axesImageOverride = {};
70781 if(options.tileClass === 'thumbnail') {
70782 axesImageOverride = {
70783 title: {text: ''},
70784 showaxeslabels: false,
70785 showticklabels: false,
70786 linetickenable: false
70787 };
70788 }
70789 for(i = 0; i < sceneIds.length; i++) {
70790 var scene = newLayout[sceneIds[i]];
70791
70792 if(!scene.xaxis) {
70793 scene.xaxis = {};
70794 }
70795
70796 if(!scene.yaxis) {
70797 scene.yaxis = {};
70798 }
70799
70800 if(!scene.zaxis) {
70801 scene.zaxis = {};
70802 }
70803
70804 extendFlat(scene.xaxis, axesImageOverride);
70805 extendFlat(scene.yaxis, axesImageOverride);
70806 extendFlat(scene.zaxis, axesImageOverride);
70807
70808 // TODO what does this do?
70809 scene._scene = null;
70810 }
70811 }
70812
70813 var gd = document.createElement('div');
70814 if(options.tileClass) gd.className = options.tileClass;
70815
70816 var plotTile = {
70817 gd: gd,
70818 td: gd, // for external (image server) compatibility
70819 layout: newLayout,
70820 data: newData,
70821 config: {
70822 staticPlot: (options.staticPlot === undefined) ?
70823 true :
70824 options.staticPlot,
70825 plotGlPixelRatio: (options.plotGlPixelRatio === undefined) ?
70826 2 :
70827 options.plotGlPixelRatio,
70828 displaylogo: options.displaylogo || false,
70829 showLink: options.showLink || false,
70830 showTips: options.showTips || false,
70831 mapboxAccessToken: context.mapboxAccessToken
70832 }
70833 };
70834
70835 if(options.setBackground !== 'transparent') {
70836 plotTile.config.setBackground = options.setBackground || 'opaque';
70837 }
70838
70839 // attaching the default Layout the gd, so you can grab it later
70840 plotTile.gd.defaultLayout = cloneLayoutOverride(options.tileClass);
70841
70842 return plotTile;
70843};
70844
70845},{"../lib":178,"../registry":269}],271:[function(_dereq_,module,exports){
70846/**
70847* Copyright 2012-2020, Plotly, Inc.
70848* All rights reserved.
70849*
70850* This source code is licensed under the MIT license found in the
70851* LICENSE file in the root directory of this source tree.
70852*/
70853
70854'use strict';
70855
70856var Lib = _dereq_('../lib');
70857
70858var toImage = _dereq_('../plot_api/to_image');
70859
70860var fileSaver = _dereq_('./filesaver');
70861var helpers = _dereq_('./helpers');
70862
70863/**
70864 * Plotly.downloadImage
70865 *
70866 * @param {object | string | HTML div} gd
70867 * can either be a data/layout/config object
70868 * or an existing graph <div>
70869 * or an id to an existing graph <div>
70870 * @param {object} opts (see Plotly.toImage in ../plot_api/to_image)
70871 * @return {promise}
70872 */
70873function downloadImage(gd, opts) {
70874 var _gd;
70875 if(!Lib.isPlainObject(gd)) _gd = Lib.getGraphDiv(gd);
70876
70877 opts = opts || {};
70878 opts.format = opts.format || 'png';
70879 opts.imageDataOnly = true;
70880
70881 return new Promise(function(resolve, reject) {
70882 if(_gd && _gd._snapshotInProgress) {
70883 reject(new Error('Snapshotting already in progress.'));
70884 }
70885
70886 // see comments within svgtoimg for additional
70887 // discussion of problems with IE
70888 // can now draw to canvas, but CORS tainted canvas
70889 // does not allow toDataURL
70890 // svg format will work though
70891 if(Lib.isIE() && opts.format !== 'svg') {
70892 reject(new Error(helpers.MSG_IE_BAD_FORMAT));
70893 }
70894
70895 if(_gd) _gd._snapshotInProgress = true;
70896 var promise = toImage(gd, opts);
70897
70898 var filename = opts.filename || gd.fn || 'newplot';
70899 filename += '.' + opts.format.replace('-', '.');
70900
70901 promise.then(function(result) {
70902 if(_gd) _gd._snapshotInProgress = false;
70903 return fileSaver(result, filename, opts.format);
70904 }).then(function(name) {
70905 resolve(name);
70906 }).catch(function(err) {
70907 if(_gd) _gd._snapshotInProgress = false;
70908 reject(err);
70909 });
70910 });
70911}
70912
70913module.exports = downloadImage;
70914
70915},{"../lib":178,"../plot_api/to_image":215,"./filesaver":272,"./helpers":273}],272:[function(_dereq_,module,exports){
70916/**
70917* Copyright 2012-2020, Plotly, Inc.
70918* All rights reserved.
70919*
70920* This source code is licensed under the MIT license found in the
70921* LICENSE file in the root directory of this source tree.
70922*/
70923
70924'use strict';
70925
70926var Lib = _dereq_('../lib');
70927var helpers = _dereq_('./helpers');
70928
70929/*
70930* substantial portions of this code from FileSaver.js
70931* https://github.com/eligrey/FileSaver.js
70932* License: https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md
70933* FileSaver.js
70934* A saveAs() FileSaver implementation.
70935* 1.1.20160328
70936*
70937* By Eli Grey, http://eligrey.com
70938* License: MIT
70939* See https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md
70940*/
70941function fileSaver(url, name, format) {
70942 var saveLink = document.createElement('a');
70943 var canUseSaveLink = 'download' in saveLink;
70944
70945 var promise = new Promise(function(resolve, reject) {
70946 var blob;
70947 var objectUrl;
70948
70949 if(Lib.isIE9orBelow()) {
70950 reject(new Error('IE < 10 unsupported'));
70951 }
70952
70953 // Safari doesn't allow downloading of blob urls
70954 if(Lib.isSafari()) {
70955 var prefix = format === 'svg' ? ',' : ';base64,';
70956 helpers.octetStream(prefix + encodeURIComponent(url));
70957 return resolve(name);
70958 }
70959
70960 // IE 10+ (native saveAs)
70961 if(Lib.isIE()) {
70962 // At this point we are only dealing with a decoded SVG as
70963 // a data URL (since IE only supports SVG)
70964 blob = helpers.createBlob(url, 'svg');
70965 window.navigator.msSaveBlob(blob, name);
70966 blob = null;
70967 return resolve(name);
70968 }
70969
70970 if(canUseSaveLink) {
70971 blob = helpers.createBlob(url, format);
70972 objectUrl = helpers.createObjectURL(blob);
70973
70974 saveLink.href = objectUrl;
70975 saveLink.download = name;
70976 document.body.appendChild(saveLink);
70977 saveLink.click();
70978
70979 document.body.removeChild(saveLink);
70980 helpers.revokeObjectURL(objectUrl);
70981 blob = null;
70982
70983 return resolve(name);
70984 }
70985
70986 reject(new Error('download error'));
70987 });
70988
70989 return promise;
70990}
70991
70992
70993module.exports = fileSaver;
70994
70995},{"../lib":178,"./helpers":273}],273:[function(_dereq_,module,exports){
70996/**
70997* Copyright 2012-2020, Plotly, Inc.
70998* All rights reserved.
70999*
71000* This source code is licensed under the MIT license found in the
71001* LICENSE file in the root directory of this source tree.
71002*/
71003
71004'use strict';
71005
71006var Registry = _dereq_('../registry');
71007
71008exports.getDelay = function(fullLayout) {
71009 if(!fullLayout._has) return 0;
71010
71011 return (
71012 fullLayout._has('gl3d') ||
71013 fullLayout._has('gl2d') ||
71014 fullLayout._has('mapbox')
71015 ) ? 500 : 0;
71016};
71017
71018exports.getRedrawFunc = function(gd) {
71019 return function() {
71020 var fullLayout = gd._fullLayout || {};
71021 var hasPolar = fullLayout._has && fullLayout._has('polar');
71022 var hasLegacyPolar = !hasPolar && gd.data && gd.data[0] && gd.data[0].r;
71023
71024 if(!hasLegacyPolar) {
71025 Registry.getComponentMethod('colorbar', 'draw')(gd);
71026 }
71027 };
71028};
71029
71030exports.encodeSVG = function(svg) {
71031 return 'data:image/svg+xml,' + encodeURIComponent(svg);
71032};
71033
71034exports.encodeJSON = function(json) {
71035 return 'data:application/json,' + encodeURIComponent(json);
71036};
71037
71038var DOM_URL = window.URL || window.webkitURL;
71039
71040exports.createObjectURL = function(blob) {
71041 return DOM_URL.createObjectURL(blob);
71042};
71043
71044exports.revokeObjectURL = function(url) {
71045 return DOM_URL.revokeObjectURL(url);
71046};
71047
71048exports.createBlob = function(url, format) {
71049 if(format === 'svg') {
71050 return new window.Blob([url], {type: 'image/svg+xml;charset=utf-8'});
71051 } else if(format === 'full-json') {
71052 return new window.Blob([url], {type: 'application/json;charset=utf-8'});
71053 } else {
71054 var binary = fixBinary(window.atob(url));
71055 return new window.Blob([binary], {type: 'image/' + format});
71056 }
71057};
71058
71059exports.octetStream = function(s) {
71060 document.location.href = 'data:application/octet-stream' + s;
71061};
71062
71063// Taken from https://bl.ocks.org/nolanlawson/0eac306e4dac2114c752
71064function fixBinary(b) {
71065 var len = b.length;
71066 var buf = new ArrayBuffer(len);
71067 var arr = new Uint8Array(buf);
71068 for(var i = 0; i < len; i++) {
71069 arr[i] = b.charCodeAt(i);
71070 }
71071 return buf;
71072}
71073
71074exports.IMAGE_URL_PREFIX = /^data:image\/\w+;base64,/;
71075
71076exports.MSG_IE_BAD_FORMAT = 'Sorry IE does not support downloading from canvas. Try {format:\'svg\'} instead.';
71077
71078},{"../registry":269}],274:[function(_dereq_,module,exports){
71079/**
71080* Copyright 2012-2020, Plotly, Inc.
71081* All rights reserved.
71082*
71083* This source code is licensed under the MIT license found in the
71084* LICENSE file in the root directory of this source tree.
71085*/
71086
71087
71088'use strict';
71089
71090var helpers = _dereq_('./helpers');
71091
71092var Snapshot = {
71093 getDelay: helpers.getDelay,
71094 getRedrawFunc: helpers.getRedrawFunc,
71095 clone: _dereq_('./cloneplot'),
71096 toSVG: _dereq_('./tosvg'),
71097 svgToImg: _dereq_('./svgtoimg'),
71098 toImage: _dereq_('./toimage'),
71099 downloadImage: _dereq_('./download')
71100};
71101
71102module.exports = Snapshot;
71103
71104},{"./cloneplot":270,"./download":271,"./helpers":273,"./svgtoimg":275,"./toimage":276,"./tosvg":277}],275:[function(_dereq_,module,exports){
71105/**
71106* Copyright 2012-2020, Plotly, Inc.
71107* All rights reserved.
71108*
71109* This source code is licensed under the MIT license found in the
71110* LICENSE file in the root directory of this source tree.
71111*/
71112
71113'use strict';
71114
71115var Lib = _dereq_('../lib');
71116var EventEmitter = _dereq_('events').EventEmitter;
71117
71118var helpers = _dereq_('./helpers');
71119
71120function svgToImg(opts) {
71121 var ev = opts.emitter || new EventEmitter();
71122
71123 var promise = new Promise(function(resolve, reject) {
71124 var Image = window.Image;
71125 var svg = opts.svg;
71126 var format = opts.format || 'png';
71127
71128 // IE only support svg
71129 if(Lib.isIE() && format !== 'svg') {
71130 var ieSvgError = new Error(helpers.MSG_IE_BAD_FORMAT);
71131 reject(ieSvgError);
71132 // eventually remove the ev
71133 // in favor of promises
71134 if(!opts.promise) {
71135 return ev.emit('error', ieSvgError);
71136 } else {
71137 return promise;
71138 }
71139 }
71140
71141 var canvas = opts.canvas;
71142 var scale = opts.scale || 1;
71143 var w0 = opts.width || 300;
71144 var h0 = opts.height || 150;
71145 var w1 = scale * w0;
71146 var h1 = scale * h0;
71147
71148 var ctx = canvas.getContext('2d');
71149 var img = new Image();
71150 var svgBlob, url;
71151
71152 if(format === 'svg' || Lib.isIE9orBelow() || Lib.isSafari()) {
71153 url = helpers.encodeSVG(svg);
71154 } else {
71155 svgBlob = helpers.createBlob(svg, 'svg');
71156 url = helpers.createObjectURL(svgBlob);
71157 }
71158
71159 canvas.width = w1;
71160 canvas.height = h1;
71161
71162 img.onload = function() {
71163 var imgData;
71164
71165 svgBlob = null;
71166 helpers.revokeObjectURL(url);
71167
71168 // don't need to draw to canvas if svg
71169 // save some time and also avoid failure on IE
71170 if(format !== 'svg') {
71171 ctx.drawImage(img, 0, 0, w1, h1);
71172 }
71173
71174 switch(format) {
71175 case 'jpeg':
71176 imgData = canvas.toDataURL('image/jpeg');
71177 break;
71178 case 'png':
71179 imgData = canvas.toDataURL('image/png');
71180 break;
71181 case 'webp':
71182 imgData = canvas.toDataURL('image/webp');
71183 break;
71184 case 'svg':
71185 imgData = url;
71186 break;
71187 default:
71188 var errorMsg = 'Image format is not jpeg, png, svg or webp.';
71189 reject(new Error(errorMsg));
71190 // eventually remove the ev
71191 // in favor of promises
71192 if(!opts.promise) {
71193 return ev.emit('error', errorMsg);
71194 }
71195 }
71196 resolve(imgData);
71197 // eventually remove the ev
71198 // in favor of promises
71199 if(!opts.promise) {
71200 ev.emit('success', imgData);
71201 }
71202 };
71203
71204 img.onerror = function(err) {
71205 svgBlob = null;
71206 helpers.revokeObjectURL(url);
71207
71208 reject(err);
71209 // eventually remove the ev
71210 // in favor of promises
71211 if(!opts.promise) {
71212 return ev.emit('error', err);
71213 }
71214 };
71215
71216 img.src = url;
71217 });
71218
71219 // temporary for backward compatibility
71220 // move to only Promise in 2.0.0
71221 // and eliminate the EventEmitter
71222 if(opts.promise) {
71223 return promise;
71224 }
71225
71226 return ev;
71227}
71228
71229module.exports = svgToImg;
71230
71231},{"../lib":178,"./helpers":273,"events":15}],276:[function(_dereq_,module,exports){
71232/**
71233* Copyright 2012-2020, Plotly, Inc.
71234* All rights reserved.
71235*
71236* This source code is licensed under the MIT license found in the
71237* LICENSE file in the root directory of this source tree.
71238*/
71239
71240'use strict';
71241
71242var EventEmitter = _dereq_('events').EventEmitter;
71243
71244var Registry = _dereq_('../registry');
71245var Lib = _dereq_('../lib');
71246
71247var helpers = _dereq_('./helpers');
71248var clonePlot = _dereq_('./cloneplot');
71249var toSVG = _dereq_('./tosvg');
71250var svgToImg = _dereq_('./svgtoimg');
71251
71252/**
71253 * @param {object} gd figure Object
71254 * @param {object} opts option object
71255 * @param opts.format 'jpeg' | 'png' | 'webp' | 'svg'
71256 */
71257function toImage(gd, opts) {
71258 // first clone the GD so we can operate in a clean environment
71259 var ev = new EventEmitter();
71260
71261 var clone = clonePlot(gd, {format: 'png'});
71262 var clonedGd = clone.gd;
71263
71264 // put the cloned div somewhere off screen before attaching to DOM
71265 clonedGd.style.position = 'absolute';
71266 clonedGd.style.left = '-5000px';
71267 document.body.appendChild(clonedGd);
71268
71269 function wait() {
71270 var delay = helpers.getDelay(clonedGd._fullLayout);
71271
71272 setTimeout(function() {
71273 var svg = toSVG(clonedGd);
71274
71275 var canvas = document.createElement('canvas');
71276 canvas.id = Lib.randstr();
71277
71278 ev = svgToImg({
71279 format: opts.format,
71280 width: clonedGd._fullLayout.width,
71281 height: clonedGd._fullLayout.height,
71282 canvas: canvas,
71283 emitter: ev,
71284 svg: svg
71285 });
71286
71287 ev.clean = function() {
71288 if(clonedGd) document.body.removeChild(clonedGd);
71289 };
71290 }, delay);
71291 }
71292
71293 var redrawFunc = helpers.getRedrawFunc(clonedGd);
71294
71295 Registry.call('plot', clonedGd, clone.data, clone.layout, clone.config)
71296 .then(redrawFunc)
71297 .then(wait)
71298 .catch(function(err) {
71299 ev.emit('error', err);
71300 });
71301
71302
71303 return ev;
71304}
71305
71306module.exports = toImage;
71307
71308},{"../lib":178,"../registry":269,"./cloneplot":270,"./helpers":273,"./svgtoimg":275,"./tosvg":277,"events":15}],277:[function(_dereq_,module,exports){
71309/**
71310* Copyright 2012-2020, Plotly, Inc.
71311* All rights reserved.
71312*
71313* This source code is licensed under the MIT license found in the
71314* LICENSE file in the root directory of this source tree.
71315*/
71316
71317
71318'use strict';
71319
71320var d3 = _dereq_('d3');
71321
71322var Lib = _dereq_('../lib');
71323var Drawing = _dereq_('../components/drawing');
71324var Color = _dereq_('../components/color');
71325
71326var xmlnsNamespaces = _dereq_('../constants/xmlns_namespaces');
71327var DOUBLEQUOTE_REGEX = /"/g;
71328var DUMMY_SUB = 'TOBESTRIPPED';
71329var DUMMY_REGEX = new RegExp('("' + DUMMY_SUB + ')|(' + DUMMY_SUB + '")', 'g');
71330
71331function htmlEntityDecode(s) {
71332 var hiddenDiv = d3.select('body').append('div').style({display: 'none'}).html('');
71333 var replaced = s.replace(/(&[^;]*;)/gi, function(d) {
71334 if(d === '&lt;') { return '&#60;'; } // special handling for brackets
71335 if(d === '&rt;') { return '&#62;'; }
71336 if(d.indexOf('<') !== -1 || d.indexOf('>') !== -1) { return ''; }
71337 return hiddenDiv.html(d).text(); // everything else, let the browser decode it to unicode
71338 });
71339 hiddenDiv.remove();
71340 return replaced;
71341}
71342
71343function xmlEntityEncode(str) {
71344 return str.replace(/&(?!\w+;|\#[0-9]+;| \#x[0-9A-F]+;)/g, '&amp;');
71345}
71346
71347module.exports = function toSVG(gd, format, scale) {
71348 var fullLayout = gd._fullLayout;
71349 var svg = fullLayout._paper;
71350 var toppaper = fullLayout._toppaper;
71351 var width = fullLayout.width;
71352 var height = fullLayout.height;
71353 var i;
71354
71355 // make background color a rect in the svg, then revert after scraping
71356 // all other alterations have been dealt with by properly preparing the svg
71357 // in the first place... like setting cursors with css classes so we don't
71358 // have to remove them, and providing the right namespaces in the svg to
71359 // begin with
71360 svg.insert('rect', ':first-child')
71361 .call(Drawing.setRect, 0, 0, width, height)
71362 .call(Color.fill, fullLayout.paper_bgcolor);
71363
71364 // subplot-specific to-SVG methods
71365 // which notably add the contents of the gl-container
71366 // into the main svg node
71367 var basePlotModules = fullLayout._basePlotModules || [];
71368 for(i = 0; i < basePlotModules.length; i++) {
71369 var _module = basePlotModules[i];
71370
71371 if(_module.toSVG) _module.toSVG(gd);
71372 }
71373
71374 // add top items above them assumes everything in toppaper is either
71375 // a group or a defs, and if it's empty (like hoverlayer) we can ignore it.
71376 if(toppaper) {
71377 var nodes = toppaper.node().childNodes;
71378
71379 // make copy of nodes as childNodes prop gets mutated in loop below
71380 var topGroups = Array.prototype.slice.call(nodes);
71381
71382 for(i = 0; i < topGroups.length; i++) {
71383 var topGroup = topGroups[i];
71384
71385 if(topGroup.childNodes.length) svg.node().appendChild(topGroup);
71386 }
71387 }
71388
71389 // remove draglayer for Adobe Illustrator compatibility
71390 if(fullLayout._draggers) {
71391 fullLayout._draggers.remove();
71392 }
71393
71394 // in case the svg element had an explicit background color, remove this
71395 // we want the rect to get the color so it's the right size; svg bg will
71396 // fill whatever container it's displayed in regardless of plot size.
71397 svg.node().style.background = '';
71398
71399 svg.selectAll('text')
71400 .attr({'data-unformatted': null, 'data-math': null})
71401 .each(function() {
71402 var txt = d3.select(this);
71403
71404 // hidden text is pre-formatting mathjax, the browser ignores it
71405 // but in a static plot it's useless and it can confuse batik
71406 // we've tried to standardize on display:none but make sure we still
71407 // catch visibility:hidden if it ever arises
71408 if(this.style.visibility === 'hidden' || this.style.display === 'none') {
71409 txt.remove();
71410 return;
71411 } else {
71412 // clear other visibility/display values to default
71413 // to not potentially confuse non-browser SVG implementations
71414 txt.style({visibility: null, display: null});
71415 }
71416
71417 // Font family styles break things because of quotation marks,
71418 // so we must remove them *after* the SVG DOM has been serialized
71419 // to a string (browsers convert singles back)
71420 var ff = this.style.fontFamily;
71421 if(ff && ff.indexOf('"') !== -1) {
71422 txt.style('font-family', ff.replace(DOUBLEQUOTE_REGEX, DUMMY_SUB));
71423 }
71424 });
71425
71426
71427 if(fullLayout._gradientUrlQueryParts) {
71428 var queryParts = [];
71429 for(var k in fullLayout._gradientUrlQueryParts) queryParts.push(k);
71430
71431 if(queryParts.length) {
71432 svg.selectAll(queryParts.join(',')).each(function() {
71433 var pt = d3.select(this);
71434
71435 // similar to font family styles above,
71436 // we must remove " after the SVG DOM has been serialized
71437 var fill = this.style.fill;
71438 if(fill && fill.indexOf('url(') !== -1) {
71439 pt.style('fill', fill.replace(DOUBLEQUOTE_REGEX, DUMMY_SUB));
71440 }
71441
71442 var stroke = this.style.stroke;
71443 if(stroke && stroke.indexOf('url(') !== -1) {
71444 pt.style('stroke', stroke.replace(DOUBLEQUOTE_REGEX, DUMMY_SUB));
71445 }
71446 });
71447 }
71448 }
71449
71450 if(format === 'pdf' || format === 'eps') {
71451 // these formats make the extra line MathJax adds around symbols look super thick in some cases
71452 // it looks better if this is removed entirely.
71453 svg.selectAll('#MathJax_SVG_glyphs path')
71454 .attr('stroke-width', 0);
71455 }
71456
71457 // fix for IE namespacing quirk?
71458 // http://stackoverflow.com/questions/19610089/unwanted-namespaces-on-svg-markup-when-using-xmlserializer-in-javascript-with-ie
71459 svg.node().setAttributeNS(xmlnsNamespaces.xmlns, 'xmlns', xmlnsNamespaces.svg);
71460 svg.node().setAttributeNS(xmlnsNamespaces.xmlns, 'xmlns:xlink', xmlnsNamespaces.xlink);
71461
71462 if(format === 'svg' && scale) {
71463 svg.attr('width', scale * width);
71464 svg.attr('height', scale * height);
71465 svg.attr('viewBox', '0 0 ' + width + ' ' + height);
71466 }
71467
71468 var s = new window.XMLSerializer().serializeToString(svg.node());
71469 s = htmlEntityDecode(s);
71470 s = xmlEntityEncode(s);
71471
71472 // Fix quotations around font strings and gradient URLs
71473 s = s.replace(DUMMY_REGEX, '\'');
71474
71475 // IE is very strict, so we will need to clean
71476 // svg with the following regex
71477 // yes this is messy, but do not know a better way
71478 // Even with this IE will not work due to tainted canvas
71479 // see https://github.com/kangax/fabric.js/issues/1957
71480 // http://stackoverflow.com/questions/18112047/canvas-todataurl-working-in-all-browsers-except-ie10
71481 // Leave here just in case the CORS/tainted IE issue gets resolved
71482 if(Lib.isIE()) {
71483 // replace double quote with single quote
71484 s = s.replace(/"/gi, '\'');
71485 // url in svg are single quoted
71486 // since we changed double to single
71487 // we'll need to change these to double-quoted
71488 s = s.replace(/(\('#)([^']*)('\))/gi, '(\"#$2\")');
71489 // font names with spaces will be escaped single-quoted
71490 // we'll need to change these to double-quoted
71491 s = s.replace(/(\\')/gi, '\"');
71492 }
71493
71494 return s;
71495};
71496
71497},{"../components/color":52,"../components/drawing":74,"../constants/xmlns_namespaces":159,"../lib":178,"d3":16}],278:[function(_dereq_,module,exports){
71498/**
71499* Copyright 2012-2020, Plotly, Inc.
71500* All rights reserved.
71501*
71502* This source code is licensed under the MIT license found in the
71503* LICENSE file in the root directory of this source tree.
71504*/
71505
71506'use strict';
71507
71508var Lib = _dereq_('../../lib');
71509
71510// arrayOk attributes, merge them into calcdata array
71511module.exports = function arraysToCalcdata(cd, trace) {
71512 for(var i = 0; i < cd.length; i++) cd[i].i = i;
71513
71514 Lib.mergeArray(trace.text, cd, 'tx');
71515 Lib.mergeArray(trace.hovertext, cd, 'htx');
71516
71517 var marker = trace.marker;
71518 if(marker) {
71519 Lib.mergeArray(marker.opacity, cd, 'mo', true);
71520 Lib.mergeArray(marker.color, cd, 'mc');
71521
71522 var markerLine = marker.line;
71523 if(markerLine) {
71524 Lib.mergeArray(markerLine.color, cd, 'mlc');
71525 Lib.mergeArrayCastPositive(markerLine.width, cd, 'mlw');
71526 }
71527 }
71528};
71529
71530},{"../../lib":178}],279:[function(_dereq_,module,exports){
71531/**
71532* Copyright 2012-2020, Plotly, Inc.
71533* All rights reserved.
71534*
71535* This source code is licensed under the MIT license found in the
71536* LICENSE file in the root directory of this source tree.
71537*/
71538
71539'use strict';
71540
71541var scatterAttrs = _dereq_('../scatter/attributes');
71542var hovertemplateAttrs = _dereq_('../../plots/template_attributes').hovertemplateAttrs;
71543var texttemplateAttrs = _dereq_('../../plots/template_attributes').texttemplateAttrs;
71544var colorScaleAttrs = _dereq_('../../components/colorscale/attributes');
71545var fontAttrs = _dereq_('../../plots/font_attributes');
71546var constants = _dereq_('./constants');
71547
71548var extendFlat = _dereq_('../../lib/extend').extendFlat;
71549
71550var textFontAttrs = fontAttrs({
71551 editType: 'calc',
71552 arrayOk: true,
71553 colorEditType: 'style',
71554
71555});
71556
71557var scatterMarkerAttrs = scatterAttrs.marker;
71558var scatterMarkerLineAttrs = scatterMarkerAttrs.line;
71559
71560var markerLineWidth = extendFlat({},
71561 scatterMarkerLineAttrs.width, { dflt: 0 });
71562
71563var markerLine = extendFlat({
71564 width: markerLineWidth,
71565 editType: 'calc'
71566}, colorScaleAttrs('marker.line'));
71567
71568var marker = extendFlat({
71569 line: markerLine,
71570 editType: 'calc'
71571}, colorScaleAttrs('marker'), {
71572 opacity: {
71573 valType: 'number',
71574 arrayOk: true,
71575 dflt: 1,
71576 min: 0,
71577 max: 1,
71578
71579 editType: 'style',
71580
71581 }
71582});
71583
71584module.exports = {
71585 x: scatterAttrs.x,
71586 x0: scatterAttrs.x0,
71587 dx: scatterAttrs.dx,
71588 y: scatterAttrs.y,
71589 y0: scatterAttrs.y0,
71590 dy: scatterAttrs.dy,
71591
71592 text: scatterAttrs.text,
71593 texttemplate: texttemplateAttrs({editType: 'plot'}, {
71594 keys: constants.eventDataKeys
71595 }),
71596 hovertext: scatterAttrs.hovertext,
71597 hovertemplate: hovertemplateAttrs({}, {
71598 keys: constants.eventDataKeys
71599 }),
71600
71601 textposition: {
71602 valType: 'enumerated',
71603
71604 values: ['inside', 'outside', 'auto', 'none'],
71605 dflt: 'none',
71606 arrayOk: true,
71607 editType: 'calc',
71608
71609 },
71610
71611 insidetextanchor: {
71612 valType: 'enumerated',
71613 values: ['end', 'middle', 'start'],
71614 dflt: 'end',
71615
71616 editType: 'plot',
71617
71618 },
71619
71620 textangle: {
71621 valType: 'angle',
71622 dflt: 'auto',
71623
71624 editType: 'plot',
71625
71626 },
71627
71628 textfont: extendFlat({}, textFontAttrs, {
71629
71630 }),
71631
71632 insidetextfont: extendFlat({}, textFontAttrs, {
71633
71634 }),
71635
71636 outsidetextfont: extendFlat({}, textFontAttrs, {
71637
71638 }),
71639
71640 constraintext: {
71641 valType: 'enumerated',
71642 values: ['inside', 'outside', 'both', 'none'],
71643
71644 dflt: 'both',
71645 editType: 'calc',
71646
71647 },
71648
71649 cliponaxis: extendFlat({}, scatterAttrs.cliponaxis, {
71650
71651 }),
71652
71653 orientation: {
71654 valType: 'enumerated',
71655
71656 values: ['v', 'h'],
71657 editType: 'calc+clearAxisTypes',
71658
71659 },
71660
71661 base: {
71662 valType: 'any',
71663 dflt: null,
71664 arrayOk: true,
71665
71666 editType: 'calc',
71667
71668 },
71669
71670 offset: {
71671 valType: 'number',
71672 dflt: null,
71673 arrayOk: true,
71674
71675 editType: 'calc',
71676
71677 },
71678
71679 width: {
71680 valType: 'number',
71681 dflt: null,
71682 min: 0,
71683 arrayOk: true,
71684
71685 editType: 'calc',
71686
71687 },
71688
71689 marker: marker,
71690
71691 offsetgroup: {
71692 valType: 'string',
71693
71694 dflt: '',
71695 editType: 'calc',
71696
71697 },
71698 alignmentgroup: {
71699 valType: 'string',
71700
71701 dflt: '',
71702 editType: 'calc',
71703
71704 },
71705
71706 selected: {
71707 marker: {
71708 opacity: scatterAttrs.selected.marker.opacity,
71709 color: scatterAttrs.selected.marker.color,
71710 editType: 'style'
71711 },
71712 textfont: scatterAttrs.selected.textfont,
71713 editType: 'style'
71714 },
71715 unselected: {
71716 marker: {
71717 opacity: scatterAttrs.unselected.marker.opacity,
71718 color: scatterAttrs.unselected.marker.color,
71719 editType: 'style'
71720 },
71721 textfont: scatterAttrs.unselected.textfont,
71722 editType: 'style'
71723 },
71724
71725 r: scatterAttrs.r,
71726 t: scatterAttrs.t,
71727
71728 _deprecated: {
71729 bardir: {
71730 valType: 'enumerated',
71731
71732 editType: 'calc',
71733 values: ['v', 'h'],
71734
71735 }
71736 }
71737};
71738
71739},{"../../components/colorscale/attributes":59,"../../lib/extend":173,"../../plots/font_attributes":250,"../../plots/template_attributes":264,"../scatter/attributes":389,"./constants":281}],280:[function(_dereq_,module,exports){
71740/**
71741* Copyright 2012-2020, Plotly, Inc.
71742* All rights reserved.
71743*
71744* This source code is licensed under the MIT license found in the
71745* LICENSE file in the root directory of this source tree.
71746*/
71747
71748'use strict';
71749
71750var Axes = _dereq_('../../plots/cartesian/axes');
71751var hasColorscale = _dereq_('../../components/colorscale/helpers').hasColorscale;
71752var colorscaleCalc = _dereq_('../../components/colorscale/calc');
71753var arraysToCalcdata = _dereq_('./arrays_to_calcdata');
71754var calcSelection = _dereq_('../scatter/calc_selection');
71755
71756module.exports = function calc(gd, trace) {
71757 var xa = Axes.getFromId(gd, trace.xaxis || 'x');
71758 var ya = Axes.getFromId(gd, trace.yaxis || 'y');
71759 var size, pos;
71760
71761 var sizeOpts = {
71762 msUTC: !!(trace.base || trace.base === 0)
71763 };
71764
71765 if(trace.orientation === 'h') {
71766 size = xa.makeCalcdata(trace, 'x', sizeOpts);
71767 pos = ya.makeCalcdata(trace, 'y');
71768 } else {
71769 size = ya.makeCalcdata(trace, 'y', sizeOpts);
71770 pos = xa.makeCalcdata(trace, 'x');
71771 }
71772
71773 // create the "calculated data" to plot
71774 var serieslen = Math.min(pos.length, size.length);
71775 var cd = new Array(serieslen);
71776
71777 // set position and size
71778 for(var i = 0; i < serieslen; i++) {
71779 cd[i] = { p: pos[i], s: size[i] };
71780
71781 if(trace.ids) {
71782 cd[i].id = String(trace.ids[i]);
71783 }
71784 }
71785
71786 // auto-z and autocolorscale if applicable
71787 if(hasColorscale(trace, 'marker')) {
71788 colorscaleCalc(gd, trace, {
71789 vals: trace.marker.color,
71790 containerStr: 'marker',
71791 cLetter: 'c'
71792 });
71793 }
71794 if(hasColorscale(trace, 'marker.line')) {
71795 colorscaleCalc(gd, trace, {
71796 vals: trace.marker.line.color,
71797 containerStr: 'marker.line',
71798 cLetter: 'c'
71799 });
71800 }
71801
71802 arraysToCalcdata(cd, trace);
71803 calcSelection(cd, trace);
71804
71805 return cd;
71806};
71807
71808},{"../../components/colorscale/calc":60,"../../components/colorscale/helpers":63,"../../plots/cartesian/axes":222,"../scatter/calc_selection":391,"./arrays_to_calcdata":278}],281:[function(_dereq_,module,exports){
71809/**
71810* Copyright 2012-2020, Plotly, Inc.
71811* All rights reserved.
71812*
71813* This source code is licensed under the MIT license found in the
71814* LICENSE file in the root directory of this source tree.
71815*/
71816
71817
71818'use strict';
71819
71820module.exports = {
71821 // padding in pixels around text
71822 TEXTPAD: 3,
71823 // 'value' and 'label' are not really necessary for bar traces,
71824 // but they were made available to `texttemplate` (maybe by accident)
71825 // via tokens `%{value}` and `%{label}` starting in 1.50.0,
71826 // so let's include them in the event data also.
71827 eventDataKeys: ['value', 'label']
71828};
71829
71830},{}],282:[function(_dereq_,module,exports){
71831/**
71832* Copyright 2012-2020, Plotly, Inc.
71833* All rights reserved.
71834*
71835* This source code is licensed under the MIT license found in the
71836* LICENSE file in the root directory of this source tree.
71837*/
71838
71839'use strict';
71840
71841var isNumeric = _dereq_('fast-isnumeric');
71842var isArrayOrTypedArray = _dereq_('../../lib').isArrayOrTypedArray;
71843var BADNUM = _dereq_('../../constants/numerical').BADNUM;
71844
71845var Registry = _dereq_('../../registry');
71846var Axes = _dereq_('../../plots/cartesian/axes');
71847var getAxisGroup = _dereq_('../../plots/cartesian/axis_ids').getAxisGroup;
71848var Sieve = _dereq_('./sieve.js');
71849
71850/*
71851 * Bar chart stacking/grouping positioning and autoscaling calculations
71852 * for each direction separately calculate the ranges and positions
71853 * note that this handles histograms too
71854 * now doing this one subplot at a time
71855 */
71856
71857function crossTraceCalc(gd, plotinfo) {
71858 var xa = plotinfo.xaxis;
71859 var ya = plotinfo.yaxis;
71860
71861 var fullLayout = gd._fullLayout;
71862 var fullTraces = gd._fullData;
71863 var calcTraces = gd.calcdata;
71864 var calcTracesHorz = [];
71865 var calcTracesVert = [];
71866
71867 for(var i = 0; i < fullTraces.length; i++) {
71868 var fullTrace = fullTraces[i];
71869 if(
71870 fullTrace.visible === true &&
71871 Registry.traceIs(fullTrace, 'bar') &&
71872 fullTrace.xaxis === xa._id &&
71873 fullTrace.yaxis === ya._id
71874 ) {
71875 if(fullTrace.orientation === 'h') {
71876 calcTracesHorz.push(calcTraces[i]);
71877 } else {
71878 calcTracesVert.push(calcTraces[i]);
71879 }
71880
71881 if(fullTrace._computePh) {
71882 var cd = gd.calcdata[i];
71883 for(var j = 0; j < cd.length; j++) {
71884 if(typeof cd[j].ph0 === 'function') cd[j].ph0 = cd[j].ph0();
71885 if(typeof cd[j].ph1 === 'function') cd[j].ph1 = cd[j].ph1();
71886 }
71887 }
71888 }
71889 }
71890
71891 var opts = {
71892 mode: fullLayout.barmode,
71893 norm: fullLayout.barnorm,
71894 gap: fullLayout.bargap,
71895 groupgap: fullLayout.bargroupgap
71896 };
71897
71898 setGroupPositions(gd, xa, ya, calcTracesVert, opts);
71899 setGroupPositions(gd, ya, xa, calcTracesHorz, opts);
71900}
71901
71902function setGroupPositions(gd, pa, sa, calcTraces, opts) {
71903 if(!calcTraces.length) return;
71904
71905 var excluded;
71906 var included;
71907 var i, calcTrace, fullTrace;
71908
71909 initBase(sa, calcTraces);
71910
71911 switch(opts.mode) {
71912 case 'overlay':
71913 setGroupPositionsInOverlayMode(pa, sa, calcTraces, opts);
71914 break;
71915
71916 case 'group':
71917 // exclude from the group those traces for which the user set an offset
71918 excluded = [];
71919 included = [];
71920 for(i = 0; i < calcTraces.length; i++) {
71921 calcTrace = calcTraces[i];
71922 fullTrace = calcTrace[0].trace;
71923
71924 if(fullTrace.offset === undefined) included.push(calcTrace);
71925 else excluded.push(calcTrace);
71926 }
71927
71928 if(included.length) {
71929 setGroupPositionsInGroupMode(gd, pa, sa, included, opts);
71930 }
71931 if(excluded.length) {
71932 setGroupPositionsInOverlayMode(pa, sa, excluded, opts);
71933 }
71934 break;
71935
71936 case 'stack':
71937 case 'relative':
71938 // exclude from the stack those traces for which the user set a base
71939 excluded = [];
71940 included = [];
71941 for(i = 0; i < calcTraces.length; i++) {
71942 calcTrace = calcTraces[i];
71943 fullTrace = calcTrace[0].trace;
71944
71945 if(fullTrace.base === undefined) included.push(calcTrace);
71946 else excluded.push(calcTrace);
71947 }
71948
71949 if(included.length) {
71950 setGroupPositionsInStackOrRelativeMode(gd, pa, sa, included, opts);
71951 }
71952 if(excluded.length) {
71953 setGroupPositionsInOverlayMode(pa, sa, excluded, opts);
71954 }
71955 break;
71956 }
71957
71958 collectExtents(calcTraces, pa);
71959}
71960
71961function initBase(sa, calcTraces) {
71962 var i, j;
71963
71964 for(i = 0; i < calcTraces.length; i++) {
71965 var cd = calcTraces[i];
71966 var trace = cd[0].trace;
71967 var base = (trace.type === 'funnel') ? trace._base : trace.base;
71968 var b;
71969
71970 // not sure if it really makes sense to have dates for bar size data...
71971 // ideally if we want to make gantt charts or something we'd treat
71972 // the actual size (trace.x or y) as time delta but base as absolute
71973 // time. But included here for completeness.
71974 var scalendar = trace.orientation === 'h' ? trace.xcalendar : trace.ycalendar;
71975
71976 // 'base' on categorical axes makes no sense
71977 var d2c = sa.type === 'category' || sa.type === 'multicategory' ?
71978 function() { return null; } :
71979 sa.d2c;
71980
71981 if(isArrayOrTypedArray(base)) {
71982 for(j = 0; j < Math.min(base.length, cd.length); j++) {
71983 b = d2c(base[j], 0, scalendar);
71984 if(isNumeric(b)) {
71985 cd[j].b = +b;
71986 cd[j].hasB = 1;
71987 } else cd[j].b = 0;
71988 }
71989 for(; j < cd.length; j++) {
71990 cd[j].b = 0;
71991 }
71992 } else {
71993 b = d2c(base, 0, scalendar);
71994 var hasBase = isNumeric(b);
71995 b = hasBase ? b : 0;
71996 for(j = 0; j < cd.length; j++) {
71997 cd[j].b = b;
71998 if(hasBase) cd[j].hasB = 1;
71999 }
72000 }
72001 }
72002}
72003
72004function setGroupPositionsInOverlayMode(pa, sa, calcTraces, opts) {
72005 // update position axis and set bar offsets and widths
72006 for(var i = 0; i < calcTraces.length; i++) {
72007 var calcTrace = calcTraces[i];
72008
72009 var sieve = new Sieve([calcTrace], {
72010 sepNegVal: false,
72011 overlapNoMerge: !opts.norm
72012 });
72013
72014 // set bar offsets and widths, and update position axis
72015 setOffsetAndWidth(pa, sieve, opts);
72016
72017 // set bar bases and sizes, and update size axis
72018 //
72019 // (note that `setGroupPositionsInOverlayMode` handles the case barnorm
72020 // is defined, because this function is also invoked for traces that
72021 // can't be grouped or stacked)
72022 if(opts.norm) {
72023 sieveBars(sieve);
72024 normalizeBars(sa, sieve, opts);
72025 } else {
72026 setBaseAndTop(sa, sieve);
72027 }
72028 }
72029}
72030
72031function setGroupPositionsInGroupMode(gd, pa, sa, calcTraces, opts) {
72032 var sieve = new Sieve(calcTraces, {
72033 sepNegVal: false,
72034 overlapNoMerge: !opts.norm
72035 });
72036
72037 // set bar offsets and widths, and update position axis
72038 setOffsetAndWidthInGroupMode(gd, pa, sieve, opts);
72039
72040 // relative-stack bars within the same trace that would otherwise
72041 // be hidden
72042 unhideBarsWithinTrace(sieve);
72043
72044 // set bar bases and sizes, and update size axis
72045 if(opts.norm) {
72046 sieveBars(sieve);
72047 normalizeBars(sa, sieve, opts);
72048 } else {
72049 setBaseAndTop(sa, sieve);
72050 }
72051}
72052
72053function setGroupPositionsInStackOrRelativeMode(gd, pa, sa, calcTraces, opts) {
72054 var sieve = new Sieve(calcTraces, {
72055 sepNegVal: opts.mode === 'relative',
72056 overlapNoMerge: !(opts.norm || opts.mode === 'stack' || opts.mode === 'relative')
72057 });
72058
72059 // set bar offsets and widths, and update position axis
72060 setOffsetAndWidth(pa, sieve, opts);
72061
72062 // set bar bases and sizes, and update size axis
72063 stackBars(sa, sieve, opts);
72064
72065 // flag the outmost bar (for text display purposes)
72066 for(var i = 0; i < calcTraces.length; i++) {
72067 var calcTrace = calcTraces[i];
72068
72069 for(var j = 0; j < calcTrace.length; j++) {
72070 var bar = calcTrace[j];
72071
72072 if(bar.s !== BADNUM) {
72073 var isOutmostBar = ((bar.b + bar.s) === sieve.get(bar.p, bar.s));
72074 if(isOutmostBar) bar._outmost = true;
72075 }
72076 }
72077 }
72078
72079 // Note that marking the outmost bars has to be done
72080 // before `normalizeBars` changes `bar.b` and `bar.s`.
72081 if(opts.norm) normalizeBars(sa, sieve, opts);
72082}
72083
72084function setOffsetAndWidth(pa, sieve, opts) {
72085 var minDiff = sieve.minDiff;
72086 var calcTraces = sieve.traces;
72087
72088 // set bar offsets and widths
72089 var barGroupWidth = minDiff * (1 - opts.gap);
72090 var barWidthPlusGap = barGroupWidth;
72091 var barWidth = barWidthPlusGap * (1 - (opts.groupgap || 0));
72092
72093 // computer bar group center and bar offset
72094 var offsetFromCenter = -barWidth / 2;
72095
72096 for(var i = 0; i < calcTraces.length; i++) {
72097 var calcTrace = calcTraces[i];
72098 var t = calcTrace[0].t;
72099
72100 // store bar width and offset for this trace
72101 t.barwidth = barWidth;
72102 t.poffset = offsetFromCenter;
72103 t.bargroupwidth = barGroupWidth;
72104 t.bardelta = minDiff;
72105 }
72106
72107 // stack bars that only differ by rounding
72108 sieve.binWidth = calcTraces[0][0].t.barwidth / 100;
72109
72110 // if defined, apply trace offset and width
72111 applyAttributes(sieve);
72112
72113 // store the bar center in each calcdata item
72114 setBarCenterAndWidth(pa, sieve);
72115
72116 // update position axes
72117 updatePositionAxis(pa, sieve);
72118}
72119
72120function setOffsetAndWidthInGroupMode(gd, pa, sieve, opts) {
72121 var fullLayout = gd._fullLayout;
72122 var positions = sieve.positions;
72123 var distinctPositions = sieve.distinctPositions;
72124 var minDiff = sieve.minDiff;
72125 var calcTraces = sieve.traces;
72126 var nTraces = calcTraces.length;
72127
72128 // if there aren't any overlapping positions,
72129 // let them have full width even if mode is group
72130 var overlap = (positions.length !== distinctPositions.length);
72131 var barGroupWidth = minDiff * (1 - opts.gap);
72132
72133 var groupId = getAxisGroup(fullLayout, pa._id) + calcTraces[0][0].trace.orientation;
72134 var alignmentGroups = fullLayout._alignmentOpts[groupId] || {};
72135
72136 for(var i = 0; i < nTraces; i++) {
72137 var calcTrace = calcTraces[i];
72138 var trace = calcTrace[0].trace;
72139
72140 var alignmentGroupOpts = alignmentGroups[trace.alignmentgroup] || {};
72141 var nOffsetGroups = Object.keys(alignmentGroupOpts.offsetGroups || {}).length;
72142
72143 var barWidthPlusGap;
72144 if(nOffsetGroups) {
72145 barWidthPlusGap = barGroupWidth / nOffsetGroups;
72146 } else {
72147 barWidthPlusGap = overlap ? barGroupWidth / nTraces : barGroupWidth;
72148 }
72149
72150 var barWidth = barWidthPlusGap * (1 - (opts.groupgap || 0));
72151
72152 var offsetFromCenter;
72153 if(nOffsetGroups) {
72154 offsetFromCenter = ((2 * trace._offsetIndex + 1 - nOffsetGroups) * barWidthPlusGap - barWidth) / 2;
72155 } else {
72156 offsetFromCenter = overlap ?
72157 ((2 * i + 1 - nTraces) * barWidthPlusGap - barWidth) / 2 :
72158 -barWidth / 2;
72159 }
72160
72161 var t = calcTrace[0].t;
72162 t.barwidth = barWidth;
72163 t.poffset = offsetFromCenter;
72164 t.bargroupwidth = barGroupWidth;
72165 t.bardelta = minDiff;
72166 }
72167
72168 // stack bars that only differ by rounding
72169 sieve.binWidth = calcTraces[0][0].t.barwidth / 100;
72170
72171 // if defined, apply trace width
72172 applyAttributes(sieve);
72173
72174 // store the bar center in each calcdata item
72175 setBarCenterAndWidth(pa, sieve);
72176
72177 // update position axes
72178 updatePositionAxis(pa, sieve, overlap);
72179}
72180
72181function applyAttributes(sieve) {
72182 var calcTraces = sieve.traces;
72183 var i, j;
72184
72185 for(i = 0; i < calcTraces.length; i++) {
72186 var calcTrace = calcTraces[i];
72187 var calcTrace0 = calcTrace[0];
72188 var fullTrace = calcTrace0.trace;
72189 var t = calcTrace0.t;
72190 var offset = fullTrace._offset || fullTrace.offset;
72191 var initialPoffset = t.poffset;
72192 var newPoffset;
72193
72194 if(isArrayOrTypedArray(offset)) {
72195 // if offset is an array, then clone it into t.poffset.
72196 newPoffset = Array.prototype.slice.call(offset, 0, calcTrace.length);
72197
72198 // guard against non-numeric items
72199 for(j = 0; j < newPoffset.length; j++) {
72200 if(!isNumeric(newPoffset[j])) {
72201 newPoffset[j] = initialPoffset;
72202 }
72203 }
72204
72205 // if the length of the array is too short,
72206 // then extend it with the initial value of t.poffset
72207 for(j = newPoffset.length; j < calcTrace.length; j++) {
72208 newPoffset.push(initialPoffset);
72209 }
72210
72211 t.poffset = newPoffset;
72212 } else if(offset !== undefined) {
72213 t.poffset = offset;
72214 }
72215
72216 var width = fullTrace._width || fullTrace.width;
72217 var initialBarwidth = t.barwidth;
72218
72219 if(isArrayOrTypedArray(width)) {
72220 // if width is an array, then clone it into t.barwidth.
72221 var newBarwidth = Array.prototype.slice.call(width, 0, calcTrace.length);
72222
72223 // guard against non-numeric items
72224 for(j = 0; j < newBarwidth.length; j++) {
72225 if(!isNumeric(newBarwidth[j])) newBarwidth[j] = initialBarwidth;
72226 }
72227
72228 // if the length of the array is too short,
72229 // then extend it with the initial value of t.barwidth
72230 for(j = newBarwidth.length; j < calcTrace.length; j++) {
72231 newBarwidth.push(initialBarwidth);
72232 }
72233
72234 t.barwidth = newBarwidth;
72235
72236 // if user didn't set offset,
72237 // then correct t.poffset to ensure bars remain centered
72238 if(offset === undefined) {
72239 newPoffset = [];
72240 for(j = 0; j < calcTrace.length; j++) {
72241 newPoffset.push(
72242 initialPoffset + (initialBarwidth - newBarwidth[j]) / 2
72243 );
72244 }
72245 t.poffset = newPoffset;
72246 }
72247 } else if(width !== undefined) {
72248 t.barwidth = width;
72249
72250 // if user didn't set offset,
72251 // then correct t.poffset to ensure bars remain centered
72252 if(offset === undefined) {
72253 t.poffset = initialPoffset + (initialBarwidth - width) / 2;
72254 }
72255 }
72256 }
72257}
72258
72259function setBarCenterAndWidth(pa, sieve) {
72260 var calcTraces = sieve.traces;
72261 var pLetter = getAxisLetter(pa);
72262
72263 for(var i = 0; i < calcTraces.length; i++) {
72264 var calcTrace = calcTraces[i];
72265 var t = calcTrace[0].t;
72266 var poffset = t.poffset;
72267 var poffsetIsArray = Array.isArray(poffset);
72268 var barwidth = t.barwidth;
72269 var barwidthIsArray = Array.isArray(barwidth);
72270
72271 for(var j = 0; j < calcTrace.length; j++) {
72272 var calcBar = calcTrace[j];
72273
72274 // store the actual bar width and position, for use by hover
72275 var width = calcBar.w = barwidthIsArray ? barwidth[j] : barwidth;
72276 calcBar[pLetter] = calcBar.p + (poffsetIsArray ? poffset[j] : poffset) + width / 2;
72277 }
72278 }
72279}
72280
72281function updatePositionAxis(pa, sieve, allowMinDtick) {
72282 var calcTraces = sieve.traces;
72283 var minDiff = sieve.minDiff;
72284 var vpad = minDiff / 2;
72285
72286 Axes.minDtick(pa, sieve.minDiff, sieve.distinctPositions[0], allowMinDtick);
72287
72288 for(var i = 0; i < calcTraces.length; i++) {
72289 var calcTrace = calcTraces[i];
72290 var calcTrace0 = calcTrace[0];
72291 var fullTrace = calcTrace0.trace;
72292 var pts = [];
72293 var bar, l, r, j;
72294
72295 for(j = 0; j < calcTrace.length; j++) {
72296 bar = calcTrace[j];
72297 l = bar.p - vpad;
72298 r = bar.p + vpad;
72299 pts.push(l, r);
72300 }
72301
72302 if(fullTrace.width || fullTrace.offset) {
72303 var t = calcTrace0.t;
72304 var poffset = t.poffset;
72305 var barwidth = t.barwidth;
72306 var poffsetIsArray = Array.isArray(poffset);
72307 var barwidthIsArray = Array.isArray(barwidth);
72308
72309 for(j = 0; j < calcTrace.length; j++) {
72310 bar = calcTrace[j];
72311 var calcBarOffset = poffsetIsArray ? poffset[j] : poffset;
72312 var calcBarWidth = barwidthIsArray ? barwidth[j] : barwidth;
72313 l = bar.p + calcBarOffset;
72314 r = l + calcBarWidth;
72315 pts.push(l, r);
72316 }
72317 }
72318
72319 fullTrace._extremes[pa._id] = Axes.findExtremes(pa, pts, {padded: false});
72320 }
72321}
72322
72323// store these bar bases and tops in calcdata
72324// and make sure the size axis includes zero,
72325// along with the bases and tops of each bar.
72326function setBaseAndTop(sa, sieve) {
72327 var calcTraces = sieve.traces;
72328 var sLetter = getAxisLetter(sa);
72329
72330 for(var i = 0; i < calcTraces.length; i++) {
72331 var calcTrace = calcTraces[i];
72332 var fullTrace = calcTrace[0].trace;
72333 var pts = [];
72334 var tozero = false;
72335
72336 for(var j = 0; j < calcTrace.length; j++) {
72337 var bar = calcTrace[j];
72338 var base = bar.b;
72339 var top = base + bar.s;
72340
72341 bar[sLetter] = top;
72342 pts.push(top);
72343 if(bar.hasB) pts.push(base);
72344
72345 if(!bar.hasB || !bar.b) {
72346 tozero = true;
72347 }
72348 }
72349
72350 fullTrace._extremes[sa._id] = Axes.findExtremes(sa, pts, {
72351 tozero: tozero,
72352 padded: true
72353 });
72354 }
72355}
72356
72357function stackBars(sa, sieve, opts) {
72358 var sLetter = getAxisLetter(sa);
72359 var calcTraces = sieve.traces;
72360 var calcTrace;
72361 var fullTrace;
72362 var isFunnel;
72363 var i, j;
72364 var bar;
72365
72366 for(i = 0; i < calcTraces.length; i++) {
72367 calcTrace = calcTraces[i];
72368 fullTrace = calcTrace[0].trace;
72369
72370 if(fullTrace.type === 'funnel') {
72371 for(j = 0; j < calcTrace.length; j++) {
72372 bar = calcTrace[j];
72373
72374 if(bar.s !== BADNUM) {
72375 // create base of funnels
72376 sieve.put(bar.p, -0.5 * bar.s);
72377 }
72378 }
72379 }
72380 }
72381
72382 for(i = 0; i < calcTraces.length; i++) {
72383 calcTrace = calcTraces[i];
72384 fullTrace = calcTrace[0].trace;
72385
72386 isFunnel = (fullTrace.type === 'funnel');
72387
72388 var pts = [];
72389
72390 for(j = 0; j < calcTrace.length; j++) {
72391 bar = calcTrace[j];
72392
72393 if(bar.s !== BADNUM) {
72394 // stack current bar and get previous sum
72395 var value;
72396 if(isFunnel) {
72397 value = bar.s;
72398 } else {
72399 value = bar.s + bar.b;
72400 }
72401
72402 var base = sieve.put(bar.p, value);
72403
72404 var top = base + value;
72405
72406 // store the bar base and top in each calcdata item
72407 bar.b = base;
72408 bar[sLetter] = top;
72409
72410 if(!opts.norm) {
72411 pts.push(top);
72412 if(bar.hasB) {
72413 pts.push(base);
72414 }
72415 }
72416 }
72417 }
72418
72419 // if barnorm is set, let normalizeBars update the axis range
72420 if(!opts.norm) {
72421 fullTrace._extremes[sa._id] = Axes.findExtremes(sa, pts, {
72422 // N.B. we don't stack base with 'base',
72423 // so set tozero:true always!
72424 tozero: true,
72425 padded: true
72426 });
72427 }
72428 }
72429}
72430
72431function sieveBars(sieve) {
72432 var calcTraces = sieve.traces;
72433
72434 for(var i = 0; i < calcTraces.length; i++) {
72435 var calcTrace = calcTraces[i];
72436
72437 for(var j = 0; j < calcTrace.length; j++) {
72438 var bar = calcTrace[j];
72439
72440 if(bar.s !== BADNUM) {
72441 sieve.put(bar.p, bar.b + bar.s);
72442 }
72443 }
72444 }
72445}
72446
72447function unhideBarsWithinTrace(sieve) {
72448 var calcTraces = sieve.traces;
72449
72450 for(var i = 0; i < calcTraces.length; i++) {
72451 var calcTrace = calcTraces[i];
72452 var fullTrace = calcTrace[0].trace;
72453
72454 if(fullTrace.base === undefined) {
72455 var inTraceSieve = new Sieve([calcTrace], {
72456 sepNegVal: true,
72457 overlapNoMerge: true
72458 });
72459
72460 for(var j = 0; j < calcTrace.length; j++) {
72461 var bar = calcTrace[j];
72462
72463 if(bar.p !== BADNUM) {
72464 // stack current bar and get previous sum
72465 var base = inTraceSieve.put(bar.p, bar.b + bar.s);
72466
72467 // if previous sum if non-zero, this means:
72468 // multiple bars have same starting point are potentially hidden,
72469 // shift them vertically so that all bars are visible by default
72470 if(base) bar.b = base;
72471 }
72472 }
72473 }
72474 }
72475}
72476
72477// Note:
72478//
72479// normalizeBars requires that either sieveBars or stackBars has been
72480// previously invoked.
72481function normalizeBars(sa, sieve, opts) {
72482 var calcTraces = sieve.traces;
72483 var sLetter = getAxisLetter(sa);
72484 var sTop = opts.norm === 'fraction' ? 1 : 100;
72485 var sTiny = sTop / 1e9; // in case of rounding error in sum
72486 var sMin = sa.l2c(sa.c2l(0));
72487 var sMax = opts.mode === 'stack' ? sTop : sMin;
72488
72489 function needsPadding(v) {
72490 return (
72491 isNumeric(sa.c2l(v)) &&
72492 ((v < sMin - sTiny) || (v > sMax + sTiny) || !isNumeric(sMin))
72493 );
72494 }
72495
72496 for(var i = 0; i < calcTraces.length; i++) {
72497 var calcTrace = calcTraces[i];
72498 var fullTrace = calcTrace[0].trace;
72499 var pts = [];
72500 var tozero = false;
72501 var padded = false;
72502
72503 for(var j = 0; j < calcTrace.length; j++) {
72504 var bar = calcTrace[j];
72505
72506 if(bar.s !== BADNUM) {
72507 var scale = Math.abs(sTop / sieve.get(bar.p, bar.s));
72508 bar.b *= scale;
72509 bar.s *= scale;
72510
72511 var base = bar.b;
72512 var top = base + bar.s;
72513
72514 bar[sLetter] = top;
72515 pts.push(top);
72516 padded = padded || needsPadding(top);
72517
72518 if(bar.hasB) {
72519 pts.push(base);
72520 padded = padded || needsPadding(base);
72521 }
72522
72523 if(!bar.hasB || !bar.b) {
72524 tozero = true;
72525 }
72526 }
72527 }
72528
72529 fullTrace._extremes[sa._id] = Axes.findExtremes(sa, pts, {
72530 tozero: tozero,
72531 padded: padded
72532 });
72533 }
72534}
72535
72536// find the full position span of bars at each position
72537// for use by hover, to ensure labels move in if bars are
72538// narrower than the space they're in.
72539// run once per trace group (subplot & direction) and
72540// the same mapping is attached to all calcdata traces
72541function collectExtents(calcTraces, pa) {
72542 var pLetter = getAxisLetter(pa);
72543 var extents = {};
72544 var i, j, cd;
72545
72546 var pMin = Infinity;
72547 var pMax = -Infinity;
72548
72549 for(i = 0; i < calcTraces.length; i++) {
72550 cd = calcTraces[i];
72551 for(j = 0; j < cd.length; j++) {
72552 var p = cd[j].p;
72553 if(isNumeric(p)) {
72554 pMin = Math.min(pMin, p);
72555 pMax = Math.max(pMax, p);
72556 }
72557 }
72558 }
72559
72560 // this is just for positioning of hover labels, and nobody will care if
72561 // the label is 1px too far out; so round positions to 1/10K in case
72562 // position values don't exactly match from trace to trace
72563 var roundFactor = 10000 / (pMax - pMin);
72564 var round = extents.round = function(p) {
72565 return String(Math.round(roundFactor * (p - pMin)));
72566 };
72567
72568 for(i = 0; i < calcTraces.length; i++) {
72569 cd = calcTraces[i];
72570 cd[0].t.extents = extents;
72571
72572 var poffset = cd[0].t.poffset;
72573 var poffsetIsArray = Array.isArray(poffset);
72574
72575 for(j = 0; j < cd.length; j++) {
72576 var di = cd[j];
72577 var p0 = di[pLetter] - di.w / 2;
72578
72579 if(isNumeric(p0)) {
72580 var p1 = di[pLetter] + di.w / 2;
72581 var pVal = round(di.p);
72582 if(extents[pVal]) {
72583 extents[pVal] = [Math.min(p0, extents[pVal][0]), Math.max(p1, extents[pVal][1])];
72584 } else {
72585 extents[pVal] = [p0, p1];
72586 }
72587 }
72588
72589 di.p0 = di.p + (poffsetIsArray ? poffset[j] : poffset);
72590 di.p1 = di.p0 + di.w;
72591 di.s0 = di.b;
72592 di.s1 = di.s0 + di.s;
72593 }
72594 }
72595}
72596
72597function getAxisLetter(ax) {
72598 return ax._id.charAt(0);
72599}
72600
72601module.exports = {
72602 crossTraceCalc: crossTraceCalc,
72603 setGroupPositions: setGroupPositions
72604};
72605
72606},{"../../constants/numerical":158,"../../lib":178,"../../plots/cartesian/axes":222,"../../plots/cartesian/axis_ids":225,"../../registry":269,"./sieve.js":292,"fast-isnumeric":18}],283:[function(_dereq_,module,exports){
72607/**
72608* Copyright 2012-2020, Plotly, Inc.
72609* All rights reserved.
72610*
72611* This source code is licensed under the MIT license found in the
72612* LICENSE file in the root directory of this source tree.
72613*/
72614
72615'use strict';
72616
72617var Lib = _dereq_('../../lib');
72618var Color = _dereq_('../../components/color');
72619var Registry = _dereq_('../../registry');
72620
72621var handleXYDefaults = _dereq_('../scatter/xy_defaults');
72622var handleStyleDefaults = _dereq_('./style_defaults');
72623var getAxisGroup = _dereq_('../../plots/cartesian/axis_ids').getAxisGroup;
72624var attributes = _dereq_('./attributes');
72625
72626var coerceFont = Lib.coerceFont;
72627
72628function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
72629 function coerce(attr, dflt) {
72630 return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
72631 }
72632
72633 var len = handleXYDefaults(traceIn, traceOut, layout, coerce);
72634 if(!len) {
72635 traceOut.visible = false;
72636 return;
72637 }
72638
72639 coerce('orientation', (traceOut.x && !traceOut.y) ? 'h' : 'v');
72640 coerce('base');
72641 coerce('offset');
72642 coerce('width');
72643
72644 coerce('text');
72645 coerce('hovertext');
72646 coerce('hovertemplate');
72647
72648 var textposition = coerce('textposition');
72649 handleText(traceIn, traceOut, layout, coerce, textposition, {
72650 moduleHasSelected: true,
72651 moduleHasUnselected: true,
72652 moduleHasConstrain: true,
72653 moduleHasCliponaxis: true,
72654 moduleHasTextangle: true,
72655 moduleHasInsideanchor: true
72656 });
72657
72658 handleStyleDefaults(traceIn, traceOut, coerce, defaultColor, layout);
72659
72660 var lineColor = (traceOut.marker.line || {}).color;
72661
72662 // override defaultColor for error bars with defaultLine
72663 var errorBarsSupplyDefaults = Registry.getComponentMethod('errorbars', 'supplyDefaults');
72664 errorBarsSupplyDefaults(traceIn, traceOut, lineColor || Color.defaultLine, {axis: 'y'});
72665 errorBarsSupplyDefaults(traceIn, traceOut, lineColor || Color.defaultLine, {axis: 'x', inherit: 'y'});
72666
72667 Lib.coerceSelectionMarkerOpacity(traceOut, coerce);
72668}
72669
72670function handleGroupingDefaults(traceIn, traceOut, fullLayout, coerce) {
72671 var orientation = traceOut.orientation;
72672 // N.B. grouping is done across all trace types that support it
72673 var posAxId = traceOut[{v: 'x', h: 'y'}[orientation] + 'axis'];
72674 var groupId = getAxisGroup(fullLayout, posAxId) + orientation;
72675
72676 var alignmentOpts = fullLayout._alignmentOpts || {};
72677 var alignmentgroup = coerce('alignmentgroup');
72678
72679 var alignmentGroups = alignmentOpts[groupId];
72680 if(!alignmentGroups) alignmentGroups = alignmentOpts[groupId] = {};
72681
72682 var alignmentGroupOpts = alignmentGroups[alignmentgroup];
72683
72684 if(alignmentGroupOpts) {
72685 alignmentGroupOpts.traces.push(traceOut);
72686 } else {
72687 alignmentGroupOpts = alignmentGroups[alignmentgroup] = {
72688 traces: [traceOut],
72689 alignmentIndex: Object.keys(alignmentGroups).length,
72690 offsetGroups: {}
72691 };
72692 }
72693
72694 var offsetgroup = coerce('offsetgroup');
72695 var offsetGroups = alignmentGroupOpts.offsetGroups;
72696 var offsetGroupOpts = offsetGroups[offsetgroup];
72697
72698 if(offsetgroup) {
72699 if(!offsetGroupOpts) {
72700 offsetGroupOpts = offsetGroups[offsetgroup] = {
72701 offsetIndex: Object.keys(offsetGroups).length
72702 };
72703 }
72704
72705 traceOut._offsetIndex = offsetGroupOpts.offsetIndex;
72706 }
72707}
72708
72709function crossTraceDefaults(fullData, fullLayout) {
72710 var traceIn, traceOut;
72711
72712 function coerce(attr) {
72713 return Lib.coerce(traceOut._input, traceOut, attributes, attr);
72714 }
72715
72716 if(fullLayout.barmode === 'group') {
72717 for(var i = 0; i < fullData.length; i++) {
72718 traceOut = fullData[i];
72719
72720 if(traceOut.type === 'bar') {
72721 traceIn = traceOut._input;
72722 handleGroupingDefaults(traceIn, traceOut, fullLayout, coerce);
72723 }
72724 }
72725 }
72726}
72727
72728function handleText(traceIn, traceOut, layout, coerce, textposition, opts) {
72729 opts = opts || {};
72730 var moduleHasSelected = !(opts.moduleHasSelected === false);
72731 var moduleHasUnselected = !(opts.moduleHasUnselected === false);
72732 var moduleHasConstrain = !(opts.moduleHasConstrain === false);
72733 var moduleHasCliponaxis = !(opts.moduleHasCliponaxis === false);
72734 var moduleHasTextangle = !(opts.moduleHasTextangle === false);
72735 var moduleHasInsideanchor = !(opts.moduleHasInsideanchor === false);
72736 var hasPathbar = !!opts.hasPathbar;
72737
72738 var hasBoth = Array.isArray(textposition) || textposition === 'auto';
72739 var hasInside = hasBoth || textposition === 'inside';
72740 var hasOutside = hasBoth || textposition === 'outside';
72741
72742 if(hasInside || hasOutside) {
72743 var dfltFont = coerceFont(coerce, 'textfont', layout.font);
72744
72745 // Note that coercing `insidetextfont` is always needed –
72746 // even if `textposition` is `outside` for each trace – since
72747 // an outside label can become an inside one, for example because
72748 // of a bar being stacked on top of it.
72749 var insideTextFontDefault = Lib.extendFlat({}, dfltFont);
72750 var isTraceTextfontColorSet = traceIn.textfont && traceIn.textfont.color;
72751 var isColorInheritedFromLayoutFont = !isTraceTextfontColorSet;
72752 if(isColorInheritedFromLayoutFont) {
72753 delete insideTextFontDefault.color;
72754 }
72755 coerceFont(coerce, 'insidetextfont', insideTextFontDefault);
72756
72757 if(hasPathbar) {
72758 var pathbarTextFontDefault = Lib.extendFlat({}, dfltFont);
72759 if(isColorInheritedFromLayoutFont) {
72760 delete pathbarTextFontDefault.color;
72761 }
72762 coerceFont(coerce, 'pathbar.textfont', pathbarTextFontDefault);
72763 }
72764
72765 if(hasOutside) coerceFont(coerce, 'outsidetextfont', dfltFont);
72766
72767 if(moduleHasSelected) coerce('selected.textfont.color');
72768 if(moduleHasUnselected) coerce('unselected.textfont.color');
72769 if(moduleHasConstrain) coerce('constraintext');
72770 if(moduleHasCliponaxis) coerce('cliponaxis');
72771 if(moduleHasTextangle) coerce('textangle');
72772
72773 coerce('texttemplate');
72774 }
72775
72776 if(hasInside) {
72777 if(moduleHasInsideanchor) coerce('insidetextanchor');
72778 }
72779}
72780
72781module.exports = {
72782 supplyDefaults: supplyDefaults,
72783 crossTraceDefaults: crossTraceDefaults,
72784 handleGroupingDefaults: handleGroupingDefaults,
72785 handleText: handleText
72786};
72787
72788},{"../../components/color":52,"../../lib":178,"../../plots/cartesian/axis_ids":225,"../../registry":269,"../scatter/xy_defaults":415,"./attributes":279,"./style_defaults":294}],284:[function(_dereq_,module,exports){
72789/**
72790* Copyright 2012-2020, Plotly, Inc.
72791* All rights reserved.
72792*
72793* This source code is licensed under the MIT license found in the
72794* LICENSE file in the root directory of this source tree.
72795*/
72796
72797'use strict';
72798
72799module.exports = function eventData(out, pt, trace) {
72800 // standard cartesian event data
72801 out.x = 'xVal' in pt ? pt.xVal : pt.x;
72802 out.y = 'yVal' in pt ? pt.yVal : pt.y;
72803 if(pt.xa) out.xaxis = pt.xa;
72804 if(pt.ya) out.yaxis = pt.ya;
72805
72806 if(trace.orientation === 'h') {
72807 out.label = out.y;
72808 out.value = out.x;
72809 } else {
72810 out.label = out.x;
72811 out.value = out.y;
72812 }
72813
72814 return out;
72815};
72816
72817},{}],285:[function(_dereq_,module,exports){
72818/**
72819* Copyright 2012-2020, Plotly, Inc.
72820* All rights reserved.
72821*
72822* This source code is licensed under the MIT license found in the
72823* LICENSE file in the root directory of this source tree.
72824*/
72825
72826'use strict';
72827
72828var isNumeric = _dereq_('fast-isnumeric');
72829var tinycolor = _dereq_('tinycolor2');
72830var isArrayOrTypedArray = _dereq_('../../lib').isArrayOrTypedArray;
72831
72832exports.coerceString = function(attributeDefinition, value, defaultValue) {
72833 if(typeof value === 'string') {
72834 if(value || !attributeDefinition.noBlank) return value;
72835 } else if(typeof value === 'number' || value === true) {
72836 if(!attributeDefinition.strict) return String(value);
72837 }
72838
72839 return (defaultValue !== undefined) ?
72840 defaultValue :
72841 attributeDefinition.dflt;
72842};
72843
72844exports.coerceNumber = function(attributeDefinition, value, defaultValue) {
72845 if(isNumeric(value)) {
72846 value = +value;
72847
72848 var min = attributeDefinition.min;
72849 var max = attributeDefinition.max;
72850 var isOutOfBounds = (min !== undefined && value < min) ||
72851 (max !== undefined && value > max);
72852
72853 if(!isOutOfBounds) return value;
72854 }
72855
72856 return (defaultValue !== undefined) ?
72857 defaultValue :
72858 attributeDefinition.dflt;
72859};
72860
72861exports.coerceColor = function(attributeDefinition, value, defaultValue) {
72862 if(tinycolor(value).isValid()) return value;
72863
72864 return (defaultValue !== undefined) ?
72865 defaultValue :
72866 attributeDefinition.dflt;
72867};
72868
72869exports.coerceEnumerated = function(attributeDefinition, value, defaultValue) {
72870 if(attributeDefinition.coerceNumber) value = +value;
72871
72872 if(attributeDefinition.values.indexOf(value) !== -1) return value;
72873
72874 return (defaultValue !== undefined) ?
72875 defaultValue :
72876 attributeDefinition.dflt;
72877};
72878
72879exports.getValue = function(arrayOrScalar, index) {
72880 var value;
72881 if(!Array.isArray(arrayOrScalar)) value = arrayOrScalar;
72882 else if(index < arrayOrScalar.length) value = arrayOrScalar[index];
72883 return value;
72884};
72885
72886exports.getLineWidth = function(trace, di) {
72887 var w =
72888 (0 < di.mlw) ? di.mlw :
72889 !isArrayOrTypedArray(trace.marker.line.width) ? trace.marker.line.width :
72890 0;
72891
72892 return w;
72893};
72894
72895},{"../../lib":178,"fast-isnumeric":18,"tinycolor2":35}],286:[function(_dereq_,module,exports){
72896/**
72897* Copyright 2012-2020, Plotly, Inc.
72898* All rights reserved.
72899*
72900* This source code is licensed under the MIT license found in the
72901* LICENSE file in the root directory of this source tree.
72902*/
72903
72904
72905'use strict';
72906
72907var Fx = _dereq_('../../components/fx');
72908var Registry = _dereq_('../../registry');
72909var Color = _dereq_('../../components/color');
72910
72911var fillText = _dereq_('../../lib').fillText;
72912var getLineWidth = _dereq_('./helpers').getLineWidth;
72913var hoverLabelText = _dereq_('../../plots/cartesian/axes').hoverLabelText;
72914var BADNUM = _dereq_('../../constants/numerical').BADNUM;
72915
72916function hoverPoints(pointData, xval, yval, hovermode) {
72917 var barPointData = hoverOnBars(pointData, xval, yval, hovermode);
72918
72919 if(barPointData) {
72920 var cd = barPointData.cd;
72921 var trace = cd[0].trace;
72922 var di = cd[barPointData.index];
72923
72924 barPointData.color = getTraceColor(trace, di);
72925 Registry.getComponentMethod('errorbars', 'hoverInfo')(di, trace, barPointData);
72926
72927 return [barPointData];
72928 }
72929}
72930
72931function hoverOnBars(pointData, xval, yval, hovermode) {
72932 var cd = pointData.cd;
72933 var trace = cd[0].trace;
72934 var t = cd[0].t;
72935 var isClosest = (hovermode === 'closest');
72936 var isWaterfall = (trace.type === 'waterfall');
72937 var maxHoverDistance = pointData.maxHoverDistance;
72938
72939 var posVal, sizeVal, posLetter, sizeLetter, dx, dy, pRangeCalc;
72940
72941 function thisBarMinPos(di) { return di[posLetter] - di.w / 2; }
72942 function thisBarMaxPos(di) { return di[posLetter] + di.w / 2; }
72943
72944 var minPos = isClosest ?
72945 thisBarMinPos :
72946 function(di) {
72947 /*
72948 * In compare mode, accept a bar if you're on it *or* its group.
72949 * Nearly always it's the group that matters, but in case the bar
72950 * was explicitly set wider than its group we'd better accept the
72951 * whole bar.
72952 *
72953 * use `bardelta` instead of `bargroupwidth` so we accept hover
72954 * in the gap. That way hover doesn't flash on and off as you
72955 * mouse over the plot in compare modes.
72956 * In 'closest' mode though the flashing seems inevitable,
72957 * without far more complex logic
72958 */
72959 return Math.min(thisBarMinPos(di), di.p - t.bardelta / 2);
72960 };
72961
72962 var maxPos = isClosest ?
72963 thisBarMaxPos :
72964 function(di) {
72965 return Math.max(thisBarMaxPos(di), di.p + t.bardelta / 2);
72966 };
72967
72968 function _positionFn(_minPos, _maxPos) {
72969 // add a little to the pseudo-distance for wider bars, so that like scatter,
72970 // if you are over two overlapping bars, the narrower one wins.
72971 return Fx.inbox(_minPos - posVal, _maxPos - posVal,
72972 maxHoverDistance + Math.min(1, Math.abs(_maxPos - _minPos) / pRangeCalc) - 1);
72973 }
72974
72975 function positionFn(di) {
72976 return _positionFn(minPos(di), maxPos(di));
72977 }
72978
72979 function thisBarPositionFn(di) {
72980 return _positionFn(thisBarMinPos(di), thisBarMaxPos(di));
72981 }
72982
72983 function sizeFn(di) {
72984 var v = sizeVal;
72985 var b = di.b;
72986 var s = di[sizeLetter];
72987
72988 if(isWaterfall) {
72989 var rawS = Math.abs(di.rawS) || 0;
72990 if(v > 0) {
72991 s += rawS;
72992 } else if(v < 0) {
72993 s -= rawS;
72994 }
72995 }
72996
72997 // add a gradient so hovering near the end of a
72998 // bar makes it a little closer match
72999 return Fx.inbox(b - v, s - v, maxHoverDistance + (s - v) / (s - b) - 1);
73000 }
73001
73002 if(trace.orientation === 'h') {
73003 posVal = yval;
73004 sizeVal = xval;
73005 posLetter = 'y';
73006 sizeLetter = 'x';
73007 dx = sizeFn;
73008 dy = positionFn;
73009 } else {
73010 posVal = xval;
73011 sizeVal = yval;
73012 posLetter = 'x';
73013 sizeLetter = 'y';
73014 dy = sizeFn;
73015 dx = positionFn;
73016 }
73017
73018 var pa = pointData[posLetter + 'a'];
73019 var sa = pointData[sizeLetter + 'a'];
73020
73021 pRangeCalc = Math.abs(pa.r2c(pa.range[1]) - pa.r2c(pa.range[0]));
73022
73023 function dxy(di) { return (dx(di) + dy(di)) / 2; }
73024 var distfn = Fx.getDistanceFunction(hovermode, dx, dy, dxy);
73025 Fx.getClosest(cd, distfn, pointData);
73026
73027 // skip the rest (for this trace) if we didn't find a close point
73028 if(pointData.index === false) return;
73029
73030 // skip points inside axis rangebreaks
73031 if(cd[pointData.index].p === BADNUM) return;
73032
73033 // if we get here and we're not in 'closest' mode, push min/max pos back
73034 // onto the group - even though that means occasionally the mouse will be
73035 // over the hover label.
73036 if(!isClosest) {
73037 minPos = function(di) {
73038 return Math.min(thisBarMinPos(di), di.p - t.bargroupwidth / 2);
73039 };
73040 maxPos = function(di) {
73041 return Math.max(thisBarMaxPos(di), di.p + t.bargroupwidth / 2);
73042 };
73043 }
73044
73045 // the closest data point
73046 var index = pointData.index;
73047 var di = cd[index];
73048
73049 var size = (trace.base) ? di.b + di.s : di.s;
73050 pointData[sizeLetter + '0'] = pointData[sizeLetter + '1'] = sa.c2p(di[sizeLetter], true);
73051 pointData[sizeLetter + 'LabelVal'] = size;
73052
73053 var extent = t.extents[t.extents.round(di.p)];
73054 pointData[posLetter + '0'] = pa.c2p(isClosest ? minPos(di) : extent[0], true);
73055 pointData[posLetter + '1'] = pa.c2p(isClosest ? maxPos(di) : extent[1], true);
73056 pointData[posLetter + 'LabelVal'] = di.p;
73057
73058 pointData.labelLabel = hoverLabelText(pa, pointData[posLetter + 'LabelVal']);
73059 pointData.valueLabel = hoverLabelText(sa, pointData[sizeLetter + 'LabelVal']);
73060
73061 // spikelines always want "closest" distance regardless of hovermode
73062 pointData.spikeDistance = (sizeFn(di) + thisBarPositionFn(di)) / 2 - maxHoverDistance;
73063 // they also want to point to the data value, regardless of where the label goes
73064 // in case of bars shifted within groups
73065 pointData[posLetter + 'Spike'] = pa.c2p(di.p, true);
73066
73067 fillText(di, trace, pointData);
73068 pointData.hovertemplate = trace.hovertemplate;
73069
73070 return pointData;
73071}
73072
73073function getTraceColor(trace, di) {
73074 var mc = di.mcc || trace.marker.color;
73075 var mlc = di.mlcc || trace.marker.line.color;
73076 var mlw = getLineWidth(trace, di);
73077
73078 if(Color.opacity(mc)) return mc;
73079 else if(Color.opacity(mlc) && mlw) return mlc;
73080}
73081
73082module.exports = {
73083 hoverPoints: hoverPoints,
73084 hoverOnBars: hoverOnBars,
73085 getTraceColor: getTraceColor
73086};
73087
73088},{"../../components/color":52,"../../components/fx":92,"../../constants/numerical":158,"../../lib":178,"../../plots/cartesian/axes":222,"../../registry":269,"./helpers":285}],287:[function(_dereq_,module,exports){
73089/**
73090* Copyright 2012-2020, Plotly, Inc.
73091* All rights reserved.
73092*
73093* This source code is licensed under the MIT license found in the
73094* LICENSE file in the root directory of this source tree.
73095*/
73096
73097'use strict';
73098
73099module.exports = {
73100 attributes: _dereq_('./attributes'),
73101 layoutAttributes: _dereq_('./layout_attributes'),
73102 supplyDefaults: _dereq_('./defaults').supplyDefaults,
73103 crossTraceDefaults: _dereq_('./defaults').crossTraceDefaults,
73104 supplyLayoutDefaults: _dereq_('./layout_defaults'),
73105 calc: _dereq_('./calc'),
73106 crossTraceCalc: _dereq_('./cross_trace_calc').crossTraceCalc,
73107 colorbar: _dereq_('../scatter/marker_colorbar'),
73108 arraysToCalcdata: _dereq_('./arrays_to_calcdata'),
73109 plot: _dereq_('./plot').plot,
73110 style: _dereq_('./style').style,
73111 styleOnSelect: _dereq_('./style').styleOnSelect,
73112 hoverPoints: _dereq_('./hover').hoverPoints,
73113 eventData: _dereq_('./event_data'),
73114 selectPoints: _dereq_('./select'),
73115
73116 moduleType: 'trace',
73117 name: 'bar',
73118 basePlotModule: _dereq_('../../plots/cartesian'),
73119 categories: ['bar-like', 'cartesian', 'svg', 'bar', 'oriented', 'errorBarsOK', 'showLegend', 'zoomScale'],
73120 animatable: true,
73121 meta: {
73122
73123 }
73124};
73125
73126},{"../../plots/cartesian":235,"../scatter/marker_colorbar":407,"./arrays_to_calcdata":278,"./attributes":279,"./calc":280,"./cross_trace_calc":282,"./defaults":283,"./event_data":284,"./hover":286,"./layout_attributes":288,"./layout_defaults":289,"./plot":290,"./select":291,"./style":293}],288:[function(_dereq_,module,exports){
73127/**
73128* Copyright 2012-2020, Plotly, Inc.
73129* All rights reserved.
73130*
73131* This source code is licensed under the MIT license found in the
73132* LICENSE file in the root directory of this source tree.
73133*/
73134
73135'use strict';
73136
73137
73138module.exports = {
73139 barmode: {
73140 valType: 'enumerated',
73141 values: ['stack', 'group', 'overlay', 'relative'],
73142 dflt: 'group',
73143
73144 editType: 'calc',
73145
73146 },
73147 barnorm: {
73148 valType: 'enumerated',
73149 values: ['', 'fraction', 'percent'],
73150 dflt: '',
73151
73152 editType: 'calc',
73153
73154 },
73155 bargap: {
73156 valType: 'number',
73157 min: 0,
73158 max: 1,
73159
73160 editType: 'calc',
73161
73162 },
73163 bargroupgap: {
73164 valType: 'number',
73165 min: 0,
73166 max: 1,
73167 dflt: 0,
73168
73169 editType: 'calc',
73170
73171 }
73172};
73173
73174},{}],289:[function(_dereq_,module,exports){
73175/**
73176* Copyright 2012-2020, Plotly, Inc.
73177* All rights reserved.
73178*
73179* This source code is licensed under the MIT license found in the
73180* LICENSE file in the root directory of this source tree.
73181*/
73182
73183'use strict';
73184
73185var Registry = _dereq_('../../registry');
73186var Axes = _dereq_('../../plots/cartesian/axes');
73187var Lib = _dereq_('../../lib');
73188
73189var layoutAttributes = _dereq_('./layout_attributes');
73190
73191module.exports = function(layoutIn, layoutOut, fullData) {
73192 function coerce(attr, dflt) {
73193 return Lib.coerce(layoutIn, layoutOut, layoutAttributes, attr, dflt);
73194 }
73195
73196 var hasBars = false;
73197 var shouldBeGapless = false;
73198 var gappedAnyway = false;
73199 var usedSubplots = {};
73200
73201 var mode = coerce('barmode');
73202
73203 for(var i = 0; i < fullData.length; i++) {
73204 var trace = fullData[i];
73205 if(Registry.traceIs(trace, 'bar') && trace.visible) hasBars = true;
73206 else continue;
73207
73208 // if we have at least 2 grouped bar traces on the same subplot,
73209 // we should default to a gap anyway, even if the data is histograms
73210 if(mode === 'group') {
73211 var subploti = trace.xaxis + trace.yaxis;
73212 if(usedSubplots[subploti]) gappedAnyway = true;
73213 usedSubplots[subploti] = true;
73214 }
73215
73216 if(trace.visible && trace.type === 'histogram') {
73217 var pa = Axes.getFromId({_fullLayout: layoutOut},
73218 trace[trace.orientation === 'v' ? 'xaxis' : 'yaxis']);
73219 if(pa.type !== 'category') shouldBeGapless = true;
73220 }
73221 }
73222
73223 if(!hasBars) {
73224 delete layoutOut.barmode;
73225 return;
73226 }
73227
73228 if(mode !== 'overlay') coerce('barnorm');
73229
73230 coerce('bargap', (shouldBeGapless && !gappedAnyway) ? 0 : 0.2);
73231 coerce('bargroupgap');
73232};
73233
73234},{"../../lib":178,"../../plots/cartesian/axes":222,"../../registry":269,"./layout_attributes":288}],290:[function(_dereq_,module,exports){
73235/**
73236* Copyright 2012-2020, Plotly, Inc.
73237* All rights reserved.
73238*
73239* This source code is licensed under the MIT license found in the
73240* LICENSE file in the root directory of this source tree.
73241*/
73242
73243'use strict';
73244
73245var d3 = _dereq_('d3');
73246var isNumeric = _dereq_('fast-isnumeric');
73247
73248var Lib = _dereq_('../../lib');
73249var svgTextUtils = _dereq_('../../lib/svg_text_utils');
73250
73251var Color = _dereq_('../../components/color');
73252var Drawing = _dereq_('../../components/drawing');
73253var Registry = _dereq_('../../registry');
73254var tickText = _dereq_('../../plots/cartesian/axes').tickText;
73255
73256var uniformText = _dereq_('./uniform_text');
73257var recordMinTextSize = uniformText.recordMinTextSize;
73258var clearMinTextSize = uniformText.clearMinTextSize;
73259
73260var style = _dereq_('./style');
73261var helpers = _dereq_('./helpers');
73262var constants = _dereq_('./constants');
73263var attributes = _dereq_('./attributes');
73264
73265var attributeText = attributes.text;
73266var attributeTextPosition = attributes.textposition;
73267
73268var appendArrayPointValue = _dereq_('../../components/fx/helpers').appendArrayPointValue;
73269
73270var TEXTPAD = constants.TEXTPAD;
73271
73272function keyFunc(d) {return d.id;}
73273function getKeyFunc(trace) {
73274 if(trace.ids) {
73275 return keyFunc;
73276 }
73277}
73278
73279function dirSign(a, b) {
73280 return (a < b) ? 1 : -1;
73281}
73282
73283function getXY(di, xa, ya, isHorizontal) {
73284 var s = [];
73285 var p = [];
73286
73287 var sAxis = isHorizontal ? xa : ya;
73288 var pAxis = isHorizontal ? ya : xa;
73289
73290 s[0] = sAxis.c2p(di.s0, true);
73291 p[0] = pAxis.c2p(di.p0, true);
73292
73293 s[1] = sAxis.c2p(di.s1, true);
73294 p[1] = pAxis.c2p(di.p1, true);
73295
73296 return isHorizontal ? [s, p] : [p, s];
73297}
73298
73299function transition(selection, fullLayout, opts, makeOnCompleteCallback) {
73300 if(!fullLayout.uniformtext.mode && hasTransition(opts)) {
73301 var onComplete;
73302 if(makeOnCompleteCallback) {
73303 onComplete = makeOnCompleteCallback();
73304 }
73305 return selection
73306 .transition()
73307 .duration(opts.duration)
73308 .ease(opts.easing)
73309 .each('end', function() { onComplete && onComplete(); })
73310 .each('interrupt', function() { onComplete && onComplete(); });
73311 } else {
73312 return selection;
73313 }
73314}
73315
73316function hasTransition(transitionOpts) {
73317 return transitionOpts && transitionOpts.duration > 0;
73318}
73319
73320function plot(gd, plotinfo, cdModule, traceLayer, opts, makeOnCompleteCallback) {
73321 var xa = plotinfo.xaxis;
73322 var ya = plotinfo.yaxis;
73323 var fullLayout = gd._fullLayout;
73324
73325 if(!opts) {
73326 opts = {
73327 mode: fullLayout.barmode,
73328 norm: fullLayout.barmode,
73329 gap: fullLayout.bargap,
73330 groupgap: fullLayout.bargroupgap
73331 };
73332
73333 // don't clear bar when this is called from waterfall or funnel
73334 clearMinTextSize('bar', fullLayout);
73335 }
73336
73337 var bartraces = Lib.makeTraceGroups(traceLayer, cdModule, 'trace bars').each(function(cd) {
73338 var plotGroup = d3.select(this);
73339 var trace = cd[0].trace;
73340 var isWaterfall = (trace.type === 'waterfall');
73341 var isFunnel = (trace.type === 'funnel');
73342 var isBar = (trace.type === 'bar');
73343 var shouldDisplayZeros = (isBar || isFunnel);
73344
73345 var adjustPixel = 0;
73346 if(isWaterfall && trace.connector.visible && trace.connector.mode === 'between') {
73347 adjustPixel = trace.connector.line.width / 2;
73348 }
73349
73350 var isHorizontal = (trace.orientation === 'h');
73351 var withTransition = hasTransition(opts);
73352
73353 var pointGroup = Lib.ensureSingle(plotGroup, 'g', 'points');
73354
73355 var keyFunc = getKeyFunc(trace);
73356 var bars = pointGroup.selectAll('g.point').data(Lib.identity, keyFunc);
73357
73358 bars.enter().append('g')
73359 .classed('point', true);
73360
73361 bars.exit().remove();
73362
73363 bars.each(function(di, i) {
73364 var bar = d3.select(this);
73365
73366 // now display the bar
73367 // clipped xf/yf (2nd arg true): non-positive
73368 // log values go off-screen by plotwidth
73369 // so you see them continue if you drag the plot
73370 var xy = getXY(di, xa, ya, isHorizontal);
73371
73372 var x0 = xy[0][0];
73373 var x1 = xy[0][1];
73374 var y0 = xy[1][0];
73375 var y1 = xy[1][1];
73376
73377 // empty bars
73378 var isBlank = (isHorizontal ? x1 - x0 : y1 - y0) === 0;
73379
73380 // display zeros if line.width > 0
73381 if(isBlank && shouldDisplayZeros && helpers.getLineWidth(trace, di)) {
73382 isBlank = false;
73383 }
73384
73385 // skip nulls
73386 if(!isBlank) {
73387 isBlank = (
73388 !isNumeric(x0) ||
73389 !isNumeric(x1) ||
73390 !isNumeric(y0) ||
73391 !isNumeric(y1)
73392 );
73393 }
73394
73395 // record isBlank
73396 di.isBlank = isBlank;
73397
73398 // for blank bars, ensure start and end positions are equal - important for smooth transitions
73399 if(isBlank) {
73400 if(isHorizontal) {
73401 x1 = x0;
73402 } else {
73403 y1 = y0;
73404 }
73405 }
73406
73407 // in waterfall mode `between` we need to adjust bar end points to match the connector width
73408 if(adjustPixel && !isBlank) {
73409 if(isHorizontal) {
73410 x0 -= dirSign(x0, x1) * adjustPixel;
73411 x1 += dirSign(x0, x1) * adjustPixel;
73412 } else {
73413 y0 -= dirSign(y0, y1) * adjustPixel;
73414 y1 += dirSign(y0, y1) * adjustPixel;
73415 }
73416 }
73417
73418 var lw;
73419 var mc;
73420
73421 if(trace.type === 'waterfall') {
73422 if(!isBlank) {
73423 var cont = trace[di.dir].marker;
73424 lw = cont.line.width;
73425 mc = cont.color;
73426 }
73427 } else {
73428 lw = helpers.getLineWidth(trace, di);
73429 mc = di.mc || trace.marker.color;
73430 }
73431
73432 function roundWithLine(v) {
73433 var offset = d3.round((lw / 2) % 1, 2);
73434
73435 // if there are explicit gaps, don't round,
73436 // it can make the gaps look crappy
73437 return (opts.gap === 0 && opts.groupgap === 0) ?
73438 d3.round(Math.round(v) - offset, 2) : v;
73439 }
73440
73441 function expandToVisible(v, vc, hideZeroSpan) {
73442 if(hideZeroSpan && v === vc) {
73443 // should not expand zero span bars
73444 // when start and end positions are identical
73445 // i.e. for vertical when y0 === y1
73446 // and for horizontal when x0 === x1
73447 return v;
73448 }
73449
73450 // if it's not in danger of disappearing entirely,
73451 // round more precisely
73452 return Math.abs(v - vc) >= 2 ? roundWithLine(v) :
73453 // but if it's very thin, expand it so it's
73454 // necessarily visible, even if it might overlap
73455 // its neighbor
73456 (v > vc ? Math.ceil(v) : Math.floor(v));
73457 }
73458
73459 if(!gd._context.staticPlot) {
73460 // if bars are not fully opaque or they have a line
73461 // around them, round to integer pixels, mainly for
73462 // safari so we prevent overlaps from its expansive
73463 // pixelation. if the bars ARE fully opaque and have
73464 // no line, expand to a full pixel to make sure we
73465 // can see them
73466
73467 var op = Color.opacity(mc);
73468 var fixpx = (op < 1 || lw > 0.01) ? roundWithLine : expandToVisible;
73469
73470 x0 = fixpx(x0, x1, isHorizontal);
73471 x1 = fixpx(x1, x0, isHorizontal);
73472 y0 = fixpx(y0, y1, !isHorizontal);
73473 y1 = fixpx(y1, y0, !isHorizontal);
73474 }
73475
73476 var sel = transition(Lib.ensureSingle(bar, 'path'), fullLayout, opts, makeOnCompleteCallback);
73477 sel
73478 .style('vector-effect', 'non-scaling-stroke')
73479 .attr('d', isNaN((x1 - x0) * (y1 - y0)) ? 'M0,0Z' : 'M' + x0 + ',' + y0 + 'V' + y1 + 'H' + x1 + 'V' + y0 + 'Z')
73480 .call(Drawing.setClipUrl, plotinfo.layerClipId, gd);
73481
73482 if(!fullLayout.uniformtext.mode && withTransition) {
73483 var styleFns = Drawing.makePointStyleFns(trace);
73484 Drawing.singlePointStyle(di, sel, trace, styleFns, gd);
73485 }
73486
73487 appendBarText(gd, plotinfo, bar, cd, i, x0, x1, y0, y1, opts, makeOnCompleteCallback);
73488
73489 if(plotinfo.layerClipId) {
73490 Drawing.hideOutsideRangePoint(di, bar.select('text'), xa, ya, trace.xcalendar, trace.ycalendar);
73491 }
73492 });
73493
73494 // lastly, clip points groups of `cliponaxis !== false` traces
73495 // on `plotinfo._hasClipOnAxisFalse === true` subplots
73496 var hasClipOnAxisFalse = trace.cliponaxis === false;
73497 Drawing.setClipUrl(plotGroup, hasClipOnAxisFalse ? null : plotinfo.layerClipId, gd);
73498 });
73499
73500 // error bars are on the top
73501 Registry.getComponentMethod('errorbars', 'plot')(gd, bartraces, plotinfo, opts);
73502}
73503
73504function appendBarText(gd, plotinfo, bar, cd, i, x0, x1, y0, y1, opts, makeOnCompleteCallback) {
73505 var xa = plotinfo.xaxis;
73506 var ya = plotinfo.yaxis;
73507
73508 var fullLayout = gd._fullLayout;
73509 var textPosition;
73510
73511 function appendTextNode(bar, text, font) {
73512 var textSelection = Lib.ensureSingle(bar, 'text')
73513 .text(text)
73514 .attr({
73515 'class': 'bartext bartext-' + textPosition,
73516 'text-anchor': 'middle',
73517 // prohibit tex interpretation until we can handle
73518 // tex and regular text together
73519 'data-notex': 1
73520 })
73521 .call(Drawing.font, font)
73522 .call(svgTextUtils.convertToTspans, gd);
73523
73524 return textSelection;
73525 }
73526
73527 // get trace attributes
73528 var trace = cd[0].trace;
73529 var isHorizontal = (trace.orientation === 'h');
73530
73531 var text = getText(fullLayout, cd, i, xa, ya);
73532 textPosition = getTextPosition(trace, i);
73533
73534 // compute text position
73535 var inStackOrRelativeMode =
73536 opts.mode === 'stack' ||
73537 opts.mode === 'relative';
73538
73539 var calcBar = cd[i];
73540 var isOutmostBar = !inStackOrRelativeMode || calcBar._outmost;
73541
73542 if(!text ||
73543 textPosition === 'none' ||
73544 ((calcBar.isBlank || x0 === x1 || y0 === y1) && (
73545 textPosition === 'auto' ||
73546 textPosition === 'inside'))) {
73547 bar.select('text').remove();
73548 return;
73549 }
73550
73551 var layoutFont = fullLayout.font;
73552 var barColor = style.getBarColor(cd[i], trace);
73553 var insideTextFont = style.getInsideTextFont(trace, i, layoutFont, barColor);
73554 var outsideTextFont = style.getOutsideTextFont(trace, i, layoutFont);
73555
73556 // Special case: don't use the c2p(v, true) value on log size axes,
73557 // so that we can get correctly inside text scaling
73558 var di = bar.datum();
73559 if(isHorizontal) {
73560 if(xa.type === 'log' && di.s0 <= 0) {
73561 if(xa.range[0] < xa.range[1]) {
73562 x0 = 0;
73563 } else {
73564 x0 = xa._length;
73565 }
73566 }
73567 } else {
73568 if(ya.type === 'log' && di.s0 <= 0) {
73569 if(ya.range[0] < ya.range[1]) {
73570 y0 = ya._length;
73571 } else {
73572 y0 = 0;
73573 }
73574 }
73575 }
73576
73577 // padding excluded
73578 var barWidth = Math.abs(x1 - x0) - 2 * TEXTPAD;
73579 var barHeight = Math.abs(y1 - y0) - 2 * TEXTPAD;
73580
73581 var textSelection;
73582 var textBB;
73583 var textWidth;
73584 var textHeight;
73585 var font;
73586
73587 if(textPosition === 'outside') {
73588 if(!isOutmostBar && !calcBar.hasB) textPosition = 'inside';
73589 }
73590
73591 if(textPosition === 'auto') {
73592 if(isOutmostBar) {
73593 // draw text using insideTextFont and check if it fits inside bar
73594 textPosition = 'inside';
73595
73596 font = Lib.ensureUniformFontSize(gd, insideTextFont);
73597
73598 textSelection = appendTextNode(bar, text, font);
73599
73600 textBB = Drawing.bBox(textSelection.node()),
73601 textWidth = textBB.width,
73602 textHeight = textBB.height;
73603
73604 var textHasSize = (textWidth > 0 && textHeight > 0);
73605 var fitsInside = (textWidth <= barWidth && textHeight <= barHeight);
73606 var fitsInsideIfRotated = (textWidth <= barHeight && textHeight <= barWidth);
73607 var fitsInsideIfShrunk = (isHorizontal) ?
73608 (barWidth >= textWidth * (barHeight / textHeight)) :
73609 (barHeight >= textHeight * (barWidth / textWidth));
73610
73611 if(textHasSize && (
73612 fitsInside ||
73613 fitsInsideIfRotated ||
73614 fitsInsideIfShrunk)
73615 ) {
73616 textPosition = 'inside';
73617 } else {
73618 textPosition = 'outside';
73619 textSelection.remove();
73620 textSelection = null;
73621 }
73622 } else {
73623 textPosition = 'inside';
73624 }
73625 }
73626
73627 if(!textSelection) {
73628 font = Lib.ensureUniformFontSize(gd, (textPosition === 'outside') ? outsideTextFont : insideTextFont);
73629
73630 textSelection = appendTextNode(bar, text, font);
73631
73632 var currentTransform = textSelection.attr('transform');
73633 textSelection.attr('transform', '');
73634 textBB = Drawing.bBox(textSelection.node()),
73635 textWidth = textBB.width,
73636 textHeight = textBB.height;
73637 textSelection.attr('transform', currentTransform);
73638
73639 if(textWidth <= 0 || textHeight <= 0) {
73640 textSelection.remove();
73641 return;
73642 }
73643 }
73644
73645 var angle = trace.textangle;
73646
73647 // compute text transform
73648 var transform, constrained;
73649 if(textPosition === 'outside') {
73650 constrained =
73651 trace.constraintext === 'both' ||
73652 trace.constraintext === 'outside';
73653
73654 transform = toMoveOutsideBar(x0, x1, y0, y1, textBB, {
73655 isHorizontal: isHorizontal,
73656 constrained: constrained,
73657 angle: angle
73658 });
73659 } else {
73660 constrained =
73661 trace.constraintext === 'both' ||
73662 trace.constraintext === 'inside';
73663
73664 transform = toMoveInsideBar(x0, x1, y0, y1, textBB, {
73665 isHorizontal: isHorizontal,
73666 constrained: constrained,
73667 angle: angle,
73668 anchor: trace.insidetextanchor
73669 });
73670 }
73671
73672 transform.fontSize = font.size;
73673 recordMinTextSize(trace.type, transform, fullLayout);
73674 calcBar.transform = transform;
73675
73676 transition(textSelection, fullLayout, opts, makeOnCompleteCallback)
73677 .attr('transform', Lib.getTextTransform(transform));
73678}
73679
73680function getRotateFromAngle(angle) {
73681 return (angle === 'auto') ? 0 : angle;
73682}
73683
73684function getRotatedTextSize(textBB, rotate) {
73685 var a = Math.PI / 180 * rotate;
73686 var absSin = Math.abs(Math.sin(a));
73687 var absCos = Math.abs(Math.cos(a));
73688
73689 return {
73690 x: textBB.width * absCos + textBB.height * absSin,
73691 y: textBB.width * absSin + textBB.height * absCos
73692 };
73693}
73694
73695function toMoveInsideBar(x0, x1, y0, y1, textBB, opts) {
73696 var isHorizontal = !!opts.isHorizontal;
73697 var constrained = !!opts.constrained;
73698 var angle = opts.angle || 0;
73699 var anchor = opts.anchor || 'end';
73700 var isEnd = anchor === 'end';
73701 var isStart = anchor === 'start';
73702 var leftToRight = opts.leftToRight || 0; // left: -1, center: 0, right: 1
73703 var toRight = (leftToRight + 1) / 2;
73704 var toLeft = 1 - toRight;
73705
73706 var textWidth = textBB.width;
73707 var textHeight = textBB.height;
73708 var lx = Math.abs(x1 - x0);
73709 var ly = Math.abs(y1 - y0);
73710
73711 // compute remaining space
73712 var textpad = (
73713 lx > (2 * TEXTPAD) &&
73714 ly > (2 * TEXTPAD)
73715 ) ? TEXTPAD : 0;
73716
73717 lx -= 2 * textpad;
73718 ly -= 2 * textpad;
73719
73720 var rotate = getRotateFromAngle(angle);
73721 if((angle === 'auto') &&
73722 !(textWidth <= lx && textHeight <= ly) &&
73723 (textWidth > lx || textHeight > ly) && (
73724 !(textWidth > ly || textHeight > lx) ||
73725 ((textWidth < textHeight) !== (lx < ly))
73726 )) {
73727 rotate += 90;
73728 }
73729
73730 var t = getRotatedTextSize(textBB, rotate);
73731
73732 var scale = 1;
73733 if(constrained) {
73734 scale = Math.min(
73735 1,
73736 lx / t.x,
73737 ly / t.y
73738 );
73739 }
73740
73741 // compute text and target positions
73742 var textX = (
73743 textBB.left * toLeft +
73744 textBB.right * toRight
73745 );
73746 var textY = (textBB.top + textBB.bottom) / 2;
73747 var targetX = (
73748 (x0 + TEXTPAD) * toLeft +
73749 (x1 - TEXTPAD) * toRight
73750 );
73751 var targetY = (y0 + y1) / 2;
73752 var anchorX = 0;
73753 var anchorY = 0;
73754 if(isStart || isEnd) {
73755 var extrapad = (isHorizontal ? t.x : t.y) / 2;
73756 var dir = isHorizontal ? dirSign(x0, x1) : dirSign(y0, y1);
73757
73758 if(isHorizontal) {
73759 if(isStart) {
73760 targetX = x0 + dir * textpad;
73761 anchorX = -dir * extrapad;
73762 } else {
73763 targetX = x1 - dir * textpad;
73764 anchorX = dir * extrapad;
73765 }
73766 } else {
73767 if(isStart) {
73768 targetY = y0 + dir * textpad;
73769 anchorY = -dir * extrapad;
73770 } else {
73771 targetY = y1 - dir * textpad;
73772 anchorY = dir * extrapad;
73773 }
73774 }
73775 }
73776
73777 return {
73778 textX: textX,
73779 textY: textY,
73780 targetX: targetX,
73781 targetY: targetY,
73782 anchorX: anchorX,
73783 anchorY: anchorY,
73784 scale: scale,
73785 rotate: rotate
73786 };
73787}
73788
73789function toMoveOutsideBar(x0, x1, y0, y1, textBB, opts) {
73790 var isHorizontal = !!opts.isHorizontal;
73791 var constrained = !!opts.constrained;
73792 var angle = opts.angle || 0;
73793
73794 var textWidth = textBB.width;
73795 var textHeight = textBB.height;
73796 var lx = Math.abs(x1 - x0);
73797 var ly = Math.abs(y1 - y0);
73798
73799 var textpad;
73800 // Keep the padding so the text doesn't sit right against
73801 // the bars, but don't factor it into barWidth
73802 if(isHorizontal) {
73803 textpad = (ly > 2 * TEXTPAD) ? TEXTPAD : 0;
73804 } else {
73805 textpad = (lx > 2 * TEXTPAD) ? TEXTPAD : 0;
73806 }
73807
73808 // compute rotate and scale
73809 var scale = 1;
73810 if(constrained) {
73811 scale = (isHorizontal) ?
73812 Math.min(1, ly / textHeight) :
73813 Math.min(1, lx / textWidth);
73814 }
73815
73816 var rotate = getRotateFromAngle(angle);
73817 var t = getRotatedTextSize(textBB, rotate);
73818
73819 // compute text and target positions
73820 var extrapad = (isHorizontal ? t.x : t.y) / 2;
73821 var textX = (textBB.left + textBB.right) / 2;
73822 var textY = (textBB.top + textBB.bottom) / 2;
73823 var targetX = (x0 + x1) / 2;
73824 var targetY = (y0 + y1) / 2;
73825 var anchorX = 0;
73826 var anchorY = 0;
73827
73828 var dir = isHorizontal ? dirSign(x1, x0) : dirSign(y0, y1);
73829 if(isHorizontal) {
73830 targetX = x1 - dir * textpad;
73831 anchorX = dir * extrapad;
73832 } else {
73833 targetY = y1 + dir * textpad;
73834 anchorY = -dir * extrapad;
73835 }
73836
73837 return {
73838 textX: textX,
73839 textY: textY,
73840 targetX: targetX,
73841 targetY: targetY,
73842 anchorX: anchorX,
73843 anchorY: anchorY,
73844 scale: scale,
73845 rotate: rotate
73846 };
73847}
73848
73849function getText(fullLayout, cd, index, xa, ya) {
73850 var trace = cd[0].trace;
73851 var texttemplate = trace.texttemplate;
73852
73853 var value;
73854 if(texttemplate) {
73855 value = calcTexttemplate(fullLayout, cd, index, xa, ya);
73856 } else if(trace.textinfo) {
73857 value = calcTextinfo(cd, index, xa, ya);
73858 } else {
73859 value = helpers.getValue(trace.text, index);
73860 }
73861
73862 return helpers.coerceString(attributeText, value);
73863}
73864
73865function getTextPosition(trace, index) {
73866 var value = helpers.getValue(trace.textposition, index);
73867 return helpers.coerceEnumerated(attributeTextPosition, value);
73868}
73869
73870function calcTexttemplate(fullLayout, cd, index, xa, ya) {
73871 var trace = cd[0].trace;
73872 var texttemplate = Lib.castOption(trace, index, 'texttemplate');
73873 if(!texttemplate) return '';
73874 var isWaterfall = (trace.type === 'waterfall');
73875 var isFunnel = (trace.type === 'funnel');
73876
73877 var pLetter, pAxis;
73878 var vLetter, vAxis;
73879 if(trace.orientation === 'h') {
73880 pLetter = 'y';
73881 pAxis = ya;
73882 vLetter = 'x';
73883 vAxis = xa;
73884 } else {
73885 pLetter = 'x';
73886 pAxis = xa;
73887 vLetter = 'y';
73888 vAxis = ya;
73889 }
73890
73891 function formatLabel(u) {
73892 return tickText(pAxis, u, true).text;
73893 }
73894
73895 function formatNumber(v) {
73896 return tickText(vAxis, +v, true).text;
73897 }
73898
73899 var cdi = cd[index];
73900 var obj = {};
73901
73902 obj.label = cdi.p;
73903 obj.labelLabel = obj[pLetter + 'Label'] = formatLabel(cdi.p);
73904
73905 var tx = Lib.castOption(trace, cdi.i, 'text');
73906 if(tx === 0 || tx) obj.text = tx;
73907
73908 obj.value = cdi.s;
73909 obj.valueLabel = obj[vLetter + 'Label'] = formatNumber(cdi.s);
73910
73911 var pt = {};
73912 appendArrayPointValue(pt, trace, cdi.i);
73913
73914 if(isWaterfall) {
73915 obj.delta = +cdi.rawS || cdi.s;
73916 obj.deltaLabel = formatNumber(obj.delta);
73917 obj.final = cdi.v;
73918 obj.finalLabel = formatNumber(obj.final);
73919 obj.initial = obj.final - obj.delta;
73920 obj.initialLabel = formatNumber(obj.initial);
73921 }
73922
73923 if(isFunnel) {
73924 obj.value = cdi.s;
73925 obj.valueLabel = formatNumber(obj.value);
73926
73927 obj.percentInitial = cdi.begR;
73928 obj.percentInitialLabel = Lib.formatPercent(cdi.begR);
73929 obj.percentPrevious = cdi.difR;
73930 obj.percentPreviousLabel = Lib.formatPercent(cdi.difR);
73931 obj.percentTotal = cdi.sumR;
73932 obj.percenTotalLabel = Lib.formatPercent(cdi.sumR);
73933 }
73934
73935 var customdata = Lib.castOption(trace, cdi.i, 'customdata');
73936 if(customdata) obj.customdata = customdata;
73937 return Lib.texttemplateString(texttemplate, obj, fullLayout._d3locale, pt, obj, trace._meta || {});
73938}
73939
73940function calcTextinfo(cd, index, xa, ya) {
73941 var trace = cd[0].trace;
73942 var isHorizontal = (trace.orientation === 'h');
73943 var isWaterfall = (trace.type === 'waterfall');
73944 var isFunnel = (trace.type === 'funnel');
73945
73946 function formatLabel(u) {
73947 var pAxis = isHorizontal ? ya : xa;
73948 return tickText(pAxis, u, true).text;
73949 }
73950
73951 function formatNumber(v) {
73952 var sAxis = isHorizontal ? xa : ya;
73953 return tickText(sAxis, +v, true).text;
73954 }
73955
73956 var textinfo = trace.textinfo;
73957 var cdi = cd[index];
73958
73959 var parts = textinfo.split('+');
73960 var text = [];
73961 var tx;
73962
73963 var hasFlag = function(flag) { return parts.indexOf(flag) !== -1; };
73964
73965 if(hasFlag('label')) {
73966 text.push(formatLabel(cd[index].p));
73967 }
73968
73969 if(hasFlag('text')) {
73970 tx = Lib.castOption(trace, cdi.i, 'text');
73971 if(tx === 0 || tx) text.push(tx);
73972 }
73973
73974 if(isWaterfall) {
73975 var delta = +cdi.rawS || cdi.s;
73976 var final = cdi.v;
73977 var initial = final - delta;
73978
73979 if(hasFlag('initial')) text.push(formatNumber(initial));
73980 if(hasFlag('delta')) text.push(formatNumber(delta));
73981 if(hasFlag('final')) text.push(formatNumber(final));
73982 }
73983
73984 if(isFunnel) {
73985 if(hasFlag('value')) text.push(formatNumber(cdi.s));
73986
73987 var nPercent = 0;
73988 if(hasFlag('percent initial')) nPercent++;
73989 if(hasFlag('percent previous')) nPercent++;
73990 if(hasFlag('percent total')) nPercent++;
73991
73992 var hasMultiplePercents = nPercent > 1;
73993
73994 if(hasFlag('percent initial')) {
73995 tx = Lib.formatPercent(cdi.begR);
73996 if(hasMultiplePercents) tx += ' of initial';
73997 text.push(tx);
73998 }
73999 if(hasFlag('percent previous')) {
74000 tx = Lib.formatPercent(cdi.difR);
74001 if(hasMultiplePercents) tx += ' of previous';
74002 text.push(tx);
74003 }
74004 if(hasFlag('percent total')) {
74005 tx = Lib.formatPercent(cdi.sumR);
74006 if(hasMultiplePercents) tx += ' of total';
74007 text.push(tx);
74008 }
74009 }
74010
74011 return text.join('<br>');
74012}
74013
74014module.exports = {
74015 plot: plot,
74016 toMoveInsideBar: toMoveInsideBar
74017};
74018
74019},{"../../components/color":52,"../../components/drawing":74,"../../components/fx/helpers":88,"../../lib":178,"../../lib/svg_text_utils":199,"../../plots/cartesian/axes":222,"../../registry":269,"./attributes":279,"./constants":281,"./helpers":285,"./style":293,"./uniform_text":295,"d3":16,"fast-isnumeric":18}],291:[function(_dereq_,module,exports){
74020/**
74021* Copyright 2012-2020, Plotly, Inc.
74022* All rights reserved.
74023*
74024* This source code is licensed under the MIT license found in the
74025* LICENSE file in the root directory of this source tree.
74026*/
74027
74028'use strict';
74029
74030module.exports = function selectPoints(searchInfo, selectionTester) {
74031 var cd = searchInfo.cd;
74032 var xa = searchInfo.xaxis;
74033 var ya = searchInfo.yaxis;
74034 var trace = cd[0].trace;
74035 var isFunnel = (trace.type === 'funnel');
74036 var isHorizontal = (trace.orientation === 'h');
74037 var selection = [];
74038 var i;
74039
74040 if(selectionTester === false) {
74041 // clear selection
74042 for(i = 0; i < cd.length; i++) {
74043 cd[i].selected = 0;
74044 }
74045 } else {
74046 for(i = 0; i < cd.length; i++) {
74047 var di = cd[i];
74048 var ct = 'ct' in di ? di.ct : getCentroid(di, xa, ya, isHorizontal, isFunnel);
74049
74050 if(selectionTester.contains(ct, false, i, searchInfo)) {
74051 selection.push({
74052 pointNumber: i,
74053 x: xa.c2d(di.x),
74054 y: ya.c2d(di.y)
74055 });
74056 di.selected = 1;
74057 } else {
74058 di.selected = 0;
74059 }
74060 }
74061 }
74062
74063 return selection;
74064};
74065
74066function getCentroid(d, xa, ya, isHorizontal, isFunnel) {
74067 var x0 = xa.c2p(isHorizontal ? d.s0 : d.p0, true);
74068 var x1 = xa.c2p(isHorizontal ? d.s1 : d.p1, true);
74069 var y0 = ya.c2p(isHorizontal ? d.p0 : d.s0, true);
74070 var y1 = ya.c2p(isHorizontal ? d.p1 : d.s1, true);
74071
74072 if(isFunnel) {
74073 return [(x0 + x1) / 2, (y0 + y1) / 2];
74074 } else {
74075 if(isHorizontal) {
74076 return [x1, (y0 + y1) / 2];
74077 } else {
74078 return [(x0 + x1) / 2, y1];
74079 }
74080 }
74081}
74082
74083},{}],292:[function(_dereq_,module,exports){
74084/**
74085* Copyright 2012-2020, Plotly, Inc.
74086* All rights reserved.
74087*
74088* This source code is licensed under the MIT license found in the
74089* LICENSE file in the root directory of this source tree.
74090*/
74091
74092'use strict';
74093
74094module.exports = Sieve;
74095
74096var distinctVals = _dereq_('../../lib').distinctVals;
74097var BADNUM = _dereq_('../../constants/numerical').BADNUM;
74098
74099/**
74100 * Helper class to sieve data from traces into bins
74101 *
74102 * @class
74103 *
74104 * @param {Array} traces
74105* Array of calculated traces
74106 * @param {object} opts
74107 * - @param {boolean} [sepNegVal]
74108 * If true, then split data at the same position into a bar
74109 * for positive values and another for negative values
74110 * - @param {boolean} [overlapNoMerge]
74111 * If true, then don't merge overlapping bars into a single bar
74112 */
74113function Sieve(traces, opts) {
74114 this.traces = traces;
74115 this.sepNegVal = opts.sepNegVal;
74116 this.overlapNoMerge = opts.overlapNoMerge;
74117
74118 // for single-bin histograms - see histogram/calc
74119 var width1 = Infinity;
74120
74121 var positions = [];
74122 for(var i = 0; i < traces.length; i++) {
74123 var trace = traces[i];
74124 for(var j = 0; j < trace.length; j++) {
74125 var bar = trace[j];
74126 if(bar.p !== BADNUM) positions.push(bar.p);
74127 }
74128 if(trace[0] && trace[0].width1) {
74129 width1 = Math.min(trace[0].width1, width1);
74130 }
74131 }
74132 this.positions = positions;
74133
74134 var dv = distinctVals(positions);
74135 this.distinctPositions = dv.vals;
74136 if(dv.vals.length === 1 && width1 !== Infinity) this.minDiff = width1;
74137 else this.minDiff = Math.min(dv.minDiff, width1);
74138
74139 this.binWidth = this.minDiff;
74140
74141 this.bins = {};
74142}
74143
74144/**
74145 * Sieve datum
74146 *
74147 * @method
74148 * @param {number} position
74149 * @param {number} value
74150 * @returns {number} Previous bin value
74151 */
74152Sieve.prototype.put = function put(position, value) {
74153 var label = this.getLabel(position, value);
74154 var oldValue = this.bins[label] || 0;
74155
74156 this.bins[label] = oldValue + value;
74157
74158 return oldValue;
74159};
74160
74161/**
74162 * Get current bin value for a given datum
74163 *
74164 * @method
74165 * @param {number} position Position of datum
74166 * @param {number} [value] Value of datum
74167 * (required if this.sepNegVal is true)
74168 * @returns {number} Current bin value
74169 */
74170Sieve.prototype.get = function get(position, value) {
74171 var label = this.getLabel(position, value);
74172 return this.bins[label] || 0;
74173};
74174
74175/**
74176 * Get bin label for a given datum
74177 *
74178 * @method
74179 * @param {number} position Position of datum
74180 * @param {number} [value] Value of datum
74181 * (required if this.sepNegVal is true)
74182 * @returns {string} Bin label
74183 * (prefixed with a 'v' if value is negative and this.sepNegVal is
74184 * true; otherwise prefixed with '^')
74185 */
74186Sieve.prototype.getLabel = function getLabel(position, value) {
74187 var prefix = (value < 0 && this.sepNegVal) ? 'v' : '^';
74188 var label = (this.overlapNoMerge) ?
74189 position :
74190 Math.round(position / this.binWidth);
74191 return prefix + label;
74192};
74193
74194},{"../../constants/numerical":158,"../../lib":178}],293:[function(_dereq_,module,exports){
74195/**
74196* Copyright 2012-2020, Plotly, Inc.
74197* All rights reserved.
74198*
74199* This source code is licensed under the MIT license found in the
74200* LICENSE file in the root directory of this source tree.
74201*/
74202
74203'use strict';
74204
74205var d3 = _dereq_('d3');
74206var Color = _dereq_('../../components/color');
74207var Drawing = _dereq_('../../components/drawing');
74208var Lib = _dereq_('../../lib');
74209var Registry = _dereq_('../../registry');
74210
74211var resizeText = _dereq_('./uniform_text').resizeText;
74212var attributes = _dereq_('./attributes');
74213var attributeTextFont = attributes.textfont;
74214var attributeInsideTextFont = attributes.insidetextfont;
74215var attributeOutsideTextFont = attributes.outsidetextfont;
74216var helpers = _dereq_('./helpers');
74217
74218function style(gd) {
74219 var s = d3.select(gd).selectAll('g.barlayer').selectAll('g.trace');
74220 resizeText(gd, s, 'bar');
74221
74222 var barcount = s.size();
74223 var fullLayout = gd._fullLayout;
74224
74225 // trace styling
74226 s.style('opacity', function(d) { return d[0].trace.opacity; })
74227
74228 // for gapless (either stacked or neighboring grouped) bars use
74229 // crispEdges to turn off antialiasing so an artificial gap
74230 // isn't introduced.
74231 .each(function(d) {
74232 if((fullLayout.barmode === 'stack' && barcount > 1) ||
74233 (fullLayout.bargap === 0 &&
74234 fullLayout.bargroupgap === 0 &&
74235 !d[0].trace.marker.line.width)) {
74236 d3.select(this).attr('shape-rendering', 'crispEdges');
74237 }
74238 });
74239
74240 s.selectAll('g.points').each(function(d) {
74241 var sel = d3.select(this);
74242 var trace = d[0].trace;
74243 stylePoints(sel, trace, gd);
74244 });
74245
74246 Registry.getComponentMethod('errorbars', 'style')(s);
74247}
74248
74249function stylePoints(sel, trace, gd) {
74250 Drawing.pointStyle(sel.selectAll('path'), trace, gd);
74251 styleTextPoints(sel, trace, gd);
74252}
74253
74254function styleTextPoints(sel, trace, gd) {
74255 sel.selectAll('text').each(function(d) {
74256 var tx = d3.select(this);
74257 var font = Lib.ensureUniformFontSize(gd, determineFont(tx, d, trace, gd));
74258
74259 Drawing.font(tx, font);
74260 });
74261}
74262
74263function styleOnSelect(gd, cd, sel) {
74264 var trace = cd[0].trace;
74265
74266 if(trace.selectedpoints) {
74267 stylePointsInSelectionMode(sel, trace, gd);
74268 } else {
74269 stylePoints(sel, trace, gd);
74270 Registry.getComponentMethod('errorbars', 'style')(sel);
74271 }
74272}
74273
74274function stylePointsInSelectionMode(s, trace, gd) {
74275 Drawing.selectedPointStyle(s.selectAll('path'), trace);
74276 styleTextInSelectionMode(s.selectAll('text'), trace, gd);
74277}
74278
74279function styleTextInSelectionMode(txs, trace, gd) {
74280 txs.each(function(d) {
74281 var tx = d3.select(this);
74282 var font;
74283
74284 if(d.selected) {
74285 font = Lib.ensureUniformFontSize(gd, determineFont(tx, d, trace, gd));
74286
74287 var selectedFontColor = trace.selected.textfont && trace.selected.textfont.color;
74288 if(selectedFontColor) {
74289 font.color = selectedFontColor;
74290 }
74291
74292 Drawing.font(tx, font);
74293 } else {
74294 Drawing.selectedTextStyle(tx, trace);
74295 }
74296 });
74297}
74298
74299function determineFont(tx, d, trace, gd) {
74300 var layoutFont = gd._fullLayout.font;
74301 var textFont = trace.textfont;
74302
74303 if(tx.classed('bartext-inside')) {
74304 var barColor = getBarColor(d, trace);
74305 textFont = getInsideTextFont(trace, d.i, layoutFont, barColor);
74306 } else if(tx.classed('bartext-outside')) {
74307 textFont = getOutsideTextFont(trace, d.i, layoutFont);
74308 }
74309
74310 return textFont;
74311}
74312
74313function getTextFont(trace, index, defaultValue) {
74314 return getFontValue(
74315 attributeTextFont, trace.textfont, index, defaultValue);
74316}
74317
74318function getInsideTextFont(trace, index, layoutFont, barColor) {
74319 var defaultFont = getTextFont(trace, index, layoutFont);
74320
74321 var wouldFallBackToLayoutFont =
74322 (trace._input.textfont === undefined || trace._input.textfont.color === undefined) ||
74323 (Array.isArray(trace.textfont.color) && trace.textfont.color[index] === undefined);
74324 if(wouldFallBackToLayoutFont) {
74325 defaultFont = {
74326 color: Color.contrast(barColor),
74327 family: defaultFont.family,
74328 size: defaultFont.size
74329 };
74330 }
74331
74332 return getFontValue(
74333 attributeInsideTextFont, trace.insidetextfont, index, defaultFont);
74334}
74335
74336function getOutsideTextFont(trace, index, layoutFont) {
74337 var defaultFont = getTextFont(trace, index, layoutFont);
74338 return getFontValue(
74339 attributeOutsideTextFont, trace.outsidetextfont, index, defaultFont);
74340}
74341
74342function getFontValue(attributeDefinition, attributeValue, index, defaultValue) {
74343 attributeValue = attributeValue || {};
74344
74345 var familyValue = helpers.getValue(attributeValue.family, index);
74346 var sizeValue = helpers.getValue(attributeValue.size, index);
74347 var colorValue = helpers.getValue(attributeValue.color, index);
74348
74349 return {
74350 family: helpers.coerceString(
74351 attributeDefinition.family, familyValue, defaultValue.family),
74352 size: helpers.coerceNumber(
74353 attributeDefinition.size, sizeValue, defaultValue.size),
74354 color: helpers.coerceColor(
74355 attributeDefinition.color, colorValue, defaultValue.color)
74356 };
74357}
74358
74359function getBarColor(cd, trace) {
74360 if(trace.type === 'waterfall') {
74361 return trace[cd.dir].marker.color;
74362 }
74363 return cd.mc || trace.marker.color;
74364}
74365
74366module.exports = {
74367 style: style,
74368 styleTextPoints: styleTextPoints,
74369 styleOnSelect: styleOnSelect,
74370 getInsideTextFont: getInsideTextFont,
74371 getOutsideTextFont: getOutsideTextFont,
74372 getBarColor: getBarColor,
74373 resizeText: resizeText
74374};
74375
74376},{"../../components/color":52,"../../components/drawing":74,"../../lib":178,"../../registry":269,"./attributes":279,"./helpers":285,"./uniform_text":295,"d3":16}],294:[function(_dereq_,module,exports){
74377/**
74378* Copyright 2012-2020, Plotly, Inc.
74379* All rights reserved.
74380*
74381* This source code is licensed under the MIT license found in the
74382* LICENSE file in the root directory of this source tree.
74383*/
74384
74385'use strict';
74386
74387var Color = _dereq_('../../components/color');
74388var hasColorscale = _dereq_('../../components/colorscale/helpers').hasColorscale;
74389var colorscaleDefaults = _dereq_('../../components/colorscale/defaults');
74390
74391module.exports = function handleStyleDefaults(traceIn, traceOut, coerce, defaultColor, layout) {
74392 coerce('marker.color', defaultColor);
74393
74394 if(hasColorscale(traceIn, 'marker')) {
74395 colorscaleDefaults(
74396 traceIn, traceOut, layout, coerce, {prefix: 'marker.', cLetter: 'c'}
74397 );
74398 }
74399
74400 coerce('marker.line.color', Color.defaultLine);
74401
74402 if(hasColorscale(traceIn, 'marker.line')) {
74403 colorscaleDefaults(
74404 traceIn, traceOut, layout, coerce, {prefix: 'marker.line.', cLetter: 'c'}
74405 );
74406 }
74407
74408 coerce('marker.line.width');
74409 coerce('marker.opacity');
74410 coerce('selected.marker.color');
74411 coerce('unselected.marker.color');
74412};
74413
74414},{"../../components/color":52,"../../components/colorscale/defaults":62,"../../components/colorscale/helpers":63}],295:[function(_dereq_,module,exports){
74415/**
74416* Copyright 2012-2020, Plotly, Inc.
74417* All rights reserved.
74418*
74419* This source code is licensed under the MIT license found in the
74420* LICENSE file in the root directory of this source tree.
74421*/
74422
74423'use strict';
74424
74425var d3 = _dereq_('d3');
74426var Lib = _dereq_('../../lib');
74427
74428function resizeText(gd, gTrace, traceType) {
74429 var fullLayout = gd._fullLayout;
74430 var minSize = fullLayout['_' + traceType + 'Text_minsize'];
74431 if(minSize) {
74432 var shouldHide = fullLayout.uniformtext.mode === 'hide';
74433
74434 var selector;
74435 switch(traceType) {
74436 case 'funnelarea' :
74437 case 'pie' :
74438 case 'sunburst' :
74439 selector = 'g.slice';
74440 break;
74441 case 'treemap' :
74442 selector = 'g.slice, g.pathbar';
74443 break;
74444 default :
74445 selector = 'g.points > g.point';
74446 }
74447
74448 gTrace.selectAll(selector).each(function(d) {
74449 var transform = d.transform;
74450 if(transform) {
74451 transform.scale = (shouldHide && transform.hide) ? 0 : minSize / transform.fontSize;
74452
74453 var el = d3.select(this).select('text');
74454 el.attr('transform', Lib.getTextTransform(transform));
74455 }
74456 });
74457 }
74458}
74459
74460function recordMinTextSize(
74461 traceType, // in
74462 transform, // inout
74463 fullLayout // inout
74464) {
74465 if(fullLayout.uniformtext.mode) {
74466 var minKey = getMinKey(traceType);
74467 var minSize = fullLayout.uniformtext.minsize;
74468 var size = transform.scale * transform.fontSize;
74469
74470 transform.hide = size < minSize;
74471
74472 fullLayout[minKey] = fullLayout[minKey] || Infinity;
74473 if(!transform.hide) {
74474 fullLayout[minKey] = Math.min(
74475 fullLayout[minKey],
74476 Math.max(size, minSize)
74477 );
74478 }
74479 }
74480}
74481
74482function clearMinTextSize(
74483 traceType, // in
74484 fullLayout // inout
74485) {
74486 var minKey = getMinKey(traceType);
74487 fullLayout[minKey] = undefined;
74488}
74489
74490function getMinKey(traceType) {
74491 return '_' + traceType + 'Text_minsize';
74492}
74493
74494module.exports = {
74495 recordMinTextSize: recordMinTextSize,
74496 clearMinTextSize: clearMinTextSize,
74497 resizeText: resizeText
74498};
74499
74500},{"../../lib":178,"d3":16}],296:[function(_dereq_,module,exports){
74501/**
74502* Copyright 2012-2020, Plotly, Inc.
74503* All rights reserved.
74504*
74505* This source code is licensed under the MIT license found in the
74506* LICENSE file in the root directory of this source tree.
74507*/
74508
74509'use strict';
74510
74511var scatterAttrs = _dereq_('../scatter/attributes');
74512var barAttrs = _dereq_('../bar/attributes');
74513var colorAttrs = _dereq_('../../components/color/attributes');
74514var hovertemplateAttrs = _dereq_('../../plots/template_attributes').hovertemplateAttrs;
74515var extendFlat = _dereq_('../../lib/extend').extendFlat;
74516
74517var scatterMarkerAttrs = scatterAttrs.marker;
74518var scatterMarkerLineAttrs = scatterMarkerAttrs.line;
74519
74520module.exports = {
74521 y: {
74522 valType: 'data_array',
74523 editType: 'calc+clearAxisTypes',
74524
74525 },
74526 x: {
74527 valType: 'data_array',
74528 editType: 'calc+clearAxisTypes',
74529
74530 },
74531 x0: {
74532 valType: 'any',
74533
74534 editType: 'calc+clearAxisTypes',
74535
74536 },
74537 y0: {
74538 valType: 'any',
74539
74540 editType: 'calc+clearAxisTypes',
74541
74542 },
74543
74544 dx: {
74545 valType: 'number',
74546
74547 editType: 'calc',
74548
74549 },
74550 dy: {
74551 valType: 'number',
74552
74553 editType: 'calc',
74554
74555 },
74556
74557 name: {
74558 valType: 'string',
74559
74560 editType: 'calc+clearAxisTypes',
74561
74562 },
74563
74564 q1: {
74565 valType: 'data_array',
74566
74567 editType: 'calc+clearAxisTypes',
74568
74569 },
74570 median: {
74571 valType: 'data_array',
74572
74573 editType: 'calc+clearAxisTypes',
74574
74575 },
74576 q3: {
74577 valType: 'data_array',
74578
74579 editType: 'calc+clearAxisTypes',
74580
74581 },
74582 lowerfence: {
74583 valType: 'data_array',
74584
74585 editType: 'calc',
74586
74587 },
74588 upperfence: {
74589 valType: 'data_array',
74590
74591 editType: 'calc',
74592
74593 },
74594
74595 notched: {
74596 valType: 'boolean',
74597
74598 editType: 'calc',
74599
74600 },
74601 notchwidth: {
74602 valType: 'number',
74603 min: 0,
74604 max: 0.5,
74605 dflt: 0.25,
74606
74607 editType: 'calc',
74608
74609 },
74610 notchspan: {
74611 valType: 'data_array',
74612
74613 editType: 'calc',
74614
74615 },
74616
74617 // TODO
74618 // maybe add
74619 // - loweroutlierbound / upperoutlierbound
74620 // - lowersuspectedoutlierbound / uppersuspectedoutlierbound
74621
74622 boxpoints: {
74623 valType: 'enumerated',
74624 values: ['all', 'outliers', 'suspectedoutliers', false],
74625
74626 editType: 'calc',
74627
74628 },
74629 jitter: {
74630 valType: 'number',
74631 min: 0,
74632 max: 1,
74633
74634 editType: 'calc',
74635
74636 },
74637 pointpos: {
74638 valType: 'number',
74639 min: -2,
74640 max: 2,
74641
74642 editType: 'calc',
74643
74644 },
74645
74646 boxmean: {
74647 valType: 'enumerated',
74648 values: [true, 'sd', false],
74649
74650 editType: 'calc',
74651
74652 },
74653 mean: {
74654 valType: 'data_array',
74655
74656 editType: 'calc',
74657
74658 },
74659 sd: {
74660 valType: 'data_array',
74661
74662 editType: 'calc',
74663
74664 },
74665
74666 orientation: {
74667 valType: 'enumerated',
74668 values: ['v', 'h'],
74669
74670 editType: 'calc+clearAxisTypes',
74671
74672 },
74673
74674 quartilemethod: {
74675 valType: 'enumerated',
74676 values: ['linear', 'exclusive', 'inclusive'],
74677 dflt: 'linear',
74678
74679 editType: 'calc',
74680
74681 },
74682
74683 width: {
74684 valType: 'number',
74685 min: 0,
74686
74687 dflt: 0,
74688 editType: 'calc',
74689
74690 },
74691
74692 marker: {
74693 outliercolor: {
74694 valType: 'color',
74695 dflt: 'rgba(0, 0, 0, 0)',
74696
74697 editType: 'style',
74698
74699 },
74700 symbol: extendFlat({}, scatterMarkerAttrs.symbol,
74701 {arrayOk: false, editType: 'plot'}),
74702 opacity: extendFlat({}, scatterMarkerAttrs.opacity,
74703 {arrayOk: false, dflt: 1, editType: 'style'}),
74704 size: extendFlat({}, scatterMarkerAttrs.size,
74705 {arrayOk: false, editType: 'calc'}),
74706 color: extendFlat({}, scatterMarkerAttrs.color,
74707 {arrayOk: false, editType: 'style'}),
74708 line: {
74709 color: extendFlat({}, scatterMarkerLineAttrs.color,
74710 {arrayOk: false, dflt: colorAttrs.defaultLine, editType: 'style'}
74711 ),
74712 width: extendFlat({}, scatterMarkerLineAttrs.width,
74713 {arrayOk: false, dflt: 0, editType: 'style'}
74714 ),
74715 outliercolor: {
74716 valType: 'color',
74717
74718 editType: 'style',
74719
74720 },
74721 outlierwidth: {
74722 valType: 'number',
74723 min: 0,
74724 dflt: 1,
74725
74726 editType: 'style',
74727
74728 },
74729 editType: 'style'
74730 },
74731 editType: 'plot'
74732 },
74733
74734 line: {
74735 color: {
74736 valType: 'color',
74737
74738 editType: 'style',
74739
74740 },
74741 width: {
74742 valType: 'number',
74743
74744 min: 0,
74745 dflt: 2,
74746 editType: 'style',
74747
74748 },
74749 editType: 'plot'
74750 },
74751
74752 fillcolor: scatterAttrs.fillcolor,
74753
74754 whiskerwidth: {
74755 valType: 'number',
74756 min: 0,
74757 max: 1,
74758 dflt: 0.5,
74759
74760 editType: 'calc',
74761
74762 },
74763
74764 offsetgroup: barAttrs.offsetgroup,
74765 alignmentgroup: barAttrs.alignmentgroup,
74766
74767 selected: {
74768 marker: scatterAttrs.selected.marker,
74769 editType: 'style'
74770 },
74771 unselected: {
74772 marker: scatterAttrs.unselected.marker,
74773 editType: 'style'
74774 },
74775
74776 text: extendFlat({}, scatterAttrs.text, {
74777
74778 }),
74779 hovertext: extendFlat({}, scatterAttrs.hovertext, {
74780
74781 }),
74782 hovertemplate: hovertemplateAttrs({
74783
74784 }),
74785
74786 hoveron: {
74787 valType: 'flaglist',
74788 flags: ['boxes', 'points'],
74789 dflt: 'boxes+points',
74790
74791 editType: 'style',
74792
74793 }
74794};
74795
74796},{"../../components/color/attributes":51,"../../lib/extend":173,"../../plots/template_attributes":264,"../bar/attributes":279,"../scatter/attributes":389}],297:[function(_dereq_,module,exports){
74797/**
74798* Copyright 2012-2020, Plotly, Inc.
74799* All rights reserved.
74800*
74801* This source code is licensed under the MIT license found in the
74802* LICENSE file in the root directory of this source tree.
74803*/
74804
74805'use strict';
74806
74807var isNumeric = _dereq_('fast-isnumeric');
74808
74809var Axes = _dereq_('../../plots/cartesian/axes');
74810var Lib = _dereq_('../../lib');
74811
74812var BADNUM = _dereq_('../../constants/numerical').BADNUM;
74813var _ = Lib._;
74814
74815module.exports = function calc(gd, trace) {
74816 var fullLayout = gd._fullLayout;
74817 var xa = Axes.getFromId(gd, trace.xaxis || 'x');
74818 var ya = Axes.getFromId(gd, trace.yaxis || 'y');
74819 var cd = [];
74820
74821 // N.B. violin reuses same Box.calc
74822 var numKey = trace.type === 'violin' ? '_numViolins' : '_numBoxes';
74823
74824 var i, j;
74825 var valAxis, valLetter;
74826 var posAxis, posLetter;
74827
74828 if(trace.orientation === 'h') {
74829 valAxis = xa;
74830 valLetter = 'x';
74831 posAxis = ya;
74832 posLetter = 'y';
74833 } else {
74834 valAxis = ya;
74835 valLetter = 'y';
74836 posAxis = xa;
74837 posLetter = 'x';
74838 }
74839
74840 var posArray = getPos(trace, posLetter, posAxis, fullLayout[numKey]);
74841 var dv = Lib.distinctVals(posArray);
74842 var posDistinct = dv.vals;
74843 var dPos = dv.minDiff / 2;
74844
74845 // item in trace calcdata
74846 var cdi;
74847 // array of {v: v, i, i} sample pts
74848 var pts;
74849 // values of the `pts` array of objects
74850 var boxVals;
74851 // length of sample
74852 var N;
74853 // single sample point
74854 var pt;
74855 // single sample value
74856 var v;
74857
74858 // filter function for outlier pts
74859 // outlier definition based on http://www.physics.csbsju.edu/stats/box2.html
74860 var ptFilterFn = (trace.boxpoints || trace.points) === 'all' ?
74861 Lib.identity :
74862 function(pt) { return (pt.v < cdi.lf || pt.v > cdi.uf); };
74863
74864 if(trace._hasPreCompStats) {
74865 var valArrayRaw = trace[valLetter];
74866 var d2c = function(k) { return valAxis.d2c((trace[k] || [])[i]); };
74867 var minVal = Infinity;
74868 var maxVal = -Infinity;
74869
74870 for(i = 0; i < trace._length; i++) {
74871 var posi = posArray[i];
74872 if(!isNumeric(posi)) continue;
74873
74874 cdi = {};
74875 cdi.pos = cdi[posLetter] = posi;
74876
74877 cdi.q1 = d2c('q1');
74878 cdi.med = d2c('median');
74879 cdi.q3 = d2c('q3');
74880
74881 pts = [];
74882 if(valArrayRaw && Lib.isArrayOrTypedArray(valArrayRaw[i])) {
74883 for(j = 0; j < valArrayRaw[i].length; j++) {
74884 v = valAxis.d2c(valArrayRaw[i][j]);
74885 if(v !== BADNUM) {
74886 pt = {v: v, i: [i, j]};
74887 arraysToCalcdata(pt, trace, [i, j]);
74888 pts.push(pt);
74889 }
74890 }
74891 }
74892 cdi.pts = pts.sort(sortByVal);
74893 boxVals = cdi[valLetter] = pts.map(extractVal);
74894 N = boxVals.length;
74895
74896 if(cdi.med !== BADNUM && cdi.q1 !== BADNUM && cdi.q3 !== BADNUM &&
74897 cdi.med >= cdi.q1 && cdi.q3 >= cdi.med
74898 ) {
74899 var lf = d2c('lowerfence');
74900 cdi.lf = (lf !== BADNUM && lf <= cdi.q1) ?
74901 lf :
74902 computeLowerFence(cdi, boxVals, N);
74903
74904 var uf = d2c('upperfence');
74905 cdi.uf = (uf !== BADNUM && uf >= cdi.q3) ?
74906 uf :
74907 computeUpperFence(cdi, boxVals, N);
74908
74909 var mean = d2c('mean');
74910 cdi.mean = (mean !== BADNUM) ?
74911 mean :
74912 (N ? Lib.mean(boxVals, N) : (cdi.q1 + cdi.q3) / 2);
74913
74914 var sd = d2c('sd');
74915 cdi.sd = (mean !== BADNUM && sd >= 0) ?
74916 sd :
74917 (N ? Lib.stdev(boxVals, N, cdi.mean) : (cdi.q3 - cdi.q1));
74918
74919 cdi.lo = computeLowerOutlierBound(cdi);
74920 cdi.uo = computeUpperOutlierBound(cdi);
74921
74922 var ns = d2c('notchspan');
74923 ns = (ns !== BADNUM && ns > 0) ? ns : computeNotchSpan(cdi, N);
74924 cdi.ln = cdi.med - ns;
74925 cdi.un = cdi.med + ns;
74926
74927 var imin = cdi.lf;
74928 var imax = cdi.uf;
74929 if(trace.boxpoints && boxVals.length) {
74930 imin = Math.min(imin, boxVals[0]);
74931 imax = Math.max(imax, boxVals[N - 1]);
74932 }
74933 if(trace.notched) {
74934 imin = Math.min(imin, cdi.ln);
74935 imax = Math.max(imax, cdi.un);
74936 }
74937 cdi.min = imin;
74938 cdi.max = imax;
74939 } else {
74940 Lib.warn([
74941 'Invalid input - make sure that q1 <= median <= q3',
74942 'q1 = ' + cdi.q1,
74943 'median = ' + cdi.med,
74944 'q3 = ' + cdi.q3
74945 ].join('\n'));
74946
74947 var v0;
74948 if(cdi.med !== BADNUM) {
74949 v0 = cdi.med;
74950 } else if(cdi.q1 !== BADNUM) {
74951 if(cdi.q3 !== BADNUM) v0 = (cdi.q1 + cdi.q3) / 2;
74952 else v0 = cdi.q1;
74953 } else if(cdi.q3 !== BADNUM) {
74954 v0 = cdi.q3;
74955 } else {
74956 v0 = 0;
74957 }
74958
74959 // draw box as line segment
74960 cdi.med = v0;
74961 cdi.q1 = cdi.q3 = v0;
74962 cdi.lf = cdi.uf = v0;
74963 cdi.mean = cdi.sd = v0;
74964 cdi.ln = cdi.un = v0;
74965 cdi.min = cdi.max = v0;
74966 }
74967
74968 minVal = Math.min(minVal, cdi.min);
74969 maxVal = Math.max(maxVal, cdi.max);
74970
74971 cdi.pts2 = pts.filter(ptFilterFn);
74972
74973 cd.push(cdi);
74974 }
74975
74976 trace._extremes[valAxis._id] = Axes.findExtremes(valAxis,
74977 [minVal, maxVal],
74978 {padded: true}
74979 );
74980 } else {
74981 var valArray = valAxis.makeCalcdata(trace, valLetter);
74982 var posBins = makeBins(posDistinct, dPos);
74983 var pLen = posDistinct.length;
74984 var ptsPerBin = initNestedArray(pLen);
74985
74986 // bin pts info per position bins
74987 for(i = 0; i < trace._length; i++) {
74988 v = valArray[i];
74989 if(!isNumeric(v)) continue;
74990
74991 var n = Lib.findBin(posArray[i], posBins);
74992 if(n >= 0 && n < pLen) {
74993 pt = {v: v, i: i};
74994 arraysToCalcdata(pt, trace, i);
74995 ptsPerBin[n].push(pt);
74996 }
74997 }
74998
74999 var minLowerNotch = Infinity;
75000 var maxUpperNotch = -Infinity;
75001
75002 var quartilemethod = trace.quartilemethod;
75003 var usesExclusive = quartilemethod === 'exclusive';
75004 var usesInclusive = quartilemethod === 'inclusive';
75005
75006 // build calcdata trace items, one item per distinct position
75007 for(i = 0; i < pLen; i++) {
75008 if(ptsPerBin[i].length > 0) {
75009 cdi = {};
75010 cdi.pos = cdi[posLetter] = posDistinct[i];
75011
75012 pts = cdi.pts = ptsPerBin[i].sort(sortByVal);
75013 boxVals = cdi[valLetter] = pts.map(extractVal);
75014 N = boxVals.length;
75015
75016 cdi.min = boxVals[0];
75017 cdi.max = boxVals[N - 1];
75018 cdi.mean = Lib.mean(boxVals, N);
75019 cdi.sd = Lib.stdev(boxVals, N, cdi.mean);
75020 cdi.med = Lib.interp(boxVals, 0.5);
75021
75022 if((N % 2) && (usesExclusive || usesInclusive)) {
75023 var lower;
75024 var upper;
75025
75026 if(usesExclusive) {
75027 // do NOT include the median in either half
75028 lower = boxVals.slice(0, N / 2);
75029 upper = boxVals.slice(N / 2 + 1);
75030 } else if(usesInclusive) {
75031 // include the median in either half
75032 lower = boxVals.slice(0, N / 2 + 1);
75033 upper = boxVals.slice(N / 2);
75034 }
75035
75036 cdi.q1 = Lib.interp(lower, 0.5);
75037 cdi.q3 = Lib.interp(upper, 0.5);
75038 } else {
75039 cdi.q1 = Lib.interp(boxVals, 0.25);
75040 cdi.q3 = Lib.interp(boxVals, 0.75);
75041 }
75042
75043 // lower and upper fences
75044 cdi.lf = computeLowerFence(cdi, boxVals, N);
75045 cdi.uf = computeUpperFence(cdi, boxVals, N);
75046
75047 // lower and upper outliers bounds
75048 cdi.lo = computeLowerOutlierBound(cdi);
75049 cdi.uo = computeUpperOutlierBound(cdi);
75050
75051 // lower and upper notches
75052 var mci = computeNotchSpan(cdi, N);
75053 cdi.ln = cdi.med - mci;
75054 cdi.un = cdi.med + mci;
75055 minLowerNotch = Math.min(minLowerNotch, cdi.ln);
75056 maxUpperNotch = Math.max(maxUpperNotch, cdi.un);
75057
75058 cdi.pts2 = pts.filter(ptFilterFn);
75059
75060 cd.push(cdi);
75061 }
75062 }
75063
75064 trace._extremes[valAxis._id] = Axes.findExtremes(valAxis,
75065 trace.notched ? valArray.concat([minLowerNotch, maxUpperNotch]) : valArray,
75066 {padded: true}
75067 );
75068 }
75069
75070 calcSelection(cd, trace);
75071
75072 if(cd.length > 0) {
75073 cd[0].t = {
75074 num: fullLayout[numKey],
75075 dPos: dPos,
75076 posLetter: posLetter,
75077 valLetter: valLetter,
75078 labels: {
75079 med: _(gd, 'median:'),
75080 min: _(gd, 'min:'),
75081 q1: _(gd, 'q1:'),
75082 q3: _(gd, 'q3:'),
75083 max: _(gd, 'max:'),
75084 mean: trace.boxmean === 'sd' ? _(gd, 'mean ± σ:') : _(gd, 'mean:'),
75085 lf: _(gd, 'lower fence:'),
75086 uf: _(gd, 'upper fence:')
75087 }
75088 };
75089
75090 fullLayout[numKey]++;
75091 return cd;
75092 } else {
75093 return [{t: {empty: true}}];
75094 }
75095};
75096
75097// In vertical (horizontal) box plots:
75098// if no x (y) data, use x0 (y0), or name
75099// so if you want one box
75100// per trace, set x0 (y0) to the x (y) value or category for this trace
75101// (or set x (y) to a constant array matching y (x))
75102function getPos(trace, posLetter, posAxis, num) {
75103 var hasPosArray = posLetter in trace;
75104 var hasPos0 = posLetter + '0' in trace;
75105 var hasPosStep = 'd' + posLetter in trace;
75106
75107 if(hasPosArray || (hasPos0 && hasPosStep)) {
75108 return posAxis.makeCalcdata(trace, posLetter);
75109 }
75110
75111 var pos0;
75112 if(hasPos0) {
75113 pos0 = trace[posLetter + '0'];
75114 } else if('name' in trace && (
75115 posAxis.type === 'category' || (
75116 isNumeric(trace.name) &&
75117 ['linear', 'log'].indexOf(posAxis.type) !== -1
75118 ) || (
75119 Lib.isDateTime(trace.name) &&
75120 posAxis.type === 'date'
75121 )
75122 )) {
75123 pos0 = trace.name;
75124 } else {
75125 pos0 = num;
75126 }
75127
75128 var pos0c = posAxis.type === 'multicategory' ?
75129 posAxis.r2c_just_indices(pos0) :
75130 posAxis.d2c(pos0, 0, trace[posLetter + 'calendar']);
75131
75132 var len = trace._length;
75133 var out = new Array(len);
75134 for(var i = 0; i < len; i++) out[i] = pos0c;
75135
75136 return out;
75137}
75138
75139function makeBins(x, dx) {
75140 var len = x.length;
75141 var bins = new Array(len + 1);
75142
75143 for(var i = 0; i < len; i++) {
75144 bins[i] = x[i] - dx;
75145 }
75146 bins[len] = x[len - 1] + dx;
75147
75148 return bins;
75149}
75150
75151function initNestedArray(len) {
75152 var arr = new Array(len);
75153 for(var i = 0; i < len; i++) {
75154 arr[i] = [];
75155 }
75156 return arr;
75157}
75158
75159var TRACE_TO_CALC = {
75160 text: 'tx',
75161 hovertext: 'htx'
75162};
75163
75164function arraysToCalcdata(pt, trace, ptNumber) {
75165 for(var k in TRACE_TO_CALC) {
75166 if(Lib.isArrayOrTypedArray(trace[k])) {
75167 if(Array.isArray(ptNumber)) {
75168 if(Lib.isArrayOrTypedArray(trace[k][ptNumber[0]])) {
75169 pt[TRACE_TO_CALC[k]] = trace[k][ptNumber[0]][ptNumber[1]];
75170 }
75171 } else {
75172 pt[TRACE_TO_CALC[k]] = trace[k][ptNumber];
75173 }
75174 }
75175 }
75176}
75177
75178function calcSelection(cd, trace) {
75179 if(Lib.isArrayOrTypedArray(trace.selectedpoints)) {
75180 for(var i = 0; i < cd.length; i++) {
75181 var pts = cd[i].pts || [];
75182 var ptNumber2cdIndex = {};
75183
75184 for(var j = 0; j < pts.length; j++) {
75185 ptNumber2cdIndex[pts[j].i] = j;
75186 }
75187
75188 Lib.tagSelected(pts, trace, ptNumber2cdIndex);
75189 }
75190 }
75191}
75192
75193function sortByVal(a, b) { return a.v - b.v; }
75194
75195function extractVal(o) { return o.v; }
75196
75197// last point below 1.5 * IQR
75198function computeLowerFence(cdi, boxVals, N) {
75199 if(N === 0) return cdi.q1;
75200 return Math.min(
75201 cdi.q1,
75202 boxVals[Math.min(
75203 Lib.findBin(2.5 * cdi.q1 - 1.5 * cdi.q3, boxVals, true) + 1,
75204 N - 1
75205 )]
75206 );
75207}
75208
75209// last point above 1.5 * IQR
75210function computeUpperFence(cdi, boxVals, N) {
75211 if(N === 0) return cdi.q3;
75212 return Math.max(
75213 cdi.q3,
75214 boxVals[Math.max(
75215 Lib.findBin(2.5 * cdi.q3 - 1.5 * cdi.q1, boxVals),
75216 0
75217 )]
75218 );
75219}
75220
75221// 3 IQR below (don't clip to max/min,
75222// this is only for discriminating suspected & far outliers)
75223function computeLowerOutlierBound(cdi) {
75224 return 4 * cdi.q1 - 3 * cdi.q3;
75225}
75226
75227// 3 IQR above (don't clip to max/min,
75228// this is only for discriminating suspected & far outliers)
75229function computeUpperOutlierBound(cdi) {
75230 return 4 * cdi.q3 - 3 * cdi.q1;
75231}
75232
75233// 95% confidence intervals for median
75234function computeNotchSpan(cdi, N) {
75235 if(N === 0) return 0;
75236 return 1.57 * (cdi.q3 - cdi.q1) / Math.sqrt(N);
75237}
75238
75239},{"../../constants/numerical":158,"../../lib":178,"../../plots/cartesian/axes":222,"fast-isnumeric":18}],298:[function(_dereq_,module,exports){
75240/**
75241* Copyright 2012-2020, Plotly, Inc.
75242* All rights reserved.
75243*
75244* This source code is licensed under the MIT license found in the
75245* LICENSE file in the root directory of this source tree.
75246*/
75247
75248'use strict';
75249
75250var Axes = _dereq_('../../plots/cartesian/axes');
75251var Lib = _dereq_('../../lib');
75252var getAxisGroup = _dereq_('../../plots/cartesian/axis_ids').getAxisGroup;
75253
75254var orientations = ['v', 'h'];
75255
75256function crossTraceCalc(gd, plotinfo) {
75257 var calcdata = gd.calcdata;
75258 var xa = plotinfo.xaxis;
75259 var ya = plotinfo.yaxis;
75260
75261 for(var i = 0; i < orientations.length; i++) {
75262 var orientation = orientations[i];
75263 var posAxis = orientation === 'h' ? ya : xa;
75264 var boxList = [];
75265
75266 // make list of boxes / candlesticks
75267 // For backward compatibility, candlesticks are treated as if they *are* box traces here
75268 for(var j = 0; j < calcdata.length; j++) {
75269 var cd = calcdata[j];
75270 var t = cd[0].t;
75271 var trace = cd[0].trace;
75272
75273 if(trace.visible === true &&
75274 (trace.type === 'box' || trace.type === 'candlestick') &&
75275 !t.empty &&
75276 (trace.orientation || 'v') === orientation &&
75277 trace.xaxis === xa._id &&
75278 trace.yaxis === ya._id
75279 ) {
75280 boxList.push(j);
75281 }
75282 }
75283
75284 setPositionOffset('box', gd, boxList, posAxis);
75285 }
75286}
75287
75288function setPositionOffset(traceType, gd, boxList, posAxis) {
75289 var calcdata = gd.calcdata;
75290 var fullLayout = gd._fullLayout;
75291 var axId = posAxis._id;
75292 var axLetter = axId.charAt(0);
75293
75294 var i, j, calcTrace;
75295 var pointList = [];
75296 var shownPts = 0;
75297
75298 // make list of box points
75299 for(i = 0; i < boxList.length; i++) {
75300 calcTrace = calcdata[boxList[i]];
75301 for(j = 0; j < calcTrace.length; j++) {
75302 pointList.push(posAxis.c2l(calcTrace[j].pos, true));
75303 shownPts += (calcTrace[j].pts2 || []).length;
75304 }
75305 }
75306
75307 if(!pointList.length) return;
75308
75309 // box plots - update dPos based on multiple traces
75310 var boxdv = Lib.distinctVals(pointList);
75311 var dPos0 = boxdv.minDiff / 2;
75312
75313 // check for forced minimum dtick
75314 Axes.minDtick(posAxis, boxdv.minDiff, boxdv.vals[0], true);
75315
75316 var numKey = traceType === 'violin' ? '_numViolins' : '_numBoxes';
75317 var numTotal = fullLayout[numKey];
75318 var group = fullLayout[traceType + 'mode'] === 'group' && numTotal > 1;
75319 var groupFraction = 1 - fullLayout[traceType + 'gap'];
75320 var groupGapFraction = 1 - fullLayout[traceType + 'groupgap'];
75321
75322 for(i = 0; i < boxList.length; i++) {
75323 calcTrace = calcdata[boxList[i]];
75324
75325 var trace = calcTrace[0].trace;
75326 var t = calcTrace[0].t;
75327 var width = trace.width;
75328 var side = trace.side;
75329
75330 // position coordinate delta
75331 var dPos;
75332 // box half width;
75333 var bdPos;
75334 // box center offset
75335 var bPos;
75336 // half-width within which to accept hover for this box/violin
75337 // always split the distance to the closest box/violin
75338 var wHover;
75339
75340 if(width) {
75341 dPos = bdPos = wHover = width / 2;
75342 bPos = 0;
75343 } else {
75344 dPos = dPos0;
75345
75346 if(group) {
75347 var groupId = getAxisGroup(fullLayout, posAxis._id) + trace.orientation;
75348 var alignmentGroups = fullLayout._alignmentOpts[groupId] || {};
75349 var alignmentGroupOpts = alignmentGroups[trace.alignmentgroup] || {};
75350 var nOffsetGroups = Object.keys(alignmentGroupOpts.offsetGroups || {}).length;
75351 var num = nOffsetGroups || numTotal;
75352 var shift = nOffsetGroups ? trace._offsetIndex : t.num;
75353
75354 bdPos = dPos * groupFraction * groupGapFraction / num;
75355 bPos = 2 * dPos * (-0.5 + (shift + 0.5) / num) * groupFraction;
75356 wHover = dPos * groupFraction / num;
75357 } else {
75358 bdPos = dPos * groupFraction * groupGapFraction;
75359 bPos = 0;
75360 wHover = dPos;
75361 }
75362 }
75363 t.dPos = dPos;
75364 t.bPos = bPos;
75365 t.bdPos = bdPos;
75366 t.wHover = wHover;
75367
75368 // box/violin-only value-space push value
75369 var pushplus;
75370 var pushminus;
75371 // edge of box/violin
75372 var edge = bPos + bdPos;
75373 var edgeplus;
75374 var edgeminus;
75375 // value-space padding
75376 var vpadplus;
75377 var vpadminus;
75378 // pixel-space padding
75379 var ppadplus;
75380 var ppadminus;
75381 // do we add 5% of both sides (more logic for points beyond box/violin below)
75382 var padded = Boolean(width);
75383 // does this trace show points?
75384 var hasPts = (trace.boxpoints || trace.points) && (shownPts > 0);
75385
75386 if(side === 'positive') {
75387 pushplus = dPos * (width ? 1 : 0.5);
75388 edgeplus = edge;
75389 pushminus = edgeplus = bPos;
75390 } else if(side === 'negative') {
75391 pushplus = edgeplus = bPos;
75392 pushminus = dPos * (width ? 1 : 0.5);
75393 edgeminus = edge;
75394 } else {
75395 pushplus = pushminus = dPos;
75396 edgeplus = edgeminus = edge;
75397 }
75398
75399 if(hasPts) {
75400 var pointpos = trace.pointpos;
75401 var jitter = trace.jitter;
75402 var ms = trace.marker.size / 2;
75403
75404 var pp = 0;
75405 if((pointpos + jitter) >= 0) {
75406 pp = edge * (pointpos + jitter);
75407 if(pp > pushplus) {
75408 // (++) beyond plus-value, use pp
75409 padded = true;
75410 ppadplus = ms;
75411 vpadplus = pp;
75412 } else if(pp > edgeplus) {
75413 // (+), use push-value (it's bigger), but add px-pad
75414 ppadplus = ms;
75415 vpadplus = pushplus;
75416 }
75417 }
75418 if(pp <= pushplus) {
75419 // (->) fallback to push value
75420 vpadplus = pushplus;
75421 }
75422
75423 var pm = 0;
75424 if((pointpos - jitter) <= 0) {
75425 pm = -edge * (pointpos - jitter);
75426 if(pm > pushminus) {
75427 // (--) beyond plus-value, use pp
75428 padded = true;
75429 ppadminus = ms;
75430 vpadminus = pm;
75431 } else if(pm > edgeminus) {
75432 // (-), use push-value (it's bigger), but add px-pad
75433 ppadminus = ms;
75434 vpadminus = pushminus;
75435 }
75436 }
75437 if(pm <= pushminus) {
75438 // (<-) fallback to push value
75439 vpadminus = pushminus;
75440 }
75441 } else {
75442 vpadplus = pushplus;
75443 vpadminus = pushminus;
75444 }
75445
75446 var pos = new Array(calcTrace.length);
75447 for(j = 0; j < calcTrace.length; j++) {
75448 pos[j] = calcTrace[j].pos;
75449 }
75450
75451 trace._extremes[axId] = Axes.findExtremes(posAxis, pos, {
75452 padded: padded,
75453 vpadminus: vpadminus,
75454 vpadplus: vpadplus,
75455 vpadLinearized: true,
75456 // N.B. SVG px-space positive/negative
75457 ppadminus: {x: ppadminus, y: ppadplus}[axLetter],
75458 ppadplus: {x: ppadplus, y: ppadminus}[axLetter],
75459 });
75460 }
75461}
75462
75463module.exports = {
75464 crossTraceCalc: crossTraceCalc,
75465 setPositionOffset: setPositionOffset
75466};
75467
75468},{"../../lib":178,"../../plots/cartesian/axes":222,"../../plots/cartesian/axis_ids":225}],299:[function(_dereq_,module,exports){
75469/**
75470* Copyright 2012-2020, Plotly, Inc.
75471* All rights reserved.
75472*
75473* This source code is licensed under the MIT license found in the
75474* LICENSE file in the root directory of this source tree.
75475*/
75476
75477'use strict';
75478
75479var Lib = _dereq_('../../lib');
75480var Registry = _dereq_('../../registry');
75481var Color = _dereq_('../../components/color');
75482var handleGroupingDefaults = _dereq_('../bar/defaults').handleGroupingDefaults;
75483var autoType = _dereq_('../../plots/cartesian/axis_autotype');
75484var attributes = _dereq_('./attributes');
75485
75486function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
75487 function coerce(attr, dflt) {
75488 return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
75489 }
75490
75491 handleSampleDefaults(traceIn, traceOut, coerce, layout);
75492 if(traceOut.visible === false) return;
75493
75494 var hasPreCompStats = traceOut._hasPreCompStats;
75495
75496 if(hasPreCompStats) {
75497 coerce('lowerfence');
75498 coerce('upperfence');
75499 }
75500
75501 coerce('line.color', (traceIn.marker || {}).color || defaultColor);
75502 coerce('line.width');
75503 coerce('fillcolor', Color.addOpacity(traceOut.line.color, 0.5));
75504
75505 var boxmeanDflt = false;
75506 if(hasPreCompStats) {
75507 var mean = coerce('mean');
75508 var sd = coerce('sd');
75509 if(mean && mean.length) {
75510 boxmeanDflt = true;
75511 if(sd && sd.length) boxmeanDflt = 'sd';
75512 }
75513 }
75514 coerce('boxmean', boxmeanDflt);
75515
75516 coerce('whiskerwidth');
75517 coerce('width');
75518 coerce('quartilemethod');
75519
75520 var notchedDflt = false;
75521 if(hasPreCompStats) {
75522 var notchspan = coerce('notchspan');
75523 if(notchspan && notchspan.length) {
75524 notchedDflt = true;
75525 }
75526 } else if(Lib.validate(traceIn.notchwidth, attributes.notchwidth)) {
75527 notchedDflt = true;
75528 }
75529 var notched = coerce('notched', notchedDflt);
75530 if(notched) coerce('notchwidth');
75531
75532 handlePointsDefaults(traceIn, traceOut, coerce, {prefix: 'box'});
75533}
75534
75535function handleSampleDefaults(traceIn, traceOut, coerce, layout) {
75536 function getDims(arr) {
75537 var dims = 0;
75538 if(arr && arr.length) {
75539 dims += 1;
75540 if(Lib.isArrayOrTypedArray(arr[0]) && arr[0].length) {
75541 dims += 1;
75542 }
75543 }
75544 return dims;
75545 }
75546
75547 function valid(astr) {
75548 return Lib.validate(traceIn[astr], attributes[astr]);
75549 }
75550
75551 var y = coerce('y');
75552 var x = coerce('x');
75553
75554 var sLen;
75555 if(traceOut.type === 'box') {
75556 var q1 = coerce('q1');
75557 var median = coerce('median');
75558 var q3 = coerce('q3');
75559
75560 traceOut._hasPreCompStats = (
75561 q1 && q1.length &&
75562 median && median.length &&
75563 q3 && q3.length
75564 );
75565 sLen = Math.min(
75566 Lib.minRowLength(q1),
75567 Lib.minRowLength(median),
75568 Lib.minRowLength(q3)
75569 );
75570 }
75571
75572 var yDims = getDims(y);
75573 var xDims = getDims(x);
75574 var yLen = yDims && Lib.minRowLength(y);
75575 var xLen = xDims && Lib.minRowLength(x);
75576
75577 var defaultOrientation, len;
75578 if(traceOut._hasPreCompStats) {
75579 switch(String(xDims) + String(yDims)) {
75580 // no x / no y
75581 case '00':
75582 var setInX = valid('x0') || valid('dx');
75583 var setInY = valid('y0') || valid('dy');
75584
75585 if(setInY && !setInX) {
75586 defaultOrientation = 'h';
75587 } else {
75588 defaultOrientation = 'v';
75589 }
75590
75591 len = sLen;
75592 break;
75593 // just x
75594 case '10':
75595 defaultOrientation = 'v';
75596 len = Math.min(sLen, xLen);
75597 break;
75598 case '20':
75599 defaultOrientation = 'h';
75600 len = Math.min(sLen, x.length);
75601 break;
75602 // just y
75603 case '01':
75604 defaultOrientation = 'h';
75605 len = Math.min(sLen, yLen);
75606 break;
75607 case '02':
75608 defaultOrientation = 'v';
75609 len = Math.min(sLen, y.length);
75610 break;
75611 // both
75612 case '12':
75613 defaultOrientation = 'v';
75614 len = Math.min(sLen, xLen, y.length);
75615 break;
75616 case '21':
75617 defaultOrientation = 'h';
75618 len = Math.min(sLen, x.length, yLen);
75619 break;
75620 case '11':
75621 // this one is ill-defined
75622 len = 0;
75623 break;
75624 case '22':
75625 var hasCategories = false;
75626 var i;
75627 for(i = 0; i < x.length; i++) {
75628 if(autoType(x[i]) === 'category') {
75629 hasCategories = true;
75630 break;
75631 }
75632 }
75633
75634 if(hasCategories) {
75635 defaultOrientation = 'v';
75636 len = Math.min(sLen, xLen, y.length);
75637 } else {
75638 for(i = 0; i < y.length; i++) {
75639 if(autoType(y[i]) === 'category') {
75640 hasCategories = true;
75641 break;
75642 }
75643 }
75644
75645 if(hasCategories) {
75646 defaultOrientation = 'h';
75647 len = Math.min(sLen, x.length, yLen);
75648 } else {
75649 defaultOrientation = 'v';
75650 len = Math.min(sLen, xLen, y.length);
75651 }
75652 }
75653 break;
75654 }
75655 } else if(yDims > 0) {
75656 defaultOrientation = 'v';
75657 if(xDims > 0) {
75658 len = Math.min(xLen, yLen);
75659 } else {
75660 len = Math.min(yLen);
75661 }
75662 } else if(xDims > 0) {
75663 defaultOrientation = 'h';
75664 len = Math.min(xLen);
75665 } else {
75666 len = 0;
75667 }
75668
75669 if(!len) {
75670 traceOut.visible = false;
75671 return;
75672 }
75673 traceOut._length = len;
75674
75675 var orientation = coerce('orientation', defaultOrientation);
75676
75677 // these are just used for positioning, they never define the sample
75678 if(traceOut._hasPreCompStats) {
75679 if(orientation === 'v' && xDims === 0) {
75680 coerce('x0', 0);
75681 coerce('dx', 1);
75682 } else if(orientation === 'h' && yDims === 0) {
75683 coerce('y0', 0);
75684 coerce('dy', 1);
75685 }
75686 } else {
75687 if(orientation === 'v' && xDims === 0) {
75688 coerce('x0');
75689 } else if(orientation === 'h' && yDims === 0) {
75690 coerce('y0');
75691 }
75692 }
75693
75694 var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleTraceDefaults');
75695 handleCalendarDefaults(traceIn, traceOut, ['x', 'y'], layout);
75696}
75697
75698function handlePointsDefaults(traceIn, traceOut, coerce, opts) {
75699 var prefix = opts.prefix;
75700
75701 var outlierColorDflt = Lib.coerce2(traceIn, traceOut, attributes, 'marker.outliercolor');
75702 var lineoutliercolor = coerce('marker.line.outliercolor');
75703
75704 var modeDflt = 'outliers';
75705 if(traceOut._hasPreCompStats) {
75706 modeDflt = 'all';
75707 } else if(outlierColorDflt || lineoutliercolor) {
75708 modeDflt = 'suspectedoutliers';
75709 }
75710
75711 var mode = coerce(prefix + 'points', modeDflt);
75712
75713 if(mode) {
75714 coerce('jitter', mode === 'all' ? 0.3 : 0);
75715 coerce('pointpos', mode === 'all' ? -1.5 : 0);
75716
75717 coerce('marker.symbol');
75718 coerce('marker.opacity');
75719 coerce('marker.size');
75720 coerce('marker.color', traceOut.line.color);
75721 coerce('marker.line.color');
75722 coerce('marker.line.width');
75723
75724 if(mode === 'suspectedoutliers') {
75725 coerce('marker.line.outliercolor', traceOut.marker.color);
75726 coerce('marker.line.outlierwidth');
75727 }
75728
75729 coerce('selected.marker.color');
75730 coerce('unselected.marker.color');
75731 coerce('selected.marker.size');
75732 coerce('unselected.marker.size');
75733
75734 coerce('text');
75735 coerce('hovertext');
75736 } else {
75737 delete traceOut.marker;
75738 }
75739
75740 var hoveron = coerce('hoveron');
75741 if(hoveron === 'all' || hoveron.indexOf('points') !== -1) {
75742 coerce('hovertemplate');
75743 }
75744
75745 Lib.coerceSelectionMarkerOpacity(traceOut, coerce);
75746}
75747
75748function crossTraceDefaults(fullData, fullLayout) {
75749 var traceIn, traceOut;
75750
75751 function coerce(attr) {
75752 return Lib.coerce(traceOut._input, traceOut, attributes, attr);
75753 }
75754
75755 for(var i = 0; i < fullData.length; i++) {
75756 traceOut = fullData[i];
75757 var traceType = traceOut.type;
75758
75759 if(traceType === 'box' || traceType === 'violin') {
75760 traceIn = traceOut._input;
75761 if(fullLayout[traceType + 'mode'] === 'group') {
75762 handleGroupingDefaults(traceIn, traceOut, fullLayout, coerce);
75763 }
75764 }
75765 }
75766}
75767
75768module.exports = {
75769 supplyDefaults: supplyDefaults,
75770 crossTraceDefaults: crossTraceDefaults,
75771
75772 handleSampleDefaults: handleSampleDefaults,
75773 handlePointsDefaults: handlePointsDefaults
75774};
75775
75776},{"../../components/color":52,"../../lib":178,"../../plots/cartesian/axis_autotype":223,"../../registry":269,"../bar/defaults":283,"./attributes":296}],300:[function(_dereq_,module,exports){
75777/**
75778* Copyright 2012-2020, Plotly, Inc.
75779* All rights reserved.
75780*
75781* This source code is licensed under the MIT license found in the
75782* LICENSE file in the root directory of this source tree.
75783*/
75784
75785'use strict';
75786
75787module.exports = function eventData(out, pt) {
75788 // Note: hoverOnBox property is needed for click-to-select
75789 // to ignore when a box was clicked. This is the reason box
75790 // implements this custom eventData function.
75791 if(pt.hoverOnBox) out.hoverOnBox = pt.hoverOnBox;
75792
75793 if('xVal' in pt) out.x = pt.xVal;
75794 if('yVal' in pt) out.y = pt.yVal;
75795 if(pt.xa) out.xaxis = pt.xa;
75796 if(pt.ya) out.yaxis = pt.ya;
75797
75798 return out;
75799};
75800
75801},{}],301:[function(_dereq_,module,exports){
75802/**
75803* Copyright 2012-2020, Plotly, Inc.
75804* All rights reserved.
75805*
75806* This source code is licensed under the MIT license found in the
75807* LICENSE file in the root directory of this source tree.
75808*/
75809
75810'use strict';
75811
75812var Axes = _dereq_('../../plots/cartesian/axes');
75813var Lib = _dereq_('../../lib');
75814var Fx = _dereq_('../../components/fx');
75815var Color = _dereq_('../../components/color');
75816var fillText = Lib.fillText;
75817
75818function hoverPoints(pointData, xval, yval, hovermode) {
75819 var cd = pointData.cd;
75820 var trace = cd[0].trace;
75821 var hoveron = trace.hoveron;
75822 var closeBoxData = [];
75823 var closePtData;
75824
75825 if(hoveron.indexOf('boxes') !== -1) {
75826 closeBoxData = closeBoxData.concat(hoverOnBoxes(pointData, xval, yval, hovermode));
75827 }
75828
75829 if(hoveron.indexOf('points') !== -1) {
75830 closePtData = hoverOnPoints(pointData, xval, yval);
75831 }
75832
75833 // If there's a point in range and hoveron has points, show the best single point only.
75834 // If hoveron has boxes and there's no point in range (or hoveron doesn't have points), show the box stats.
75835 if(hovermode === 'closest') {
75836 if(closePtData) return [closePtData];
75837 return closeBoxData;
75838 }
75839
75840 // Otherwise in compare mode, allow a point AND the box stats to be labeled
75841 // If there are multiple boxes in range (ie boxmode = 'overlay') we'll see stats for all of them.
75842 if(closePtData) {
75843 closeBoxData.push(closePtData);
75844 return closeBoxData;
75845 }
75846 return closeBoxData;
75847}
75848
75849function hoverOnBoxes(pointData, xval, yval, hovermode) {
75850 var cd = pointData.cd;
75851 var xa = pointData.xa;
75852 var ya = pointData.ya;
75853 var trace = cd[0].trace;
75854 var t = cd[0].t;
75855 var isViolin = trace.type === 'violin';
75856 var closeBoxData = [];
75857
75858 var pLetter, vLetter, pAxis, vAxis, vVal, pVal, dx, dy, dPos,
75859 hoverPseudoDistance, spikePseudoDistance;
75860
75861 var boxDelta = t.bdPos;
75862 var boxDeltaPos, boxDeltaNeg;
75863 var posAcceptance = t.wHover;
75864 var shiftPos = function(di) { return pAxis.c2l(di.pos) + t.bPos - pAxis.c2l(pVal); };
75865
75866 if(isViolin && trace.side !== 'both') {
75867 if(trace.side === 'positive') {
75868 dPos = function(di) {
75869 var pos = shiftPos(di);
75870 return Fx.inbox(pos, pos + posAcceptance, hoverPseudoDistance);
75871 };
75872 boxDeltaPos = boxDelta;
75873 boxDeltaNeg = 0;
75874 }
75875 if(trace.side === 'negative') {
75876 dPos = function(di) {
75877 var pos = shiftPos(di);
75878 return Fx.inbox(pos - posAcceptance, pos, hoverPseudoDistance);
75879 };
75880 boxDeltaPos = 0;
75881 boxDeltaNeg = boxDelta;
75882 }
75883 } else {
75884 dPos = function(di) {
75885 var pos = shiftPos(di);
75886 return Fx.inbox(pos - posAcceptance, pos + posAcceptance, hoverPseudoDistance);
75887 };
75888 boxDeltaPos = boxDeltaNeg = boxDelta;
75889 }
75890
75891 var dVal;
75892
75893 if(isViolin) {
75894 dVal = function(di) {
75895 return Fx.inbox(di.span[0] - vVal, di.span[1] - vVal, hoverPseudoDistance);
75896 };
75897 } else {
75898 dVal = function(di) {
75899 return Fx.inbox(di.min - vVal, di.max - vVal, hoverPseudoDistance);
75900 };
75901 }
75902
75903 if(trace.orientation === 'h') {
75904 vVal = xval;
75905 pVal = yval;
75906 dx = dVal;
75907 dy = dPos;
75908 pLetter = 'y';
75909 pAxis = ya;
75910 vLetter = 'x';
75911 vAxis = xa;
75912 } else {
75913 vVal = yval;
75914 pVal = xval;
75915 dx = dPos;
75916 dy = dVal;
75917 pLetter = 'x';
75918 pAxis = xa;
75919 vLetter = 'y';
75920 vAxis = ya;
75921 }
75922
75923 // if two boxes are overlaying, let the narrowest one win
75924 var pseudoDistance = Math.min(1, boxDelta / Math.abs(pAxis.r2c(pAxis.range[1]) - pAxis.r2c(pAxis.range[0])));
75925 hoverPseudoDistance = pointData.maxHoverDistance - pseudoDistance;
75926 spikePseudoDistance = pointData.maxSpikeDistance - pseudoDistance;
75927
75928 function dxy(di) { return (dx(di) + dy(di)) / 2; }
75929 var distfn = Fx.getDistanceFunction(hovermode, dx, dy, dxy);
75930 Fx.getClosest(cd, distfn, pointData);
75931
75932 // skip the rest (for this trace) if we didn't find a close point
75933 // and create the item(s) in closedata for this point
75934 if(pointData.index === false) return [];
75935
75936 var di = cd[pointData.index];
75937 var lc = trace.line.color;
75938 var mc = (trace.marker || {}).color;
75939
75940 if(Color.opacity(lc) && trace.line.width) pointData.color = lc;
75941 else if(Color.opacity(mc) && trace.boxpoints) pointData.color = mc;
75942 else pointData.color = trace.fillcolor;
75943
75944 pointData[pLetter + '0'] = pAxis.c2p(di.pos + t.bPos - boxDeltaNeg, true);
75945 pointData[pLetter + '1'] = pAxis.c2p(di.pos + t.bPos + boxDeltaPos, true);
75946
75947 pointData[pLetter + 'LabelVal'] = di.pos;
75948
75949 var spikePosAttr = pLetter + 'Spike';
75950 pointData.spikeDistance = dxy(di) * spikePseudoDistance / hoverPseudoDistance;
75951 pointData[spikePosAttr] = pAxis.c2p(di.pos, true);
75952
75953 // box plots: each "point" gets many labels
75954 var usedVals = {};
75955 var attrs = ['med', 'q1', 'q3', 'min', 'max'];
75956
75957 if(trace.boxmean || (trace.meanline || {}).visible) {
75958 attrs.push('mean');
75959 }
75960 if(trace.boxpoints || trace.points) {
75961 attrs.push('lf', 'uf');
75962 }
75963
75964 for(var i = 0; i < attrs.length; i++) {
75965 var attr = attrs[i];
75966
75967 if(!(attr in di) || (di[attr] in usedVals)) continue;
75968 usedVals[di[attr]] = true;
75969
75970 // copy out to a new object for each value to label
75971 var val = di[attr];
75972 var valPx = vAxis.c2p(val, true);
75973 var pointData2 = Lib.extendFlat({}, pointData);
75974
75975 pointData2.attr = attr;
75976 pointData2[vLetter + '0'] = pointData2[vLetter + '1'] = valPx;
75977 pointData2[vLetter + 'LabelVal'] = val;
75978 pointData2[vLetter + 'Label'] = (t.labels ? t.labels[attr] + ' ' : '') + Axes.hoverLabelText(vAxis, val);
75979
75980 // Note: introduced to be able to distinguish a
75981 // clicked point from a box during click-to-select
75982 pointData2.hoverOnBox = true;
75983
75984 if(attr === 'mean' && ('sd' in di) && trace.boxmean === 'sd') {
75985 pointData2[vLetter + 'err'] = di.sd;
75986 }
75987
75988 // only keep name and spikes on the first item (median)
75989 pointData.name = '';
75990 pointData.spikeDistance = undefined;
75991 pointData[spikePosAttr] = undefined;
75992
75993 // no hovertemplate support yet
75994 pointData2.hovertemplate = false;
75995
75996 closeBoxData.push(pointData2);
75997 }
75998
75999 return closeBoxData;
76000}
76001
76002function hoverOnPoints(pointData, xval, yval) {
76003 var cd = pointData.cd;
76004 var xa = pointData.xa;
76005 var ya = pointData.ya;
76006 var trace = cd[0].trace;
76007 var xPx = xa.c2p(xval);
76008 var yPx = ya.c2p(yval);
76009 var closePtData;
76010
76011 var dx = function(di) {
76012 var rad = Math.max(3, di.mrc || 0);
76013 return Math.max(Math.abs(xa.c2p(di.x) - xPx) - rad, 1 - 3 / rad);
76014 };
76015 var dy = function(di) {
76016 var rad = Math.max(3, di.mrc || 0);
76017 return Math.max(Math.abs(ya.c2p(di.y) - yPx) - rad, 1 - 3 / rad);
76018 };
76019 var distfn = Fx.quadrature(dx, dy);
76020
76021 // show one point per trace
76022 var ijClosest = false;
76023 var di, pt;
76024
76025 for(var i = 0; i < cd.length; i++) {
76026 di = cd[i];
76027
76028 for(var j = 0; j < (di.pts || []).length; j++) {
76029 pt = di.pts[j];
76030
76031 var newDistance = distfn(pt);
76032 if(newDistance <= pointData.distance) {
76033 pointData.distance = newDistance;
76034 ijClosest = [i, j];
76035 }
76036 }
76037 }
76038
76039 if(!ijClosest) return false;
76040
76041 di = cd[ijClosest[0]];
76042 pt = di.pts[ijClosest[1]];
76043
76044 var xc = xa.c2p(pt.x, true);
76045 var yc = ya.c2p(pt.y, true);
76046 var rad = pt.mrc || 1;
76047
76048 closePtData = Lib.extendFlat({}, pointData, {
76049 // corresponds to index in x/y input data array
76050 index: pt.i,
76051 color: (trace.marker || {}).color,
76052 name: trace.name,
76053 x0: xc - rad,
76054 x1: xc + rad,
76055 y0: yc - rad,
76056 y1: yc + rad,
76057 spikeDistance: pointData.distance,
76058 hovertemplate: trace.hovertemplate
76059 });
76060
76061 var pa;
76062 if(trace.orientation === 'h') {
76063 pa = ya;
76064 closePtData.xLabelVal = pt.x;
76065 closePtData.yLabelVal = di.pos;
76066 } else {
76067 pa = xa;
76068 closePtData.xLabelVal = di.pos;
76069 closePtData.yLabelVal = pt.y;
76070 }
76071
76072 var pLetter = pa._id.charAt(0);
76073 closePtData[pLetter + 'Spike'] = pa.c2p(di.pos, true);
76074
76075 fillText(pt, trace, closePtData);
76076
76077 return closePtData;
76078}
76079
76080module.exports = {
76081 hoverPoints: hoverPoints,
76082 hoverOnBoxes: hoverOnBoxes,
76083 hoverOnPoints: hoverOnPoints
76084};
76085
76086},{"../../components/color":52,"../../components/fx":92,"../../lib":178,"../../plots/cartesian/axes":222}],302:[function(_dereq_,module,exports){
76087/**
76088* Copyright 2012-2020, Plotly, Inc.
76089* All rights reserved.
76090*
76091* This source code is licensed under the MIT license found in the
76092* LICENSE file in the root directory of this source tree.
76093*/
76094
76095'use strict';
76096
76097module.exports = {
76098 attributes: _dereq_('./attributes'),
76099 layoutAttributes: _dereq_('./layout_attributes'),
76100 supplyDefaults: _dereq_('./defaults').supplyDefaults,
76101 crossTraceDefaults: _dereq_('./defaults').crossTraceDefaults,
76102 supplyLayoutDefaults: _dereq_('./layout_defaults').supplyLayoutDefaults,
76103 calc: _dereq_('./calc'),
76104 crossTraceCalc: _dereq_('./cross_trace_calc').crossTraceCalc,
76105 plot: _dereq_('./plot').plot,
76106 style: _dereq_('./style').style,
76107 styleOnSelect: _dereq_('./style').styleOnSelect,
76108 hoverPoints: _dereq_('./hover').hoverPoints,
76109 eventData: _dereq_('./event_data'),
76110 selectPoints: _dereq_('./select'),
76111
76112 moduleType: 'trace',
76113 name: 'box',
76114 basePlotModule: _dereq_('../../plots/cartesian'),
76115 categories: ['cartesian', 'svg', 'symbols', 'oriented', 'box-violin', 'showLegend', 'boxLayout', 'zoomScale'],
76116 meta: {
76117
76118 }
76119};
76120
76121},{"../../plots/cartesian":235,"./attributes":296,"./calc":297,"./cross_trace_calc":298,"./defaults":299,"./event_data":300,"./hover":301,"./layout_attributes":303,"./layout_defaults":304,"./plot":305,"./select":306,"./style":307}],303:[function(_dereq_,module,exports){
76122/**
76123* Copyright 2012-2020, Plotly, Inc.
76124* All rights reserved.
76125*
76126* This source code is licensed under the MIT license found in the
76127* LICENSE file in the root directory of this source tree.
76128*/
76129
76130'use strict';
76131
76132
76133module.exports = {
76134 boxmode: {
76135 valType: 'enumerated',
76136 values: ['group', 'overlay'],
76137 dflt: 'overlay',
76138
76139 editType: 'calc',
76140
76141 },
76142 boxgap: {
76143 valType: 'number',
76144 min: 0,
76145 max: 1,
76146 dflt: 0.3,
76147
76148 editType: 'calc',
76149
76150 },
76151 boxgroupgap: {
76152 valType: 'number',
76153 min: 0,
76154 max: 1,
76155 dflt: 0.3,
76156
76157 editType: 'calc',
76158
76159 }
76160};
76161
76162},{}],304:[function(_dereq_,module,exports){
76163/**
76164* Copyright 2012-2020, Plotly, Inc.
76165* All rights reserved.
76166*
76167* This source code is licensed under the MIT license found in the
76168* LICENSE file in the root directory of this source tree.
76169*/
76170
76171'use strict';
76172
76173var Registry = _dereq_('../../registry');
76174var Lib = _dereq_('../../lib');
76175var layoutAttributes = _dereq_('./layout_attributes');
76176
76177function _supply(layoutIn, layoutOut, fullData, coerce, traceType) {
76178 var category = traceType + 'Layout';
76179 var hasTraceType = false;
76180
76181 for(var i = 0; i < fullData.length; i++) {
76182 var trace = fullData[i];
76183
76184 if(Registry.traceIs(trace, category)) {
76185 hasTraceType = true;
76186 break;
76187 }
76188 }
76189 if(!hasTraceType) return;
76190
76191 coerce(traceType + 'mode');
76192 coerce(traceType + 'gap');
76193 coerce(traceType + 'groupgap');
76194}
76195
76196function supplyLayoutDefaults(layoutIn, layoutOut, fullData) {
76197 function coerce(attr, dflt) {
76198 return Lib.coerce(layoutIn, layoutOut, layoutAttributes, attr, dflt);
76199 }
76200 _supply(layoutIn, layoutOut, fullData, coerce, 'box');
76201}
76202
76203module.exports = {
76204 supplyLayoutDefaults: supplyLayoutDefaults,
76205 _supply: _supply
76206};
76207
76208},{"../../lib":178,"../../registry":269,"./layout_attributes":303}],305:[function(_dereq_,module,exports){
76209/**
76210* Copyright 2012-2020, Plotly, Inc.
76211* All rights reserved.
76212*
76213* This source code is licensed under the MIT license found in the
76214* LICENSE file in the root directory of this source tree.
76215*/
76216
76217'use strict';
76218
76219var d3 = _dereq_('d3');
76220
76221var Lib = _dereq_('../../lib');
76222var Drawing = _dereq_('../../components/drawing');
76223
76224// constants for dynamic jitter (ie less jitter for sparser points)
76225var JITTERCOUNT = 5; // points either side of this to include
76226var JITTERSPREAD = 0.01; // fraction of IQR to count as "dense"
76227
76228function plot(gd, plotinfo, cdbox, boxLayer) {
76229 var xa = plotinfo.xaxis;
76230 var ya = plotinfo.yaxis;
76231
76232 Lib.makeTraceGroups(boxLayer, cdbox, 'trace boxes').each(function(cd) {
76233 var plotGroup = d3.select(this);
76234 var cd0 = cd[0];
76235 var t = cd0.t;
76236 var trace = cd0.trace;
76237
76238 // whisker width
76239 t.wdPos = t.bdPos * trace.whiskerwidth;
76240
76241 if(trace.visible !== true || t.empty) {
76242 plotGroup.remove();
76243 return;
76244 }
76245
76246 var posAxis, valAxis;
76247
76248 if(trace.orientation === 'h') {
76249 posAxis = ya;
76250 valAxis = xa;
76251 } else {
76252 posAxis = xa;
76253 valAxis = ya;
76254 }
76255
76256 plotBoxAndWhiskers(plotGroup, {pos: posAxis, val: valAxis}, trace, t);
76257 plotPoints(plotGroup, {x: xa, y: ya}, trace, t);
76258 plotBoxMean(plotGroup, {pos: posAxis, val: valAxis}, trace, t);
76259 });
76260}
76261
76262function plotBoxAndWhiskers(sel, axes, trace, t) {
76263 var posAxis = axes.pos;
76264 var valAxis = axes.val;
76265 var bPos = t.bPos;
76266 var wdPos = t.wdPos || 0;
76267 var bPosPxOffset = t.bPosPxOffset || 0;
76268 var whiskerWidth = trace.whiskerwidth || 0;
76269 var notched = trace.notched || false;
76270 var nw = notched ? 1 - 2 * trace.notchwidth : 1;
76271
76272 // to support for one-sided box
76273 var bdPos0;
76274 var bdPos1;
76275 if(Array.isArray(t.bdPos)) {
76276 bdPos0 = t.bdPos[0];
76277 bdPos1 = t.bdPos[1];
76278 } else {
76279 bdPos0 = t.bdPos;
76280 bdPos1 = t.bdPos;
76281 }
76282
76283 var paths = sel.selectAll('path.box').data((
76284 trace.type !== 'violin' ||
76285 trace.box.visible
76286 ) ? Lib.identity : []);
76287
76288 paths.enter().append('path')
76289 .style('vector-effect', 'non-scaling-stroke')
76290 .attr('class', 'box');
76291
76292 paths.exit().remove();
76293
76294 paths.each(function(d) {
76295 if(d.empty) return 'M0,0Z';
76296
76297 var lcenter = posAxis.c2l(d.pos + bPos, true);
76298 var posc = posAxis.l2p(lcenter) + bPosPxOffset;
76299 var pos0 = posAxis.l2p(lcenter - bdPos0) + bPosPxOffset;
76300 var pos1 = posAxis.l2p(lcenter + bdPos1) + bPosPxOffset;
76301 var posw0 = posAxis.l2p(lcenter - wdPos) + bPosPxOffset;
76302 var posw1 = posAxis.l2p(lcenter + wdPos) + bPosPxOffset;
76303 var posm0 = posAxis.l2p(lcenter - bdPos0 * nw) + bPosPxOffset;
76304 var posm1 = posAxis.l2p(lcenter + bdPos1 * nw) + bPosPxOffset;
76305 var q1 = valAxis.c2p(d.q1, true);
76306 var q3 = valAxis.c2p(d.q3, true);
76307 // make sure median isn't identical to either of the
76308 // quartiles, so we can see it
76309 var m = Lib.constrain(
76310 valAxis.c2p(d.med, true),
76311 Math.min(q1, q3) + 1, Math.max(q1, q3) - 1
76312 );
76313
76314 // for compatibility with box, violin, and candlestick
76315 // perhaps we should put this into cd0.t instead so it's more explicit,
76316 // but what we have now is:
76317 // - box always has d.lf, but boxpoints can be anything
76318 // - violin has d.lf and should always use it (boxpoints is undefined)
76319 // - candlestick has only min/max
76320 var useExtremes = (d.lf === undefined) || (trace.boxpoints === false);
76321 var lf = valAxis.c2p(useExtremes ? d.min : d.lf, true);
76322 var uf = valAxis.c2p(useExtremes ? d.max : d.uf, true);
76323 var ln = valAxis.c2p(d.ln, true);
76324 var un = valAxis.c2p(d.un, true);
76325
76326 if(trace.orientation === 'h') {
76327 d3.select(this).attr('d',
76328 'M' + m + ',' + posm0 + 'V' + posm1 + // median line
76329 'M' + q1 + ',' + pos0 + 'V' + pos1 + // left edge
76330 (notched ? 'H' + ln + 'L' + m + ',' + posm1 + 'L' + un + ',' + pos1 : '') + // top notched edge
76331 'H' + q3 + // end of the top edge
76332 'V' + pos0 + // right edge
76333 (notched ? 'H' + un + 'L' + m + ',' + posm0 + 'L' + ln + ',' + pos0 : '') + // bottom notched edge
76334 'Z' + // end of the box
76335 'M' + q1 + ',' + posc + 'H' + lf + 'M' + q3 + ',' + posc + 'H' + uf + // whiskers
76336 ((whiskerWidth === 0) ? '' : // whisker caps
76337 'M' + lf + ',' + posw0 + 'V' + posw1 + 'M' + uf + ',' + posw0 + 'V' + posw1));
76338 } else {
76339 d3.select(this).attr('d',
76340 'M' + posm0 + ',' + m + 'H' + posm1 + // median line
76341 'M' + pos0 + ',' + q1 + 'H' + pos1 + // top of the box
76342 (notched ? 'V' + ln + 'L' + posm1 + ',' + m + 'L' + pos1 + ',' + un : '') + // notched right edge
76343 'V' + q3 + // end of the right edge
76344 'H' + pos0 + // bottom of the box
76345 (notched ? 'V' + un + 'L' + posm0 + ',' + m + 'L' + pos0 + ',' + ln : '') + // notched left edge
76346 'Z' + // end of the box
76347 'M' + posc + ',' + q1 + 'V' + lf + 'M' + posc + ',' + q3 + 'V' + uf + // whiskers
76348 ((whiskerWidth === 0) ? '' : // whisker caps
76349 'M' + posw0 + ',' + lf + 'H' + posw1 + 'M' + posw0 + ',' + uf + 'H' + posw1));
76350 }
76351 });
76352}
76353
76354function plotPoints(sel, axes, trace, t) {
76355 var xa = axes.x;
76356 var ya = axes.y;
76357 var bdPos = t.bdPos;
76358 var bPos = t.bPos;
76359
76360 // to support violin points
76361 var mode = trace.boxpoints || trace.points;
76362
76363 // repeatable pseudo-random number generator
76364 Lib.seedPseudoRandom();
76365
76366 // since box plot points get an extra level of nesting, each
76367 // box needs the trace styling info
76368 var fn = function(d) {
76369 d.forEach(function(v) {
76370 v.t = t;
76371 v.trace = trace;
76372 });
76373 return d;
76374 };
76375
76376 var gPoints = sel.selectAll('g.points')
76377 .data(mode ? fn : []);
76378
76379 gPoints.enter().append('g')
76380 .attr('class', 'points');
76381
76382 gPoints.exit().remove();
76383
76384 var paths = gPoints.selectAll('path')
76385 .data(function(d) {
76386 var i;
76387 var pts = d.pts2;
76388
76389 // normally use IQR, but if this is 0 or too small, use max-min
76390 var typicalSpread = Math.max((d.max - d.min) / 10, d.q3 - d.q1);
76391 var minSpread = typicalSpread * 1e-9;
76392 var spreadLimit = typicalSpread * JITTERSPREAD;
76393 var jitterFactors = [];
76394 var maxJitterFactor = 0;
76395 var newJitter;
76396
76397 // dynamic jitter
76398 if(trace.jitter) {
76399 if(typicalSpread === 0) {
76400 // edge case of no spread at all: fall back to max jitter
76401 maxJitterFactor = 1;
76402 jitterFactors = new Array(pts.length);
76403 for(i = 0; i < pts.length; i++) {
76404 jitterFactors[i] = 1;
76405 }
76406 } else {
76407 for(i = 0; i < pts.length; i++) {
76408 var i0 = Math.max(0, i - JITTERCOUNT);
76409 var pmin = pts[i0].v;
76410 var i1 = Math.min(pts.length - 1, i + JITTERCOUNT);
76411 var pmax = pts[i1].v;
76412
76413 if(mode !== 'all') {
76414 if(pts[i].v < d.lf) pmax = Math.min(pmax, d.lf);
76415 else pmin = Math.max(pmin, d.uf);
76416 }
76417
76418 var jitterFactor = Math.sqrt(spreadLimit * (i1 - i0) / (pmax - pmin + minSpread)) || 0;
76419 jitterFactor = Lib.constrain(Math.abs(jitterFactor), 0, 1);
76420
76421 jitterFactors.push(jitterFactor);
76422 maxJitterFactor = Math.max(jitterFactor, maxJitterFactor);
76423 }
76424 }
76425 newJitter = trace.jitter * 2 / (maxJitterFactor || 1);
76426 }
76427
76428 // fills in 'x' and 'y' in calcdata 'pts' item
76429 for(i = 0; i < pts.length; i++) {
76430 var pt = pts[i];
76431 var v = pt.v;
76432
76433 var jitterOffset = trace.jitter ?
76434 (newJitter * jitterFactors[i] * (Lib.pseudoRandom() - 0.5)) :
76435 0;
76436
76437 var posPx = d.pos + bPos + bdPos * (trace.pointpos + jitterOffset);
76438
76439 if(trace.orientation === 'h') {
76440 pt.y = posPx;
76441 pt.x = v;
76442 } else {
76443 pt.x = posPx;
76444 pt.y = v;
76445 }
76446
76447 // tag suspected outliers
76448 if(mode === 'suspectedoutliers' && v < d.uo && v > d.lo) {
76449 pt.so = true;
76450 }
76451 }
76452
76453 return pts;
76454 });
76455
76456 paths.enter().append('path')
76457 .classed('point', true);
76458
76459 paths.exit().remove();
76460
76461 paths.call(Drawing.translatePoints, xa, ya);
76462}
76463
76464function plotBoxMean(sel, axes, trace, t) {
76465 var posAxis = axes.pos;
76466 var valAxis = axes.val;
76467 var bPos = t.bPos;
76468 var bPosPxOffset = t.bPosPxOffset || 0;
76469
76470 // to support violin mean lines
76471 var mode = trace.boxmean || (trace.meanline || {}).visible;
76472
76473 // to support for one-sided box
76474 var bdPos0;
76475 var bdPos1;
76476 if(Array.isArray(t.bdPos)) {
76477 bdPos0 = t.bdPos[0];
76478 bdPos1 = t.bdPos[1];
76479 } else {
76480 bdPos0 = t.bdPos;
76481 bdPos1 = t.bdPos;
76482 }
76483
76484 var paths = sel.selectAll('path.mean').data((
76485 (trace.type === 'box' && trace.boxmean) ||
76486 (trace.type === 'violin' && trace.box.visible && trace.meanline.visible)
76487 ) ? Lib.identity : []);
76488
76489 paths.enter().append('path')
76490 .attr('class', 'mean')
76491 .style({
76492 fill: 'none',
76493 'vector-effect': 'non-scaling-stroke'
76494 });
76495
76496 paths.exit().remove();
76497
76498 paths.each(function(d) {
76499 var lcenter = posAxis.c2l(d.pos + bPos, true);
76500 var posc = posAxis.l2p(lcenter) + bPosPxOffset;
76501 var pos0 = posAxis.l2p(lcenter - bdPos0) + bPosPxOffset;
76502 var pos1 = posAxis.l2p(lcenter + bdPos1) + bPosPxOffset;
76503 var m = valAxis.c2p(d.mean, true);
76504 var sl = valAxis.c2p(d.mean - d.sd, true);
76505 var sh = valAxis.c2p(d.mean + d.sd, true);
76506
76507 if(trace.orientation === 'h') {
76508 d3.select(this).attr('d',
76509 'M' + m + ',' + pos0 + 'V' + pos1 +
76510 (mode === 'sd' ?
76511 'm0,0L' + sl + ',' + posc + 'L' + m + ',' + pos0 + 'L' + sh + ',' + posc + 'Z' :
76512 '')
76513 );
76514 } else {
76515 d3.select(this).attr('d',
76516 'M' + pos0 + ',' + m + 'H' + pos1 +
76517 (mode === 'sd' ?
76518 'm0,0L' + posc + ',' + sl + 'L' + pos0 + ',' + m + 'L' + posc + ',' + sh + 'Z' :
76519 '')
76520 );
76521 }
76522 });
76523}
76524
76525module.exports = {
76526 plot: plot,
76527 plotBoxAndWhiskers: plotBoxAndWhiskers,
76528 plotPoints: plotPoints,
76529 plotBoxMean: plotBoxMean
76530};
76531
76532},{"../../components/drawing":74,"../../lib":178,"d3":16}],306:[function(_dereq_,module,exports){
76533/**
76534* Copyright 2012-2020, Plotly, Inc.
76535* All rights reserved.
76536*
76537* This source code is licensed under the MIT license found in the
76538* LICENSE file in the root directory of this source tree.
76539*/
76540
76541'use strict';
76542
76543module.exports = function selectPoints(searchInfo, selectionTester) {
76544 var cd = searchInfo.cd;
76545 var xa = searchInfo.xaxis;
76546 var ya = searchInfo.yaxis;
76547 var selection = [];
76548 var i, j;
76549
76550 if(selectionTester === false) {
76551 for(i = 0; i < cd.length; i++) {
76552 for(j = 0; j < (cd[i].pts || []).length; j++) {
76553 // clear selection
76554 cd[i].pts[j].selected = 0;
76555 }
76556 }
76557 } else {
76558 for(i = 0; i < cd.length; i++) {
76559 for(j = 0; j < (cd[i].pts || []).length; j++) {
76560 var pt = cd[i].pts[j];
76561 var x = xa.c2p(pt.x);
76562 var y = ya.c2p(pt.y);
76563
76564 if(selectionTester.contains([x, y], null, pt.i, searchInfo)) {
76565 selection.push({
76566 pointNumber: pt.i,
76567 x: xa.c2d(pt.x),
76568 y: ya.c2d(pt.y)
76569 });
76570 pt.selected = 1;
76571 } else {
76572 pt.selected = 0;
76573 }
76574 }
76575 }
76576 }
76577
76578 return selection;
76579};
76580
76581},{}],307:[function(_dereq_,module,exports){
76582/**
76583* Copyright 2012-2020, Plotly, Inc.
76584* All rights reserved.
76585*
76586* This source code is licensed under the MIT license found in the
76587* LICENSE file in the root directory of this source tree.
76588*/
76589
76590'use strict';
76591
76592var d3 = _dereq_('d3');
76593var Color = _dereq_('../../components/color');
76594var Drawing = _dereq_('../../components/drawing');
76595
76596function style(gd, cd, sel) {
76597 var s = sel ? sel : d3.select(gd).selectAll('g.trace.boxes');
76598
76599 s.style('opacity', function(d) { return d[0].trace.opacity; });
76600
76601 s.each(function(d) {
76602 var el = d3.select(this);
76603 var trace = d[0].trace;
76604 var lineWidth = trace.line.width;
76605
76606 function styleBox(boxSel, lineWidth, lineColor, fillColor) {
76607 boxSel.style('stroke-width', lineWidth + 'px')
76608 .call(Color.stroke, lineColor)
76609 .call(Color.fill, fillColor);
76610 }
76611
76612 var allBoxes = el.selectAll('path.box');
76613
76614 if(trace.type === 'candlestick') {
76615 allBoxes.each(function(boxData) {
76616 if(boxData.empty) return;
76617
76618 var thisBox = d3.select(this);
76619 var container = trace[boxData.dir]; // dir = 'increasing' or 'decreasing'
76620 styleBox(thisBox, container.line.width, container.line.color, container.fillcolor);
76621 // TODO: custom selection style for candlesticks
76622 thisBox.style('opacity', trace.selectedpoints && !boxData.selected ? 0.3 : 1);
76623 });
76624 } else {
76625 styleBox(allBoxes, lineWidth, trace.line.color, trace.fillcolor);
76626 el.selectAll('path.mean')
76627 .style({
76628 'stroke-width': lineWidth,
76629 'stroke-dasharray': (2 * lineWidth) + 'px,' + lineWidth + 'px'
76630 })
76631 .call(Color.stroke, trace.line.color);
76632
76633 var pts = el.selectAll('path.point');
76634 Drawing.pointStyle(pts, trace, gd);
76635 }
76636 });
76637}
76638
76639function styleOnSelect(gd, cd, sel) {
76640 var trace = cd[0].trace;
76641 var pts = sel.selectAll('path.point');
76642
76643 if(trace.selectedpoints) {
76644 Drawing.selectedPointStyle(pts, trace);
76645 } else {
76646 Drawing.pointStyle(pts, trace, gd);
76647 }
76648}
76649
76650module.exports = {
76651 style: style,
76652 styleOnSelect: styleOnSelect
76653};
76654
76655},{"../../components/color":52,"../../components/drawing":74,"d3":16}],308:[function(_dereq_,module,exports){
76656/**
76657* Copyright 2012-2020, Plotly, Inc.
76658* All rights reserved.
76659*
76660* This source code is licensed under the MIT license found in the
76661* LICENSE file in the root directory of this source tree.
76662*/
76663
76664'use strict';
76665
76666var heatmapAttrs = _dereq_('../heatmap/attributes');
76667var scatterAttrs = _dereq_('../scatter/attributes');
76668var colorScaleAttrs = _dereq_('../../components/colorscale/attributes');
76669var dash = _dereq_('../../components/drawing/attributes').dash;
76670var fontAttrs = _dereq_('../../plots/font_attributes');
76671var extendFlat = _dereq_('../../lib/extend').extendFlat;
76672
76673var filterOps = _dereq_('../../constants/filter_ops');
76674var COMPARISON_OPS2 = filterOps.COMPARISON_OPS2;
76675var INTERVAL_OPS = filterOps.INTERVAL_OPS;
76676
76677var FORMAT_LINK = _dereq_('../../constants/docs').FORMAT_LINK;
76678
76679var scatterLineAttrs = scatterAttrs.line;
76680
76681module.exports = extendFlat({
76682 z: heatmapAttrs.z,
76683 x: heatmapAttrs.x,
76684 x0: heatmapAttrs.x0,
76685 dx: heatmapAttrs.dx,
76686 y: heatmapAttrs.y,
76687 y0: heatmapAttrs.y0,
76688 dy: heatmapAttrs.dy,
76689 text: heatmapAttrs.text,
76690 hovertext: heatmapAttrs.hovertext,
76691 transpose: heatmapAttrs.transpose,
76692 xtype: heatmapAttrs.xtype,
76693 ytype: heatmapAttrs.ytype,
76694 zhoverformat: heatmapAttrs.zhoverformat,
76695 hovertemplate: heatmapAttrs.hovertemplate,
76696 hoverongaps: heatmapAttrs.hoverongaps,
76697 connectgaps: extendFlat({}, heatmapAttrs.connectgaps, {
76698
76699 }),
76700
76701 fillcolor: {
76702 valType: 'color',
76703
76704 editType: 'calc',
76705
76706 },
76707
76708 autocontour: {
76709 valType: 'boolean',
76710 dflt: true,
76711
76712 editType: 'calc',
76713 impliedEdits: {
76714 'contours.start': undefined,
76715 'contours.end': undefined,
76716 'contours.size': undefined
76717 },
76718
76719 },
76720 ncontours: {
76721 valType: 'integer',
76722 dflt: 15,
76723 min: 1,
76724
76725 editType: 'calc',
76726
76727 },
76728
76729 contours: {
76730 type: {
76731 valType: 'enumerated',
76732 values: ['levels', 'constraint'],
76733 dflt: 'levels',
76734
76735 editType: 'calc',
76736
76737 },
76738 start: {
76739 valType: 'number',
76740 dflt: null,
76741
76742 editType: 'plot',
76743 impliedEdits: {'^autocontour': false},
76744
76745 },
76746 end: {
76747 valType: 'number',
76748 dflt: null,
76749
76750 editType: 'plot',
76751 impliedEdits: {'^autocontour': false},
76752
76753 },
76754 size: {
76755 valType: 'number',
76756 dflt: null,
76757 min: 0,
76758
76759 editType: 'plot',
76760 impliedEdits: {'^autocontour': false},
76761
76762 },
76763 coloring: {
76764 valType: 'enumerated',
76765 values: ['fill', 'heatmap', 'lines', 'none'],
76766 dflt: 'fill',
76767
76768 editType: 'calc',
76769
76770 },
76771 showlines: {
76772 valType: 'boolean',
76773 dflt: true,
76774
76775 editType: 'plot',
76776
76777 },
76778 showlabels: {
76779 valType: 'boolean',
76780 dflt: false,
76781
76782 editType: 'plot',
76783
76784 },
76785 labelfont: fontAttrs({
76786 editType: 'plot',
76787 colorEditType: 'style',
76788
76789 }),
76790 labelformat: {
76791 valType: 'string',
76792 dflt: '',
76793
76794 editType: 'plot',
76795
76796 },
76797 operation: {
76798 valType: 'enumerated',
76799 values: [].concat(COMPARISON_OPS2).concat(INTERVAL_OPS),
76800
76801 dflt: '=',
76802 editType: 'calc',
76803
76804 },
76805 value: {
76806 valType: 'any',
76807 dflt: 0,
76808
76809 editType: 'calc',
76810
76811 },
76812 editType: 'calc',
76813 impliedEdits: {'autocontour': false}
76814 },
76815
76816 line: {
76817 color: extendFlat({}, scatterLineAttrs.color, {
76818 editType: 'style+colorbars',
76819
76820 }),
76821 width: {
76822 valType: 'number',
76823 min: 0,
76824
76825 editType: 'style+colorbars',
76826
76827 },
76828 dash: dash,
76829 smoothing: extendFlat({}, scatterLineAttrs.smoothing, {
76830
76831 }),
76832 editType: 'plot'
76833 }
76834},
76835 colorScaleAttrs('', {
76836 cLetter: 'z',
76837 autoColorDflt: false,
76838 editTypeOverride: 'calc'
76839 })
76840);
76841
76842},{"../../components/colorscale/attributes":59,"../../components/drawing/attributes":73,"../../constants/docs":155,"../../constants/filter_ops":156,"../../lib/extend":173,"../../plots/font_attributes":250,"../heatmap/attributes":330,"../scatter/attributes":389}],309:[function(_dereq_,module,exports){
76843/**
76844* Copyright 2012-2020, Plotly, Inc.
76845* All rights reserved.
76846*
76847* This source code is licensed under the MIT license found in the
76848* LICENSE file in the root directory of this source tree.
76849*/
76850
76851'use strict';
76852
76853var Colorscale = _dereq_('../../components/colorscale');
76854
76855var heatmapCalc = _dereq_('../heatmap/calc');
76856var setContours = _dereq_('./set_contours');
76857var endPlus = _dereq_('./end_plus');
76858
76859// most is the same as heatmap calc, then adjust it
76860// though a few things inside heatmap calc still look for
76861// contour maps, because the makeBoundArray calls are too entangled
76862module.exports = function calc(gd, trace) {
76863 var cd = heatmapCalc(gd, trace);
76864
76865 var zOut = cd[0].z;
76866 setContours(trace, zOut);
76867
76868 var contours = trace.contours;
76869 var cOpts = Colorscale.extractOpts(trace);
76870 var cVals;
76871
76872 if(contours.coloring === 'heatmap' && cOpts.auto && trace.autocontour === false) {
76873 var start = contours.start;
76874 var end = endPlus(contours);
76875 var cs = contours.size || 1;
76876 var nc = Math.floor((end - start) / cs) + 1;
76877
76878 if(!isFinite(cs)) {
76879 cs = 1;
76880 nc = 1;
76881 }
76882
76883 var min0 = start - cs / 2;
76884 var max0 = min0 + nc * cs;
76885 cVals = [min0, max0];
76886 } else {
76887 cVals = zOut;
76888 }
76889
76890 Colorscale.calc(gd, trace, {vals: cVals, cLetter: 'z'});
76891
76892 return cd;
76893};
76894
76895},{"../../components/colorscale":64,"../heatmap/calc":331,"./end_plus":319,"./set_contours":327}],310:[function(_dereq_,module,exports){
76896/**
76897* Copyright 2012-2020, Plotly, Inc.
76898* All rights reserved.
76899*
76900* This source code is licensed under the MIT license found in the
76901* LICENSE file in the root directory of this source tree.
76902*/
76903
76904'use strict';
76905
76906module.exports = function(pathinfo, contours) {
76907 var pi0 = pathinfo[0];
76908 var z = pi0.z;
76909 var i;
76910
76911 switch(contours.type) {
76912 case 'levels':
76913 // Why (just) use z[0][0] and z[0][1]?
76914 //
76915 // N.B. using boundaryMin instead of edgeVal2 here makes the
76916 // `contour_scatter` mock fail
76917 var edgeVal2 = Math.min(z[0][0], z[0][1]);
76918
76919 for(i = 0; i < pathinfo.length; i++) {
76920 var pi = pathinfo[i];
76921 pi.prefixBoundary = !pi.edgepaths.length &&
76922 (edgeVal2 > pi.level || pi.starts.length && edgeVal2 === pi.level);
76923 }
76924 break;
76925 case 'constraint':
76926 // after convertToConstraints, pathinfo has length=0
76927 pi0.prefixBoundary = false;
76928
76929 // joinAllPaths does enough already when edgepaths are present
76930 if(pi0.edgepaths.length) return;
76931
76932 var na = pi0.x.length;
76933 var nb = pi0.y.length;
76934 var boundaryMax = -Infinity;
76935 var boundaryMin = Infinity;
76936
76937 for(i = 0; i < nb; i++) {
76938 boundaryMin = Math.min(boundaryMin, z[i][0]);
76939 boundaryMin = Math.min(boundaryMin, z[i][na - 1]);
76940 boundaryMax = Math.max(boundaryMax, z[i][0]);
76941 boundaryMax = Math.max(boundaryMax, z[i][na - 1]);
76942 }
76943 for(i = 1; i < na - 1; i++) {
76944 boundaryMin = Math.min(boundaryMin, z[0][i]);
76945 boundaryMin = Math.min(boundaryMin, z[nb - 1][i]);
76946 boundaryMax = Math.max(boundaryMax, z[0][i]);
76947 boundaryMax = Math.max(boundaryMax, z[nb - 1][i]);
76948 }
76949
76950 var contoursValue = contours.value;
76951 var v1, v2;
76952
76953 switch(contours._operation) {
76954 case '>':
76955 if(contoursValue > boundaryMax) {
76956 pi0.prefixBoundary = true;
76957 }
76958 break;
76959 case '<':
76960 if(contoursValue < boundaryMin ||
76961 (pi0.starts.length && contoursValue === boundaryMin)) {
76962 pi0.prefixBoundary = true;
76963 }
76964 break;
76965 case '[]':
76966 v1 = Math.min(contoursValue[0], contoursValue[1]);
76967 v2 = Math.max(contoursValue[0], contoursValue[1]);
76968 if(v2 < boundaryMin || v1 > boundaryMax ||
76969 (pi0.starts.length && v2 === boundaryMin)) {
76970 pi0.prefixBoundary = true;
76971 }
76972 break;
76973 case '][':
76974 v1 = Math.min(contoursValue[0], contoursValue[1]);
76975 v2 = Math.max(contoursValue[0], contoursValue[1]);
76976 if(v1 < boundaryMin && v2 > boundaryMax) {
76977 pi0.prefixBoundary = true;
76978 }
76979 break;
76980 }
76981 break;
76982 }
76983};
76984
76985},{}],311:[function(_dereq_,module,exports){
76986/**
76987* Copyright 2012-2020, Plotly, Inc.
76988* All rights reserved.
76989*
76990* This source code is licensed under the MIT license found in the
76991* LICENSE file in the root directory of this source tree.
76992*/
76993
76994'use strict';
76995
76996var Colorscale = _dereq_('../../components/colorscale');
76997var makeColorMap = _dereq_('./make_color_map');
76998var endPlus = _dereq_('./end_plus');
76999
77000function calc(gd, trace, opts) {
77001 var contours = trace.contours;
77002 var line = trace.line;
77003 var cs = contours.size || 1;
77004 var coloring = contours.coloring;
77005 var colorMap = makeColorMap(trace, {isColorbar: true});
77006
77007 if(coloring === 'heatmap') {
77008 var cOpts = Colorscale.extractOpts(trace);
77009 opts._fillgradient = cOpts.reversescale ?
77010 Colorscale.flipScale(cOpts.colorscale) :
77011 cOpts.colorscale;
77012 opts._zrange = [cOpts.min, cOpts.max];
77013 } else if(coloring === 'fill') {
77014 opts._fillcolor = colorMap;
77015 }
77016
77017 opts._line = {
77018 color: coloring === 'lines' ? colorMap : line.color,
77019 width: contours.showlines !== false ? line.width : 0,
77020 dash: line.dash
77021 };
77022
77023 opts._levels = {
77024 start: contours.start,
77025 end: endPlus(contours),
77026 size: cs
77027 };
77028}
77029
77030module.exports = {
77031 min: 'zmin',
77032 max: 'zmax',
77033 calc: calc
77034};
77035
77036},{"../../components/colorscale":64,"./end_plus":319,"./make_color_map":324}],312:[function(_dereq_,module,exports){
77037/**
77038* Copyright 2012-2020, Plotly, Inc.
77039* All rights reserved.
77040*
77041* This source code is licensed under the MIT license found in the
77042* LICENSE file in the root directory of this source tree.
77043*/
77044
77045'use strict';
77046module.exports = {
77047 // some constants to help with marching squares algorithm
77048 // where does the path start for each index?
77049 BOTTOMSTART: [1, 9, 13, 104, 713],
77050 TOPSTART: [4, 6, 7, 104, 713],
77051 LEFTSTART: [8, 12, 14, 208, 1114],
77052 RIGHTSTART: [2, 3, 11, 208, 1114],
77053
77054 // which way [dx,dy] do we leave a given index?
77055 // saddles are already disambiguated
77056 NEWDELTA: [
77057 null, [-1, 0], [0, -1], [-1, 0],
77058 [1, 0], null, [0, -1], [-1, 0],
77059 [0, 1], [0, 1], null, [0, 1],
77060 [1, 0], [1, 0], [0, -1]
77061 ],
77062
77063 // for each saddle, the first index here is used
77064 // for dx||dy<0, the second for dx||dy>0
77065 CHOOSESADDLE: {
77066 104: [4, 1],
77067 208: [2, 8],
77068 713: [7, 13],
77069 1114: [11, 14]
77070 },
77071
77072 // after one index has been used for a saddle, which do we
77073 // substitute to be used up later?
77074 SADDLEREMAINDER: {1: 4, 2: 8, 4: 1, 7: 13, 8: 2, 11: 14, 13: 7, 14: 11},
77075
77076 // length of a contour, as a multiple of the plot area diagonal, per label
77077 LABELDISTANCE: 2,
77078
77079 // number of contour levels after which we start increasing the number of
77080 // labels we draw. Many contours means they will generally be close
77081 // together, so it will be harder to follow a long way to find a label
77082 LABELINCREASE: 10,
77083
77084 // minimum length of a contour line, as a multiple of the label length,
77085 // at which we draw *any* labels
77086 LABELMIN: 3,
77087
77088 // max number of labels to draw on a single contour path, no matter how long
77089 LABELMAX: 10,
77090
77091 // constants for the label position cost function
77092 LABELOPTIMIZER: {
77093 // weight given to edge proximity
77094 EDGECOST: 1,
77095 // weight given to the angle off horizontal
77096 ANGLECOST: 1,
77097 // weight given to distance from already-placed labels
77098 NEIGHBORCOST: 5,
77099 // cost multiplier for labels on the same level
77100 SAMELEVELFACTOR: 10,
77101 // minimum distance (as a multiple of the label length)
77102 // for labels on the same level
77103 SAMELEVELDISTANCE: 5,
77104 // maximum cost before we won't even place the label
77105 MAXCOST: 100,
77106 // number of evenly spaced points to look at in the first
77107 // iteration of the search
77108 INITIALSEARCHPOINTS: 10,
77109 // number of binary search iterations after the initial wide search
77110 ITERATIONS: 5
77111 }
77112};
77113
77114},{}],313:[function(_dereq_,module,exports){
77115/**
77116* Copyright 2012-2020, Plotly, Inc.
77117* All rights reserved.
77118*
77119* This source code is licensed under the MIT license found in the
77120* LICENSE file in the root directory of this source tree.
77121*/
77122
77123
77124'use strict';
77125var isNumeric = _dereq_('fast-isnumeric');
77126
77127var handleLabelDefaults = _dereq_('./label_defaults');
77128
77129var Color = _dereq_('../../components/color');
77130var addOpacity = Color.addOpacity;
77131var opacity = Color.opacity;
77132
77133var filterOps = _dereq_('../../constants/filter_ops');
77134var CONSTRAINT_REDUCTION = filterOps.CONSTRAINT_REDUCTION;
77135var COMPARISON_OPS2 = filterOps.COMPARISON_OPS2;
77136
77137module.exports = function handleConstraintDefaults(traceIn, traceOut, coerce, layout, defaultColor, opts) {
77138 var contours = traceOut.contours;
77139 var showLines, lineColor, fillColor;
77140
77141 var operation = coerce('contours.operation');
77142 contours._operation = CONSTRAINT_REDUCTION[operation];
77143
77144 handleConstraintValueDefaults(coerce, contours);
77145
77146 if(operation === '=') {
77147 showLines = contours.showlines = true;
77148 } else {
77149 showLines = coerce('contours.showlines');
77150 fillColor = coerce('fillcolor', addOpacity(
77151 (traceIn.line || {}).color || defaultColor, 0.5
77152 ));
77153 }
77154
77155 if(showLines) {
77156 var lineDfltColor = fillColor && opacity(fillColor) ?
77157 addOpacity(traceOut.fillcolor, 1) :
77158 defaultColor;
77159 lineColor = coerce('line.color', lineDfltColor);
77160 coerce('line.width', 2);
77161 coerce('line.dash');
77162 }
77163
77164 coerce('line.smoothing');
77165
77166 handleLabelDefaults(coerce, layout, lineColor, opts);
77167};
77168
77169function handleConstraintValueDefaults(coerce, contours) {
77170 var zvalue;
77171
77172 if(COMPARISON_OPS2.indexOf(contours.operation) === -1) {
77173 // Requires an array of two numbers:
77174 coerce('contours.value', [0, 1]);
77175
77176 if(!Array.isArray(contours.value)) {
77177 if(isNumeric(contours.value)) {
77178 zvalue = parseFloat(contours.value);
77179 contours.value = [zvalue, zvalue + 1];
77180 }
77181 } else if(contours.value.length > 2) {
77182 contours.value = contours.value.slice(2);
77183 } else if(contours.length === 0) {
77184 contours.value = [0, 1];
77185 } else if(contours.length < 2) {
77186 zvalue = parseFloat(contours.value[0]);
77187 contours.value = [zvalue, zvalue + 1];
77188 } else {
77189 contours.value = [
77190 parseFloat(contours.value[0]),
77191 parseFloat(contours.value[1])
77192 ];
77193 }
77194 } else {
77195 // Requires a single scalar:
77196 coerce('contours.value', 0);
77197
77198 if(!isNumeric(contours.value)) {
77199 if(Array.isArray(contours.value)) {
77200 contours.value = parseFloat(contours.value[0]);
77201 } else {
77202 contours.value = 0;
77203 }
77204 }
77205 }
77206}
77207
77208},{"../../components/color":52,"../../constants/filter_ops":156,"./label_defaults":323,"fast-isnumeric":18}],314:[function(_dereq_,module,exports){
77209/**
77210* Copyright 2012-2020, Plotly, Inc.
77211* All rights reserved.
77212*
77213* This source code is licensed under the MIT license found in the
77214* LICENSE file in the root directory of this source tree.
77215*/
77216
77217'use strict';
77218
77219var filterOps = _dereq_('../../constants/filter_ops');
77220var isNumeric = _dereq_('fast-isnumeric');
77221
77222// This syntax conforms to the existing filter transform syntax, but we don't care
77223// about open vs. closed intervals for simply drawing contours constraints:
77224module.exports = {
77225 '[]': makeRangeSettings('[]'),
77226 '][': makeRangeSettings(']['),
77227 '>': makeInequalitySettings('>'),
77228 '<': makeInequalitySettings('<'),
77229 '=': makeInequalitySettings('=')
77230};
77231
77232// This does not in any way shape or form support calendars. It's adapted from
77233// transforms/filter.js.
77234function coerceValue(operation, value) {
77235 var hasArrayValue = Array.isArray(value);
77236
77237 var coercedValue;
77238
77239 function coerce(value) {
77240 return isNumeric(value) ? (+value) : null;
77241 }
77242
77243 if(filterOps.COMPARISON_OPS2.indexOf(operation) !== -1) {
77244 coercedValue = hasArrayValue ? coerce(value[0]) : coerce(value);
77245 } else if(filterOps.INTERVAL_OPS.indexOf(operation) !== -1) {
77246 coercedValue = hasArrayValue ?
77247 [coerce(value[0]), coerce(value[1])] :
77248 [coerce(value), coerce(value)];
77249 } else if(filterOps.SET_OPS.indexOf(operation) !== -1) {
77250 coercedValue = hasArrayValue ? value.map(coerce) : [coerce(value)];
77251 }
77252
77253 return coercedValue;
77254}
77255
77256// Returns a parabola scaled so that the min/max is either +/- 1 and zero at the two values
77257// provided. The data is mapped by this function when constructing intervals so that it's
77258// very easy to construct contours as normal.
77259function makeRangeSettings(operation) {
77260 return function(value) {
77261 value = coerceValue(operation, value);
77262
77263 // Ensure proper ordering:
77264 var min = Math.min(value[0], value[1]);
77265 var max = Math.max(value[0], value[1]);
77266
77267 return {
77268 start: min,
77269 end: max,
77270 size: max - min
77271 };
77272 };
77273}
77274
77275function makeInequalitySettings(operation) {
77276 return function(value) {
77277 value = coerceValue(operation, value);
77278
77279 return {
77280 start: value,
77281 end: Infinity,
77282 size: Infinity
77283 };
77284 };
77285}
77286
77287},{"../../constants/filter_ops":156,"fast-isnumeric":18}],315:[function(_dereq_,module,exports){
77288/**
77289* Copyright 2012-2020, Plotly, Inc.
77290* All rights reserved.
77291*
77292* This source code is licensed under the MIT license found in the
77293* LICENSE file in the root directory of this source tree.
77294*/
77295
77296'use strict';
77297
77298module.exports = function handleContourDefaults(traceIn, traceOut, coerce, coerce2) {
77299 var contourStart = coerce2('contours.start');
77300 var contourEnd = coerce2('contours.end');
77301 var missingEnd = (contourStart === false) || (contourEnd === false);
77302
77303 // normally we only need size if autocontour is off. But contour.calc
77304 // pushes its calculated contour size back to the input trace, so for
77305 // things like restyle that can call supplyDefaults without calc
77306 // after the initial draw, we can just reuse the previous calculation
77307 var contourSize = coerce('contours.size');
77308 var autoContour;
77309
77310 if(missingEnd) autoContour = traceOut.autocontour = true;
77311 else autoContour = coerce('autocontour', false);
77312
77313 if(autoContour || !contourSize) coerce('ncontours');
77314};
77315
77316},{}],316:[function(_dereq_,module,exports){
77317/**
77318* Copyright 2012-2020, Plotly, Inc.
77319* All rights reserved.
77320*
77321* This source code is licensed under the MIT license found in the
77322* LICENSE file in the root directory of this source tree.
77323*/
77324
77325'use strict';
77326
77327var Lib = _dereq_('../../lib');
77328
77329// The contour extraction is great, except it totally fails for constraints because we
77330// need weird range loops and flipped contours instead of the usual format. This function
77331// does some weird manipulation of the extracted pathinfo data such that it magically
77332// draws contours correctly *as* constraints.
77333//
77334// ** I do not know which "weird range loops" the comment above is referring to.
77335module.exports = function(pathinfo, operation) {
77336 var i, pi0, pi1;
77337
77338 var op0 = function(arr) { return arr.reverse(); };
77339 var op1 = function(arr) { return arr; };
77340
77341 switch(operation) {
77342 case '=':
77343 case '<':
77344 return pathinfo;
77345 case '>':
77346 if(pathinfo.length !== 1) {
77347 Lib.warn('Contour data invalid for the specified inequality operation.');
77348 }
77349
77350 // In this case there should be exactly one contour levels in pathinfo.
77351 // We flip all of the data. This will draw the contour as closed.
77352 pi0 = pathinfo[0];
77353
77354 for(i = 0; i < pi0.edgepaths.length; i++) {
77355 pi0.edgepaths[i] = op0(pi0.edgepaths[i]);
77356 }
77357 for(i = 0; i < pi0.paths.length; i++) {
77358 pi0.paths[i] = op0(pi0.paths[i]);
77359 }
77360 for(i = 0; i < pi0.starts.length; i++) {
77361 pi0.starts[i] = op0(pi0.starts[i]);
77362 }
77363
77364 return pathinfo;
77365 case '][':
77366 var tmp = op0;
77367 op0 = op1;
77368 op1 = tmp;
77369 // It's a nice rule, except this definitely *is* what's intended here.
77370 /* eslint-disable: no-fallthrough */
77371 case '[]':
77372 /* eslint-enable: no-fallthrough */
77373 if(pathinfo.length !== 2) {
77374 Lib.warn('Contour data invalid for the specified inequality range operation.');
77375 }
77376
77377 // In this case there should be exactly two contour levels in pathinfo.
77378 // - We concatenate the info into one pathinfo.
77379 // - We must also flip all of the data in the `[]` case.
77380 // This will draw the contours as closed.
77381 pi0 = copyPathinfo(pathinfo[0]);
77382 pi1 = copyPathinfo(pathinfo[1]);
77383
77384 for(i = 0; i < pi0.edgepaths.length; i++) {
77385 pi0.edgepaths[i] = op0(pi0.edgepaths[i]);
77386 }
77387 for(i = 0; i < pi0.paths.length; i++) {
77388 pi0.paths[i] = op0(pi0.paths[i]);
77389 }
77390 for(i = 0; i < pi0.starts.length; i++) {
77391 pi0.starts[i] = op0(pi0.starts[i]);
77392 }
77393
77394 while(pi1.edgepaths.length) {
77395 pi0.edgepaths.push(op1(pi1.edgepaths.shift()));
77396 }
77397 while(pi1.paths.length) {
77398 pi0.paths.push(op1(pi1.paths.shift()));
77399 }
77400 while(pi1.starts.length) {
77401 pi0.starts.push(op1(pi1.starts.shift()));
77402 }
77403
77404 return [pi0];
77405 }
77406};
77407
77408function copyPathinfo(pi) {
77409 return Lib.extendFlat({}, pi, {
77410 edgepaths: Lib.extendDeep([], pi.edgepaths),
77411 paths: Lib.extendDeep([], pi.paths),
77412 starts: Lib.extendDeep([], pi.starts)
77413 });
77414}
77415
77416},{"../../lib":178}],317:[function(_dereq_,module,exports){
77417/**
77418* Copyright 2012-2020, Plotly, Inc.
77419* All rights reserved.
77420*
77421* This source code is licensed under the MIT license found in the
77422* LICENSE file in the root directory of this source tree.
77423*/
77424
77425'use strict';
77426
77427var Lib = _dereq_('../../lib');
77428
77429var handleXYZDefaults = _dereq_('../heatmap/xyz_defaults');
77430var handleConstraintDefaults = _dereq_('./constraint_defaults');
77431var handleContoursDefaults = _dereq_('./contours_defaults');
77432var handleStyleDefaults = _dereq_('./style_defaults');
77433var attributes = _dereq_('./attributes');
77434
77435
77436module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
77437 function coerce(attr, dflt) {
77438 return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
77439 }
77440
77441 function coerce2(attr) {
77442 return Lib.coerce2(traceIn, traceOut, attributes, attr);
77443 }
77444
77445 var len = handleXYZDefaults(traceIn, traceOut, coerce, layout);
77446 if(!len) {
77447 traceOut.visible = false;
77448 return;
77449 }
77450
77451 coerce('text');
77452 coerce('hovertext');
77453 coerce('hovertemplate');
77454 coerce('hoverongaps');
77455
77456 var isConstraint = (coerce('contours.type') === 'constraint');
77457 coerce('connectgaps', Lib.isArray1D(traceOut.z));
77458
77459 if(isConstraint) {
77460 handleConstraintDefaults(traceIn, traceOut, coerce, layout, defaultColor);
77461 } else {
77462 handleContoursDefaults(traceIn, traceOut, coerce, coerce2);
77463 handleStyleDefaults(traceIn, traceOut, coerce, layout);
77464 }
77465};
77466
77467},{"../../lib":178,"../heatmap/xyz_defaults":344,"./attributes":308,"./constraint_defaults":313,"./contours_defaults":315,"./style_defaults":329}],318:[function(_dereq_,module,exports){
77468/**
77469* Copyright 2012-2020, Plotly, Inc.
77470* All rights reserved.
77471*
77472* This source code is licensed under the MIT license found in the
77473* LICENSE file in the root directory of this source tree.
77474*/
77475
77476'use strict';
77477
77478var Lib = _dereq_('../../lib');
77479var constraintMapping = _dereq_('./constraint_mapping');
77480var endPlus = _dereq_('./end_plus');
77481
77482module.exports = function emptyPathinfo(contours, plotinfo, cd0) {
77483 var contoursFinal = (contours.type === 'constraint') ?
77484 constraintMapping[contours._operation](contours.value) :
77485 contours;
77486
77487 var cs = contoursFinal.size;
77488 var pathinfo = [];
77489 var end = endPlus(contoursFinal);
77490
77491 var carpet = cd0.trace._carpetTrace;
77492
77493 var basePathinfo = carpet ? {
77494 // store axes so we can convert to px
77495 xaxis: carpet.aaxis,
77496 yaxis: carpet.baxis,
77497 // full data arrays to use for interpolation
77498 x: cd0.a,
77499 y: cd0.b
77500 } : {
77501 xaxis: plotinfo.xaxis,
77502 yaxis: plotinfo.yaxis,
77503 x: cd0.x,
77504 y: cd0.y
77505 };
77506
77507 for(var ci = contoursFinal.start; ci < end; ci += cs) {
77508 pathinfo.push(Lib.extendFlat({
77509 level: ci,
77510 // all the cells with nontrivial marching index
77511 crossings: {},
77512 // starting points on the edges of the lattice for each contour
77513 starts: [],
77514 // all unclosed paths (may have less items than starts,
77515 // if a path is closed by rounding)
77516 edgepaths: [],
77517 // all closed paths
77518 paths: [],
77519 z: cd0.z,
77520 smoothing: cd0.trace.line.smoothing
77521 }, basePathinfo));
77522
77523 if(pathinfo.length > 1000) {
77524 Lib.warn('Too many contours, clipping at 1000', contours);
77525 break;
77526 }
77527 }
77528 return pathinfo;
77529};
77530
77531},{"../../lib":178,"./constraint_mapping":314,"./end_plus":319}],319:[function(_dereq_,module,exports){
77532/**
77533* Copyright 2012-2020, Plotly, Inc.
77534* All rights reserved.
77535*
77536* This source code is licensed under the MIT license found in the
77537* LICENSE file in the root directory of this source tree.
77538*/
77539
77540
77541'use strict';
77542
77543/*
77544 * tiny helper to move the end of the contours a little to prevent
77545 * losing the last contour to rounding errors
77546 */
77547module.exports = function endPlus(contours) {
77548 return contours.end + contours.size / 1e6;
77549};
77550
77551},{}],320:[function(_dereq_,module,exports){
77552/**
77553* Copyright 2012-2020, Plotly, Inc.
77554* All rights reserved.
77555*
77556* This source code is licensed under the MIT license found in the
77557* LICENSE file in the root directory of this source tree.
77558*/
77559
77560'use strict';
77561
77562var Lib = _dereq_('../../lib');
77563var constants = _dereq_('./constants');
77564
77565module.exports = function findAllPaths(pathinfo, xtol, ytol) {
77566 var cnt,
77567 startLoc,
77568 i,
77569 pi,
77570 j;
77571
77572 // Default just passes these values through as they were before:
77573 xtol = xtol || 0.01;
77574 ytol = ytol || 0.01;
77575
77576 for(i = 0; i < pathinfo.length; i++) {
77577 pi = pathinfo[i];
77578
77579 for(j = 0; j < pi.starts.length; j++) {
77580 startLoc = pi.starts[j];
77581 makePath(pi, startLoc, 'edge', xtol, ytol);
77582 }
77583
77584 cnt = 0;
77585 while(Object.keys(pi.crossings).length && cnt < 10000) {
77586 cnt++;
77587 startLoc = Object.keys(pi.crossings)[0].split(',').map(Number);
77588 makePath(pi, startLoc, undefined, xtol, ytol);
77589 }
77590 if(cnt === 10000) Lib.log('Infinite loop in contour?');
77591 }
77592};
77593
77594function equalPts(pt1, pt2, xtol, ytol) {
77595 return Math.abs(pt1[0] - pt2[0]) < xtol &&
77596 Math.abs(pt1[1] - pt2[1]) < ytol;
77597}
77598
77599// distance in index units - uses the 3rd and 4th items in points
77600function ptDist(pt1, pt2) {
77601 var dx = pt1[2] - pt2[2];
77602 var dy = pt1[3] - pt2[3];
77603 return Math.sqrt(dx * dx + dy * dy);
77604}
77605
77606function makePath(pi, loc, edgeflag, xtol, ytol) {
77607 var locStr = loc.join(',');
77608 var mi = pi.crossings[locStr];
77609 var marchStep = getStartStep(mi, edgeflag, loc);
77610 // start by going backward a half step and finding the crossing point
77611 var pts = [getInterpPx(pi, loc, [-marchStep[0], -marchStep[1]])];
77612 var m = pi.z.length;
77613 var n = pi.z[0].length;
77614 var startLoc = loc.slice();
77615 var startStep = marchStep.slice();
77616 var cnt;
77617
77618 // now follow the path
77619 for(cnt = 0; cnt < 10000; cnt++) { // just to avoid infinite loops
77620 if(mi > 20) {
77621 mi = constants.CHOOSESADDLE[mi][(marchStep[0] || marchStep[1]) < 0 ? 0 : 1];
77622 pi.crossings[locStr] = constants.SADDLEREMAINDER[mi];
77623 } else {
77624 delete pi.crossings[locStr];
77625 }
77626
77627 marchStep = constants.NEWDELTA[mi];
77628 if(!marchStep) {
77629 Lib.log('Found bad marching index:', mi, loc, pi.level);
77630 break;
77631 }
77632
77633 // find the crossing a half step forward, and then take the full step
77634 pts.push(getInterpPx(pi, loc, marchStep));
77635 loc[0] += marchStep[0];
77636 loc[1] += marchStep[1];
77637 locStr = loc.join(',');
77638
77639 // don't include the same point multiple times
77640 if(equalPts(pts[pts.length - 1], pts[pts.length - 2], xtol, ytol)) pts.pop();
77641
77642 var atEdge = (marchStep[0] && (loc[0] < 0 || loc[0] > n - 2)) ||
77643 (marchStep[1] && (loc[1] < 0 || loc[1] > m - 2));
77644
77645 var closedLoop = loc[0] === startLoc[0] && loc[1] === startLoc[1] &&
77646 marchStep[0] === startStep[0] && marchStep[1] === startStep[1];
77647
77648 // have we completed a loop, or reached an edge?
77649 if((closedLoop) || (edgeflag && atEdge)) break;
77650
77651 mi = pi.crossings[locStr];
77652 }
77653
77654 if(cnt === 10000) {
77655 Lib.log('Infinite loop in contour?');
77656 }
77657 var closedpath = equalPts(pts[0], pts[pts.length - 1], xtol, ytol);
77658 var totaldist = 0;
77659 var distThresholdFactor = 0.2 * pi.smoothing;
77660 var alldists = [];
77661 var cropstart = 0;
77662 var distgroup, cnt2, cnt3, newpt, ptcnt, ptavg, thisdist,
77663 i, j, edgepathi, edgepathj;
77664
77665 /*
77666 * Check for points that are too close together (<1/5 the average dist
77667 * *in grid index units* (important for log axes and nonuniform grids),
77668 * less if less smoothed) and just take the center (or avg of center 2).
77669 * This cuts down on funny behavior when a point is very close to a
77670 * contour level.
77671 */
77672 for(cnt = 1; cnt < pts.length; cnt++) {
77673 thisdist = ptDist(pts[cnt], pts[cnt - 1]);
77674 totaldist += thisdist;
77675 alldists.push(thisdist);
77676 }
77677
77678 var distThreshold = totaldist / alldists.length * distThresholdFactor;
77679
77680 function getpt(i) { return pts[i % pts.length]; }
77681
77682 for(cnt = pts.length - 2; cnt >= cropstart; cnt--) {
77683 distgroup = alldists[cnt];
77684 if(distgroup < distThreshold) {
77685 cnt3 = 0;
77686 for(cnt2 = cnt - 1; cnt2 >= cropstart; cnt2--) {
77687 if(distgroup + alldists[cnt2] < distThreshold) {
77688 distgroup += alldists[cnt2];
77689 } else break;
77690 }
77691
77692 // closed path with close points wrapping around the boundary?
77693 if(closedpath && cnt === pts.length - 2) {
77694 for(cnt3 = 0; cnt3 < cnt2; cnt3++) {
77695 if(distgroup + alldists[cnt3] < distThreshold) {
77696 distgroup += alldists[cnt3];
77697 } else break;
77698 }
77699 }
77700 ptcnt = cnt - cnt2 + cnt3 + 1;
77701 ptavg = Math.floor((cnt + cnt2 + cnt3 + 2) / 2);
77702
77703 // either endpoint included: keep the endpoint
77704 if(!closedpath && cnt === pts.length - 2) newpt = pts[pts.length - 1];
77705 else if(!closedpath && cnt2 === -1) newpt = pts[0];
77706
77707 // odd # of points - just take the central one
77708 else if(ptcnt % 2) newpt = getpt(ptavg);
77709
77710 // even # of pts - average central two
77711 else {
77712 newpt = [(getpt(ptavg)[0] + getpt(ptavg + 1)[0]) / 2,
77713 (getpt(ptavg)[1] + getpt(ptavg + 1)[1]) / 2];
77714 }
77715
77716 pts.splice(cnt2 + 1, cnt - cnt2 + 1, newpt);
77717 cnt = cnt2 + 1;
77718 if(cnt3) cropstart = cnt3;
77719 if(closedpath) {
77720 if(cnt === pts.length - 2) pts[cnt3] = pts[pts.length - 1];
77721 else if(cnt === 0) pts[pts.length - 1] = pts[0];
77722 }
77723 }
77724 }
77725 pts.splice(0, cropstart);
77726
77727 // done with the index parts - remove them so path generation works right
77728 // because it depends on only having [xpx, ypx]
77729 for(cnt = 0; cnt < pts.length; cnt++) pts[cnt].length = 2;
77730
77731 // don't return single-point paths (ie all points were the same
77732 // so they got deleted?)
77733 if(pts.length < 2) return;
77734 else if(closedpath) {
77735 pts.pop();
77736 pi.paths.push(pts);
77737 } else {
77738 if(!edgeflag) {
77739 Lib.log('Unclosed interior contour?',
77740 pi.level, startLoc.join(','), pts.join('L'));
77741 }
77742
77743 // edge path - does it start where an existing edge path ends, or vice versa?
77744 var merged = false;
77745 for(i = 0; i < pi.edgepaths.length; i++) {
77746 edgepathi = pi.edgepaths[i];
77747 if(!merged && equalPts(edgepathi[0], pts[pts.length - 1], xtol, ytol)) {
77748 pts.pop();
77749 merged = true;
77750
77751 // now does it ALSO meet the end of another (or the same) path?
77752 var doublemerged = false;
77753 for(j = 0; j < pi.edgepaths.length; j++) {
77754 edgepathj = pi.edgepaths[j];
77755 if(equalPts(edgepathj[edgepathj.length - 1], pts[0], xtol, ytol)) {
77756 doublemerged = true;
77757 pts.shift();
77758 pi.edgepaths.splice(i, 1);
77759 if(j === i) {
77760 // the path is now closed
77761 pi.paths.push(pts.concat(edgepathj));
77762 } else {
77763 if(j > i) j--;
77764 pi.edgepaths[j] = edgepathj.concat(pts, edgepathi);
77765 }
77766 break;
77767 }
77768 }
77769 if(!doublemerged) {
77770 pi.edgepaths[i] = pts.concat(edgepathi);
77771 }
77772 }
77773 }
77774 for(i = 0; i < pi.edgepaths.length; i++) {
77775 if(merged) break;
77776 edgepathi = pi.edgepaths[i];
77777 if(equalPts(edgepathi[edgepathi.length - 1], pts[0], xtol, ytol)) {
77778 pts.shift();
77779 pi.edgepaths[i] = edgepathi.concat(pts);
77780 merged = true;
77781 }
77782 }
77783
77784 if(!merged) pi.edgepaths.push(pts);
77785 }
77786}
77787
77788// special function to get the marching step of the
77789// first point in the path (leading to loc)
77790function getStartStep(mi, edgeflag, loc) {
77791 var dx = 0;
77792 var dy = 0;
77793 if(mi > 20 && edgeflag) {
77794 // these saddles start at +/- x
77795 if(mi === 208 || mi === 1114) {
77796 // if we're starting at the left side, we must be going right
77797 dx = loc[0] === 0 ? 1 : -1;
77798 } else {
77799 // if we're starting at the bottom, we must be going up
77800 dy = loc[1] === 0 ? 1 : -1;
77801 }
77802 } else if(constants.BOTTOMSTART.indexOf(mi) !== -1) dy = 1;
77803 else if(constants.LEFTSTART.indexOf(mi) !== -1) dx = 1;
77804 else if(constants.TOPSTART.indexOf(mi) !== -1) dy = -1;
77805 else dx = -1;
77806 return [dx, dy];
77807}
77808
77809/*
77810 * Find the pixel coordinates of a particular crossing
77811 *
77812 * @param {object} pi: the pathinfo object at this level
77813 * @param {array} loc: the grid index [x, y] of the crossing
77814 * @param {array} step: the direction [dx, dy] we're moving on the grid
77815 *
77816 * @return {array} [xpx, ypx, xi, yi]: the first two are the pixel location,
77817 * the next two are the interpolated grid indices, which we use for
77818 * distance calculations to delete points that are too close together.
77819 * This is important when the grid is nonuniform (and most dramatically when
77820 * we're on log axes and include invalid (0 or negative) values.
77821 * It's crucial to delete these extra two before turning an array of these
77822 * points into a path, because those routines require length-2 points.
77823 */
77824function getInterpPx(pi, loc, step) {
77825 var locx = loc[0] + Math.max(step[0], 0);
77826 var locy = loc[1] + Math.max(step[1], 0);
77827 var zxy = pi.z[locy][locx];
77828 var xa = pi.xaxis;
77829 var ya = pi.yaxis;
77830
77831 if(step[1]) {
77832 var dx = (pi.level - zxy) / (pi.z[locy][locx + 1] - zxy);
77833
77834 return [xa.c2p((1 - dx) * pi.x[locx] + dx * pi.x[locx + 1], true),
77835 ya.c2p(pi.y[locy], true),
77836 locx + dx, locy];
77837 } else {
77838 var dy = (pi.level - zxy) / (pi.z[locy + 1][locx] - zxy);
77839 return [xa.c2p(pi.x[locx], true),
77840 ya.c2p((1 - dy) * pi.y[locy] + dy * pi.y[locy + 1], true),
77841 locx, locy + dy];
77842 }
77843}
77844
77845},{"../../lib":178,"./constants":312}],321:[function(_dereq_,module,exports){
77846/**
77847* Copyright 2012-2020, Plotly, Inc.
77848* All rights reserved.
77849*
77850* This source code is licensed under the MIT license found in the
77851* LICENSE file in the root directory of this source tree.
77852*/
77853
77854
77855'use strict';
77856
77857var Color = _dereq_('../../components/color');
77858
77859var heatmapHoverPoints = _dereq_('../heatmap/hover');
77860
77861module.exports = function hoverPoints(pointData, xval, yval, hovermode, hoverLayer) {
77862 var hoverData = heatmapHoverPoints(pointData, xval, yval, hovermode, hoverLayer, true);
77863
77864 if(hoverData) {
77865 hoverData.forEach(function(hoverPt) {
77866 var trace = hoverPt.trace;
77867 if(trace.contours.type === 'constraint') {
77868 if(trace.fillcolor && Color.opacity(trace.fillcolor)) {
77869 hoverPt.color = Color.addOpacity(trace.fillcolor, 1);
77870 } else if(trace.contours.showlines && Color.opacity(trace.line.color)) {
77871 hoverPt.color = Color.addOpacity(trace.line.color, 1);
77872 }
77873 }
77874 });
77875 }
77876
77877 return hoverData;
77878};
77879
77880},{"../../components/color":52,"../heatmap/hover":337}],322:[function(_dereq_,module,exports){
77881/**
77882* Copyright 2012-2020, Plotly, Inc.
77883* All rights reserved.
77884*
77885* This source code is licensed under the MIT license found in the
77886* LICENSE file in the root directory of this source tree.
77887*/
77888
77889'use strict';
77890
77891module.exports = {
77892 attributes: _dereq_('./attributes'),
77893 supplyDefaults: _dereq_('./defaults'),
77894 calc: _dereq_('./calc'),
77895 plot: _dereq_('./plot').plot,
77896 style: _dereq_('./style'),
77897 colorbar: _dereq_('./colorbar'),
77898 hoverPoints: _dereq_('./hover'),
77899
77900 moduleType: 'trace',
77901 name: 'contour',
77902 basePlotModule: _dereq_('../../plots/cartesian'),
77903 categories: ['cartesian', 'svg', '2dMap', 'contour', 'showLegend'],
77904 meta: {
77905
77906 }
77907};
77908
77909},{"../../plots/cartesian":235,"./attributes":308,"./calc":309,"./colorbar":311,"./defaults":317,"./hover":321,"./plot":326,"./style":328}],323:[function(_dereq_,module,exports){
77910/**
77911* Copyright 2012-2020, Plotly, Inc.
77912* All rights reserved.
77913*
77914* This source code is licensed under the MIT license found in the
77915* LICENSE file in the root directory of this source tree.
77916*/
77917
77918
77919'use strict';
77920
77921var Lib = _dereq_('../../lib');
77922
77923module.exports = function handleLabelDefaults(coerce, layout, lineColor, opts) {
77924 if(!opts) opts = {};
77925 var showLabels = coerce('contours.showlabels');
77926 if(showLabels) {
77927 var globalFont = layout.font;
77928 Lib.coerceFont(coerce, 'contours.labelfont', {
77929 family: globalFont.family,
77930 size: globalFont.size,
77931 color: lineColor
77932 });
77933 coerce('contours.labelformat');
77934 }
77935
77936 if(opts.hasHover !== false) coerce('zhoverformat');
77937};
77938
77939},{"../../lib":178}],324:[function(_dereq_,module,exports){
77940/**
77941* Copyright 2012-2020, Plotly, Inc.
77942* All rights reserved.
77943*
77944* This source code is licensed under the MIT license found in the
77945* LICENSE file in the root directory of this source tree.
77946*/
77947
77948'use strict';
77949
77950var d3 = _dereq_('d3');
77951
77952var Colorscale = _dereq_('../../components/colorscale');
77953var endPlus = _dereq_('./end_plus');
77954
77955module.exports = function makeColorMap(trace) {
77956 var contours = trace.contours;
77957 var start = contours.start;
77958 var end = endPlus(contours);
77959 var cs = contours.size || 1;
77960 var nc = Math.floor((end - start) / cs) + 1;
77961 var extra = contours.coloring === 'lines' ? 0 : 1;
77962 var cOpts = Colorscale.extractOpts(trace);
77963
77964 if(!isFinite(cs)) {
77965 cs = 1;
77966 nc = 1;
77967 }
77968
77969 var scl = cOpts.reversescale ?
77970 Colorscale.flipScale(cOpts.colorscale) :
77971 cOpts.colorscale;
77972
77973 var len = scl.length;
77974 var domain = new Array(len);
77975 var range = new Array(len);
77976
77977 var si, i;
77978
77979 if(contours.coloring === 'heatmap') {
77980 var zmin0 = cOpts.min;
77981 var zmax0 = cOpts.max;
77982
77983 for(i = 0; i < len; i++) {
77984 si = scl[i];
77985 domain[i] = si[0] * (zmax0 - zmin0) + zmin0;
77986 range[i] = si[1];
77987 }
77988
77989 // do the contours extend beyond the colorscale?
77990 // if so, extend the colorscale with constants
77991 var zRange = d3.extent([
77992 zmin0,
77993 zmax0,
77994 contours.start,
77995 contours.start + cs * (nc - 1)
77996 ]);
77997 var zmin = zRange[zmin0 < zmax0 ? 0 : 1];
77998 var zmax = zRange[zmin0 < zmax0 ? 1 : 0];
77999
78000 if(zmin !== zmin0) {
78001 domain.splice(0, 0, zmin);
78002 range.splice(0, 0, range[0]);
78003 }
78004
78005 if(zmax !== zmax0) {
78006 domain.push(zmax);
78007 range.push(range[range.length - 1]);
78008 }
78009 } else {
78010 for(i = 0; i < len; i++) {
78011 si = scl[i];
78012 domain[i] = (si[0] * (nc + extra - 1) - (extra / 2)) * cs + start;
78013 range[i] = si[1];
78014 }
78015 }
78016
78017 return Colorscale.makeColorScaleFunc(
78018 {domain: domain, range: range},
78019 {noNumericCheck: true}
78020 );
78021};
78022
78023},{"../../components/colorscale":64,"./end_plus":319,"d3":16}],325:[function(_dereq_,module,exports){
78024/**
78025* Copyright 2012-2020, Plotly, Inc.
78026* All rights reserved.
78027*
78028* This source code is licensed under the MIT license found in the
78029* LICENSE file in the root directory of this source tree.
78030*/
78031
78032'use strict';
78033
78034var constants = _dereq_('./constants');
78035
78036// Calculate all the marching indices, for ALL levels at once.
78037// since we want to be exhaustive we'll check for contour crossings
78038// at every intersection, rather than just following a path
78039// TODO: shorten the inner loop to only the relevant levels
78040module.exports = function makeCrossings(pathinfo) {
78041 var z = pathinfo[0].z;
78042 var m = z.length;
78043 var n = z[0].length; // we already made sure z isn't ragged in interp2d
78044 var twoWide = m === 2 || n === 2;
78045 var xi;
78046 var yi;
78047 var startIndices;
78048 var ystartIndices;
78049 var label;
78050 var corners;
78051 var mi;
78052 var pi;
78053 var i;
78054
78055 for(yi = 0; yi < m - 1; yi++) {
78056 ystartIndices = [];
78057 if(yi === 0) ystartIndices = ystartIndices.concat(constants.BOTTOMSTART);
78058 if(yi === m - 2) ystartIndices = ystartIndices.concat(constants.TOPSTART);
78059
78060 for(xi = 0; xi < n - 1; xi++) {
78061 startIndices = ystartIndices.slice();
78062 if(xi === 0) startIndices = startIndices.concat(constants.LEFTSTART);
78063 if(xi === n - 2) startIndices = startIndices.concat(constants.RIGHTSTART);
78064
78065 label = xi + ',' + yi;
78066 corners = [[z[yi][xi], z[yi][xi + 1]],
78067 [z[yi + 1][xi], z[yi + 1][xi + 1]]];
78068 for(i = 0; i < pathinfo.length; i++) {
78069 pi = pathinfo[i];
78070 mi = getMarchingIndex(pi.level, corners);
78071 if(!mi) continue;
78072
78073 pi.crossings[label] = mi;
78074 if(startIndices.indexOf(mi) !== -1) {
78075 pi.starts.push([xi, yi]);
78076 if(twoWide && startIndices.indexOf(mi,
78077 startIndices.indexOf(mi) + 1) !== -1) {
78078 // the same square has starts from opposite sides
78079 // it's not possible to have starts on opposite edges
78080 // of a corner, only a start and an end...
78081 // but if the array is only two points wide (either way)
78082 // you can have starts on opposite sides.
78083 pi.starts.push([xi, yi]);
78084 }
78085 }
78086 }
78087 }
78088 }
78089};
78090
78091// modified marching squares algorithm,
78092// so we disambiguate the saddle points from the start
78093// and we ignore the cases with no crossings
78094// the index I'm using is based on:
78095// http://en.wikipedia.org/wiki/Marching_squares
78096// except that the saddles bifurcate and I represent them
78097// as the decimal combination of the two appropriate
78098// non-saddle indices
78099function getMarchingIndex(val, corners) {
78100 var mi = (corners[0][0] > val ? 0 : 1) +
78101 (corners[0][1] > val ? 0 : 2) +
78102 (corners[1][1] > val ? 0 : 4) +
78103 (corners[1][0] > val ? 0 : 8);
78104 if(mi === 5 || mi === 10) {
78105 var avg = (corners[0][0] + corners[0][1] +
78106 corners[1][0] + corners[1][1]) / 4;
78107 // two peaks with a big valley
78108 if(val > avg) return (mi === 5) ? 713 : 1114;
78109 // two valleys with a big ridge
78110 return (mi === 5) ? 104 : 208;
78111 }
78112 return (mi === 15) ? 0 : mi;
78113}
78114
78115},{"./constants":312}],326:[function(_dereq_,module,exports){
78116/**
78117* Copyright 2012-2020, Plotly, Inc.
78118* All rights reserved.
78119*
78120* This source code is licensed under the MIT license found in the
78121* LICENSE file in the root directory of this source tree.
78122*/
78123
78124
78125'use strict';
78126
78127var d3 = _dereq_('d3');
78128
78129var Lib = _dereq_('../../lib');
78130var Drawing = _dereq_('../../components/drawing');
78131var Colorscale = _dereq_('../../components/colorscale');
78132var svgTextUtils = _dereq_('../../lib/svg_text_utils');
78133var Axes = _dereq_('../../plots/cartesian/axes');
78134var setConvert = _dereq_('../../plots/cartesian/set_convert');
78135
78136var heatmapPlot = _dereq_('../heatmap/plot');
78137var makeCrossings = _dereq_('./make_crossings');
78138var findAllPaths = _dereq_('./find_all_paths');
78139var emptyPathinfo = _dereq_('./empty_pathinfo');
78140var convertToConstraints = _dereq_('./convert_to_constraints');
78141var closeBoundaries = _dereq_('./close_boundaries');
78142var constants = _dereq_('./constants');
78143var costConstants = constants.LABELOPTIMIZER;
78144
78145exports.plot = function plot(gd, plotinfo, cdcontours, contourLayer) {
78146 var xa = plotinfo.xaxis;
78147 var ya = plotinfo.yaxis;
78148
78149 Lib.makeTraceGroups(contourLayer, cdcontours, 'contour').each(function(cd) {
78150 var plotGroup = d3.select(this);
78151 var cd0 = cd[0];
78152 var trace = cd0.trace;
78153 var x = cd0.x;
78154 var y = cd0.y;
78155 var contours = trace.contours;
78156 var pathinfo = emptyPathinfo(contours, plotinfo, cd0);
78157
78158 // use a heatmap to fill - draw it behind the lines
78159 var heatmapColoringLayer = Lib.ensureSingle(plotGroup, 'g', 'heatmapcoloring');
78160 var cdheatmaps = [];
78161 if(contours.coloring === 'heatmap') {
78162 cdheatmaps = [cd];
78163 }
78164 heatmapPlot(gd, plotinfo, cdheatmaps, heatmapColoringLayer);
78165
78166 makeCrossings(pathinfo);
78167 findAllPaths(pathinfo);
78168
78169 var leftedge = xa.c2p(x[0], true);
78170 var rightedge = xa.c2p(x[x.length - 1], true);
78171 var bottomedge = ya.c2p(y[0], true);
78172 var topedge = ya.c2p(y[y.length - 1], true);
78173 var perimeter = [
78174 [leftedge, topedge],
78175 [rightedge, topedge],
78176 [rightedge, bottomedge],
78177 [leftedge, bottomedge]
78178 ];
78179
78180 var fillPathinfo = pathinfo;
78181 if(contours.type === 'constraint') {
78182 // N.B. this also mutates pathinfo
78183 fillPathinfo = convertToConstraints(pathinfo, contours._operation);
78184 }
78185
78186 // draw everything
78187 makeBackground(plotGroup, perimeter, contours);
78188 makeFills(plotGroup, fillPathinfo, perimeter, contours);
78189 makeLinesAndLabels(plotGroup, pathinfo, gd, cd0, contours);
78190 clipGaps(plotGroup, plotinfo, gd, cd0, perimeter);
78191 });
78192};
78193
78194function makeBackground(plotgroup, perimeter, contours) {
78195 var bggroup = Lib.ensureSingle(plotgroup, 'g', 'contourbg');
78196
78197 var bgfill = bggroup.selectAll('path')
78198 .data(contours.coloring === 'fill' ? [0] : []);
78199 bgfill.enter().append('path');
78200 bgfill.exit().remove();
78201 bgfill
78202 .attr('d', 'M' + perimeter.join('L') + 'Z')
78203 .style('stroke', 'none');
78204}
78205
78206function makeFills(plotgroup, pathinfo, perimeter, contours) {
78207 var hasFills = contours.coloring === 'fill' || (contours.type === 'constraint' && contours._operation !== '=');
78208 var boundaryPath = 'M' + perimeter.join('L') + 'Z';
78209
78210 // fills prefixBoundary in pathinfo items
78211 if(hasFills) {
78212 closeBoundaries(pathinfo, contours);
78213 }
78214
78215 var fillgroup = Lib.ensureSingle(plotgroup, 'g', 'contourfill');
78216
78217 var fillitems = fillgroup.selectAll('path').data(hasFills ? pathinfo : []);
78218 fillitems.enter().append('path');
78219 fillitems.exit().remove();
78220 fillitems.each(function(pi) {
78221 // join all paths for this level together into a single path
78222 // first follow clockwise around the perimeter to close any open paths
78223 // if the whole perimeter is above this level, start with a path
78224 // enclosing the whole thing. With all that, the parity should mean
78225 // that we always fill everything above the contour, nothing below
78226 var fullpath = (pi.prefixBoundary ? boundaryPath : '') +
78227 joinAllPaths(pi, perimeter);
78228
78229 if(!fullpath) {
78230 d3.select(this).remove();
78231 } else {
78232 d3.select(this)
78233 .attr('d', fullpath)
78234 .style('stroke', 'none');
78235 }
78236 });
78237}
78238
78239function joinAllPaths(pi, perimeter) {
78240 var fullpath = '';
78241 var i = 0;
78242 var startsleft = pi.edgepaths.map(function(v, i) { return i; });
78243 var newloop = true;
78244 var endpt;
78245 var newendpt;
78246 var cnt;
78247 var nexti;
78248 var possiblei;
78249 var addpath;
78250
78251 function istop(pt) { return Math.abs(pt[1] - perimeter[0][1]) < 0.01; }
78252 function isbottom(pt) { return Math.abs(pt[1] - perimeter[2][1]) < 0.01; }
78253 function isleft(pt) { return Math.abs(pt[0] - perimeter[0][0]) < 0.01; }
78254 function isright(pt) { return Math.abs(pt[0] - perimeter[2][0]) < 0.01; }
78255
78256 while(startsleft.length) {
78257 addpath = Drawing.smoothopen(pi.edgepaths[i], pi.smoothing);
78258 fullpath += newloop ? addpath : addpath.replace(/^M/, 'L');
78259 startsleft.splice(startsleft.indexOf(i), 1);
78260 endpt = pi.edgepaths[i][pi.edgepaths[i].length - 1];
78261 nexti = -1;
78262
78263 // now loop through sides, moving our endpoint until we find a new start
78264 for(cnt = 0; cnt < 4; cnt++) { // just to prevent infinite loops
78265 if(!endpt) {
78266 Lib.log('Missing end?', i, pi);
78267 break;
78268 }
78269
78270 if(istop(endpt) && !isright(endpt)) newendpt = perimeter[1]; // right top
78271 else if(isleft(endpt)) newendpt = perimeter[0]; // left top
78272 else if(isbottom(endpt)) newendpt = perimeter[3]; // right bottom
78273 else if(isright(endpt)) newendpt = perimeter[2]; // left bottom
78274
78275 for(possiblei = 0; possiblei < pi.edgepaths.length; possiblei++) {
78276 var ptNew = pi.edgepaths[possiblei][0];
78277 // is ptNew on the (horz. or vert.) segment from endpt to newendpt?
78278 if(Math.abs(endpt[0] - newendpt[0]) < 0.01) {
78279 if(Math.abs(endpt[0] - ptNew[0]) < 0.01 &&
78280 (ptNew[1] - endpt[1]) * (newendpt[1] - ptNew[1]) >= 0) {
78281 newendpt = ptNew;
78282 nexti = possiblei;
78283 }
78284 } else if(Math.abs(endpt[1] - newendpt[1]) < 0.01) {
78285 if(Math.abs(endpt[1] - ptNew[1]) < 0.01 &&
78286 (ptNew[0] - endpt[0]) * (newendpt[0] - ptNew[0]) >= 0) {
78287 newendpt = ptNew;
78288 nexti = possiblei;
78289 }
78290 } else {
78291 Lib.log('endpt to newendpt is not vert. or horz.',
78292 endpt, newendpt, ptNew);
78293 }
78294 }
78295
78296 endpt = newendpt;
78297
78298 if(nexti >= 0) break;
78299 fullpath += 'L' + newendpt;
78300 }
78301
78302 if(nexti === pi.edgepaths.length) {
78303 Lib.log('unclosed perimeter path');
78304 break;
78305 }
78306
78307 i = nexti;
78308
78309 // if we closed back on a loop we already included,
78310 // close it and start a new loop
78311 newloop = (startsleft.indexOf(i) === -1);
78312 if(newloop) {
78313 i = startsleft[0];
78314 fullpath += 'Z';
78315 }
78316 }
78317
78318 // finally add the interior paths
78319 for(i = 0; i < pi.paths.length; i++) {
78320 fullpath += Drawing.smoothclosed(pi.paths[i], pi.smoothing);
78321 }
78322
78323 return fullpath;
78324}
78325
78326function makeLinesAndLabels(plotgroup, pathinfo, gd, cd0, contours) {
78327 var lineContainer = Lib.ensureSingle(plotgroup, 'g', 'contourlines');
78328 var showLines = contours.showlines !== false;
78329 var showLabels = contours.showlabels;
78330 var clipLinesForLabels = showLines && showLabels;
78331
78332 // Even if we're not going to show lines, we need to create them
78333 // if we're showing labels, because the fill paths include the perimeter
78334 // so can't be used to position the labels correctly.
78335 // In this case we'll remove the lines after making the labels.
78336 var linegroup = exports.createLines(lineContainer, showLines || showLabels, pathinfo);
78337
78338 var lineClip = exports.createLineClip(lineContainer, clipLinesForLabels, gd, cd0.trace.uid);
78339
78340 var labelGroup = plotgroup.selectAll('g.contourlabels')
78341 .data(showLabels ? [0] : []);
78342
78343 labelGroup.exit().remove();
78344
78345 labelGroup.enter().append('g')
78346 .classed('contourlabels', true);
78347
78348 if(showLabels) {
78349 var labelClipPathData = [];
78350 var labelData = [];
78351
78352 // invalidate the getTextLocation cache in case paths changed
78353 Lib.clearLocationCache();
78354
78355 var contourFormat = exports.labelFormatter(gd, cd0);
78356
78357 var dummyText = Drawing.tester.append('text')
78358 .attr('data-notex', 1)
78359 .call(Drawing.font, contours.labelfont);
78360
78361 var xa = pathinfo[0].xaxis;
78362 var ya = pathinfo[0].yaxis;
78363 var xLen = xa._length;
78364 var yLen = ya._length;
78365 var xRng = xa.range;
78366 var yRng = ya.range;
78367 var xMin = Lib.aggNums(Math.min, null, cd0.x);
78368 var xMax = Lib.aggNums(Math.max, null, cd0.x);
78369 var yMin = Lib.aggNums(Math.min, null, cd0.y);
78370 var yMax = Lib.aggNums(Math.max, null, cd0.y);
78371 var x0 = Math.max(xa.c2p(xMin, true), 0);
78372 var x1 = Math.min(xa.c2p(xMax, true), xLen);
78373 var y0 = Math.max(ya.c2p(yMax, true), 0);
78374 var y1 = Math.min(ya.c2p(yMin, true), yLen);
78375
78376 // visible bounds of the contour trace (and the midpoints, to
78377 // help with cost calculations)
78378 var bounds = {};
78379
78380 if(xRng[0] < xRng[1]) {
78381 bounds.left = x0;
78382 bounds.right = x1;
78383 } else {
78384 bounds.left = x1;
78385 bounds.right = x0;
78386 }
78387
78388 if(yRng[0] < yRng[1]) {
78389 bounds.top = y0;
78390 bounds.bottom = y1;
78391 } else {
78392 bounds.top = y1;
78393 bounds.bottom = y0;
78394 }
78395
78396 bounds.middle = (bounds.top + bounds.bottom) / 2;
78397 bounds.center = (bounds.left + bounds.right) / 2;
78398
78399 labelClipPathData.push([
78400 [bounds.left, bounds.top],
78401 [bounds.right, bounds.top],
78402 [bounds.right, bounds.bottom],
78403 [bounds.left, bounds.bottom]
78404 ]);
78405
78406 var plotDiagonal = Math.sqrt(xLen * xLen + yLen * yLen);
78407
78408 // the path length to use to scale the number of labels to draw:
78409 var normLength = constants.LABELDISTANCE * plotDiagonal /
78410 Math.max(1, pathinfo.length / constants.LABELINCREASE);
78411
78412 linegroup.each(function(d) {
78413 var textOpts = exports.calcTextOpts(d.level, contourFormat, dummyText, gd);
78414
78415 d3.select(this).selectAll('path').each(function() {
78416 var path = this;
78417 var pathBounds = Lib.getVisibleSegment(path, bounds, textOpts.height / 2);
78418 if(!pathBounds) return;
78419
78420 if(pathBounds.len < (textOpts.width + textOpts.height) * constants.LABELMIN) return;
78421
78422 var maxLabels = Math.min(Math.ceil(pathBounds.len / normLength),
78423 constants.LABELMAX);
78424
78425 for(var i = 0; i < maxLabels; i++) {
78426 var loc = exports.findBestTextLocation(path, pathBounds, textOpts,
78427 labelData, bounds);
78428
78429 if(!loc) break;
78430
78431 exports.addLabelData(loc, textOpts, labelData, labelClipPathData);
78432 }
78433 });
78434 });
78435
78436 dummyText.remove();
78437
78438 exports.drawLabels(labelGroup, labelData, gd, lineClip,
78439 clipLinesForLabels ? labelClipPathData : null);
78440 }
78441
78442 if(showLabels && !showLines) linegroup.remove();
78443}
78444
78445exports.createLines = function(lineContainer, makeLines, pathinfo) {
78446 var smoothing = pathinfo[0].smoothing;
78447
78448 var linegroup = lineContainer.selectAll('g.contourlevel')
78449 .data(makeLines ? pathinfo : []);
78450
78451 linegroup.exit().remove();
78452 linegroup.enter().append('g')
78453 .classed('contourlevel', true);
78454
78455 if(makeLines) {
78456 // pedgepaths / ppaths are used by contourcarpet, for the paths transformed from a/b to x/y
78457 // edgepaths / paths are used by contour since it's in x/y from the start
78458 var opencontourlines = linegroup.selectAll('path.openline')
78459 .data(function(d) { return d.pedgepaths || d.edgepaths; });
78460
78461 opencontourlines.exit().remove();
78462 opencontourlines.enter().append('path')
78463 .classed('openline', true);
78464
78465 opencontourlines
78466 .attr('d', function(d) {
78467 return Drawing.smoothopen(d, smoothing);
78468 })
78469 .style('stroke-miterlimit', 1)
78470 .style('vector-effect', 'non-scaling-stroke');
78471
78472 var closedcontourlines = linegroup.selectAll('path.closedline')
78473 .data(function(d) { return d.ppaths || d.paths; });
78474
78475 closedcontourlines.exit().remove();
78476 closedcontourlines.enter().append('path')
78477 .classed('closedline', true);
78478
78479 closedcontourlines
78480 .attr('d', function(d) {
78481 return Drawing.smoothclosed(d, smoothing);
78482 })
78483 .style('stroke-miterlimit', 1)
78484 .style('vector-effect', 'non-scaling-stroke');
78485 }
78486
78487 return linegroup;
78488};
78489
78490exports.createLineClip = function(lineContainer, clipLinesForLabels, gd, uid) {
78491 var clips = gd._fullLayout._clips;
78492 var clipId = clipLinesForLabels ? ('clipline' + uid) : null;
78493
78494 var lineClip = clips.selectAll('#' + clipId)
78495 .data(clipLinesForLabels ? [0] : []);
78496 lineClip.exit().remove();
78497
78498 lineClip.enter().append('clipPath')
78499 .classed('contourlineclip', true)
78500 .attr('id', clipId);
78501
78502 Drawing.setClipUrl(lineContainer, clipId, gd);
78503
78504 return lineClip;
78505};
78506
78507exports.labelFormatter = function(gd, cd0) {
78508 var fullLayout = gd._fullLayout;
78509 var trace = cd0.trace;
78510 var contours = trace.contours;
78511
78512 var formatAxis = {
78513 type: 'linear',
78514 _id: 'ycontour',
78515 showexponent: 'all',
78516 exponentformat: 'B'
78517 };
78518
78519 if(contours.labelformat) {
78520 formatAxis.tickformat = contours.labelformat;
78521 setConvert(formatAxis, fullLayout);
78522 } else {
78523 var cOpts = Colorscale.extractOpts(trace);
78524 if(cOpts && cOpts.colorbar && cOpts.colorbar._axis) {
78525 formatAxis = cOpts.colorbar._axis;
78526 } else {
78527 if(contours.type === 'constraint') {
78528 var value = contours.value;
78529 if(Array.isArray(value)) {
78530 formatAxis.range = [value[0], value[value.length - 1]];
78531 } else formatAxis.range = [value, value];
78532 } else {
78533 formatAxis.range = [contours.start, contours.end];
78534 formatAxis.nticks = (contours.end - contours.start) / contours.size;
78535 }
78536
78537 if(formatAxis.range[0] === formatAxis.range[1]) {
78538 formatAxis.range[1] += formatAxis.range[0] || 1;
78539 }
78540 if(!formatAxis.nticks) formatAxis.nticks = 1000;
78541
78542 setConvert(formatAxis, fullLayout);
78543 Axes.prepTicks(formatAxis);
78544 formatAxis._tmin = null;
78545 formatAxis._tmax = null;
78546 }
78547 }
78548
78549 return function(v) { return Axes.tickText(formatAxis, v).text; };
78550};
78551
78552exports.calcTextOpts = function(level, contourFormat, dummyText, gd) {
78553 var text = contourFormat(level);
78554 dummyText.text(text)
78555 .call(svgTextUtils.convertToTspans, gd);
78556
78557 var el = dummyText.node();
78558 var bBox = Drawing.bBox(el, true);
78559
78560 return {
78561 text: text,
78562 width: bBox.width,
78563 height: bBox.height,
78564 fontSize: +(el.style['font-size'].replace('px', '')),
78565 level: level,
78566 dy: (bBox.top + bBox.bottom) / 2
78567 };
78568};
78569
78570exports.findBestTextLocation = function(path, pathBounds, textOpts, labelData, plotBounds) {
78571 var textWidth = textOpts.width;
78572
78573 var p0, dp, pMax, pMin, loc;
78574 if(pathBounds.isClosed) {
78575 dp = pathBounds.len / costConstants.INITIALSEARCHPOINTS;
78576 p0 = pathBounds.min + dp / 2;
78577 pMax = pathBounds.max;
78578 } else {
78579 dp = (pathBounds.len - textWidth) / (costConstants.INITIALSEARCHPOINTS + 1);
78580 p0 = pathBounds.min + dp + textWidth / 2;
78581 pMax = pathBounds.max - (dp + textWidth) / 2;
78582 }
78583
78584 var cost = Infinity;
78585 for(var j = 0; j < costConstants.ITERATIONS; j++) {
78586 for(var p = p0; p < pMax; p += dp) {
78587 var newLocation = Lib.getTextLocation(path, pathBounds.total, p, textWidth);
78588 var newCost = locationCost(newLocation, textOpts, labelData, plotBounds);
78589 if(newCost < cost) {
78590 cost = newCost;
78591 loc = newLocation;
78592 pMin = p;
78593 }
78594 }
78595 if(cost > costConstants.MAXCOST * 2) break;
78596
78597 // subsequent iterations just look half steps away from the
78598 // best we found in the previous iteration
78599 if(j) dp /= 2;
78600 p0 = pMin - dp / 2;
78601 pMax = p0 + dp * 1.5;
78602 }
78603 if(cost <= costConstants.MAXCOST) return loc;
78604};
78605
78606/*
78607 * locationCost: a cost function for label locations
78608 * composed of three kinds of penalty:
78609 * - for open paths, being close to the end of the path
78610 * - the angle away from horizontal
78611 * - being too close to already placed neighbors
78612 */
78613function locationCost(loc, textOpts, labelData, bounds) {
78614 var halfWidth = textOpts.width / 2;
78615 var halfHeight = textOpts.height / 2;
78616 var x = loc.x;
78617 var y = loc.y;
78618 var theta = loc.theta;
78619 var dx = Math.cos(theta) * halfWidth;
78620 var dy = Math.sin(theta) * halfWidth;
78621
78622 // cost for being near an edge
78623 var normX = ((x > bounds.center) ? (bounds.right - x) : (x - bounds.left)) /
78624 (dx + Math.abs(Math.sin(theta) * halfHeight));
78625 var normY = ((y > bounds.middle) ? (bounds.bottom - y) : (y - bounds.top)) /
78626 (Math.abs(dy) + Math.cos(theta) * halfHeight);
78627 if(normX < 1 || normY < 1) return Infinity;
78628 var cost = costConstants.EDGECOST * (1 / (normX - 1) + 1 / (normY - 1));
78629
78630 // cost for not being horizontal
78631 cost += costConstants.ANGLECOST * theta * theta;
78632
78633 // cost for being close to other labels
78634 var x1 = x - dx;
78635 var y1 = y - dy;
78636 var x2 = x + dx;
78637 var y2 = y + dy;
78638 for(var i = 0; i < labelData.length; i++) {
78639 var labeli = labelData[i];
78640 var dxd = Math.cos(labeli.theta) * labeli.width / 2;
78641 var dyd = Math.sin(labeli.theta) * labeli.width / 2;
78642 var dist = Lib.segmentDistance(
78643 x1, y1,
78644 x2, y2,
78645 labeli.x - dxd, labeli.y - dyd,
78646 labeli.x + dxd, labeli.y + dyd
78647 ) * 2 / (textOpts.height + labeli.height);
78648
78649 var sameLevel = labeli.level === textOpts.level;
78650 var distOffset = sameLevel ? costConstants.SAMELEVELDISTANCE : 1;
78651
78652 if(dist <= distOffset) return Infinity;
78653
78654 var distFactor = costConstants.NEIGHBORCOST *
78655 (sameLevel ? costConstants.SAMELEVELFACTOR : 1);
78656
78657 cost += distFactor / (dist - distOffset);
78658 }
78659
78660 return cost;
78661}
78662
78663exports.addLabelData = function(loc, textOpts, labelData, labelClipPathData) {
78664 var fontSize = textOpts.fontSize;
78665 var w = textOpts.width + fontSize / 3;
78666 var h = Math.max(0, textOpts.height - fontSize / 3);
78667
78668 var x = loc.x;
78669 var y = loc.y;
78670 var theta = loc.theta;
78671
78672 var sin = Math.sin(theta);
78673 var cos = Math.cos(theta);
78674
78675 var rotateXY = function(dx, dy) {
78676 return [
78677 x + dx * cos - dy * sin,
78678 y + dx * sin + dy * cos
78679 ];
78680 };
78681
78682 var bBoxPts = [
78683 rotateXY(-w / 2, -h / 2),
78684 rotateXY(-w / 2, h / 2),
78685 rotateXY(w / 2, h / 2),
78686 rotateXY(w / 2, -h / 2)
78687 ];
78688
78689 labelData.push({
78690 text: textOpts.text,
78691 x: x,
78692 y: y,
78693 dy: textOpts.dy,
78694 theta: theta,
78695 level: textOpts.level,
78696 width: w,
78697 height: h
78698 });
78699
78700 labelClipPathData.push(bBoxPts);
78701};
78702
78703exports.drawLabels = function(labelGroup, labelData, gd, lineClip, labelClipPathData) {
78704 var labels = labelGroup.selectAll('text')
78705 .data(labelData, function(d) {
78706 return d.text + ',' + d.x + ',' + d.y + ',' + d.theta;
78707 });
78708
78709 labels.exit().remove();
78710
78711 labels.enter().append('text')
78712 .attr({
78713 'data-notex': 1,
78714 'text-anchor': 'middle'
78715 })
78716 .each(function(d) {
78717 var x = d.x + Math.sin(d.theta) * d.dy;
78718 var y = d.y - Math.cos(d.theta) * d.dy;
78719 d3.select(this)
78720 .text(d.text)
78721 .attr({
78722 x: x,
78723 y: y,
78724 transform: 'rotate(' + (180 * d.theta / Math.PI) + ' ' + x + ' ' + y + ')'
78725 })
78726 .call(svgTextUtils.convertToTspans, gd);
78727 });
78728
78729 if(labelClipPathData) {
78730 var clipPath = '';
78731 for(var i = 0; i < labelClipPathData.length; i++) {
78732 clipPath += 'M' + labelClipPathData[i].join('L') + 'Z';
78733 }
78734
78735 var lineClipPath = Lib.ensureSingle(lineClip, 'path', '');
78736 lineClipPath.attr('d', clipPath);
78737 }
78738};
78739
78740function clipGaps(plotGroup, plotinfo, gd, cd0, perimeter) {
78741 var trace = cd0.trace;
78742 var clips = gd._fullLayout._clips;
78743 var clipId = 'clip' + trace.uid;
78744
78745 var clipPath = clips.selectAll('#' + clipId)
78746 .data(trace.connectgaps ? [] : [0]);
78747 clipPath.enter().append('clipPath')
78748 .classed('contourclip', true)
78749 .attr('id', clipId);
78750 clipPath.exit().remove();
78751
78752 if(trace.connectgaps === false) {
78753 var clipPathInfo = {
78754 // fraction of the way from missing to present point
78755 // to draw the boundary.
78756 // if you make this 1 (or 1-epsilon) then a point in
78757 // a sea of missing data will disappear entirely.
78758 level: 0.9,
78759 crossings: {},
78760 starts: [],
78761 edgepaths: [],
78762 paths: [],
78763 xaxis: plotinfo.xaxis,
78764 yaxis: plotinfo.yaxis,
78765 x: cd0.x,
78766 y: cd0.y,
78767 // 0 = no data, 1 = data
78768 z: makeClipMask(cd0),
78769 smoothing: 0
78770 };
78771
78772 makeCrossings([clipPathInfo]);
78773 findAllPaths([clipPathInfo]);
78774 closeBoundaries([clipPathInfo], {type: 'levels'});
78775
78776 var path = Lib.ensureSingle(clipPath, 'path', '');
78777 path.attr('d',
78778 (clipPathInfo.prefixBoundary ? 'M' + perimeter.join('L') + 'Z' : '') +
78779 joinAllPaths(clipPathInfo, perimeter)
78780 );
78781 } else clipId = null;
78782
78783 Drawing.setClipUrl(plotGroup, clipId, gd);
78784}
78785
78786function makeClipMask(cd0) {
78787 var empties = cd0.trace._emptypoints;
78788 var z = [];
78789 var m = cd0.z.length;
78790 var n = cd0.z[0].length;
78791 var i;
78792 var row = [];
78793 var emptyPoint;
78794
78795 for(i = 0; i < n; i++) row.push(1);
78796 for(i = 0; i < m; i++) z.push(row.slice());
78797 for(i = 0; i < empties.length; i++) {
78798 emptyPoint = empties[i];
78799 z[emptyPoint[0]][emptyPoint[1]] = 0;
78800 }
78801 // save this mask to determine whether to show this data in hover
78802 cd0.zmask = z;
78803 return z;
78804}
78805
78806},{"../../components/colorscale":64,"../../components/drawing":74,"../../lib":178,"../../lib/svg_text_utils":199,"../../plots/cartesian/axes":222,"../../plots/cartesian/set_convert":242,"../heatmap/plot":341,"./close_boundaries":310,"./constants":312,"./convert_to_constraints":316,"./empty_pathinfo":318,"./find_all_paths":320,"./make_crossings":325,"d3":16}],327:[function(_dereq_,module,exports){
78807/**
78808* Copyright 2012-2020, Plotly, Inc.
78809* All rights reserved.
78810*
78811* This source code is licensed under the MIT license found in the
78812* LICENSE file in the root directory of this source tree.
78813*/
78814
78815'use strict';
78816
78817var Axes = _dereq_('../../plots/cartesian/axes');
78818var Lib = _dereq_('../../lib');
78819
78820module.exports = function setContours(trace, vals) {
78821 var contours = trace.contours;
78822
78823 // check if we need to auto-choose contour levels
78824 if(trace.autocontour) {
78825 // N.B. do not try to use coloraxis cmin/cmax,
78826 // these values here are meant to remain "per-trace" for now
78827 var zmin = trace.zmin;
78828 var zmax = trace.zmax;
78829 if(trace.zauto || zmin === undefined) {
78830 zmin = Lib.aggNums(Math.min, null, vals);
78831 }
78832 if(trace.zauto || zmax === undefined) {
78833 zmax = Lib.aggNums(Math.max, null, vals);
78834 }
78835
78836 var dummyAx = autoContours(zmin, zmax, trace.ncontours);
78837 contours.size = dummyAx.dtick;
78838 contours.start = Axes.tickFirst(dummyAx);
78839 dummyAx.range.reverse();
78840 contours.end = Axes.tickFirst(dummyAx);
78841
78842 if(contours.start === zmin) contours.start += contours.size;
78843 if(contours.end === zmax) contours.end -= contours.size;
78844
78845 // if you set a small ncontours, *and* the ends are exactly on zmin/zmax
78846 // there's an edge case where start > end now. Make sure there's at least
78847 // one meaningful contour, put it midway between the crossed values
78848 if(contours.start > contours.end) {
78849 contours.start = contours.end = (contours.start + contours.end) / 2;
78850 }
78851
78852 // copy auto-contour info back to the source data.
78853 // previously we copied the whole contours object back, but that had
78854 // other info (coloring, showlines) that should be left to supplyDefaults
78855 if(!trace._input.contours) trace._input.contours = {};
78856 Lib.extendFlat(trace._input.contours, {
78857 start: contours.start,
78858 end: contours.end,
78859 size: contours.size
78860 });
78861 trace._input.autocontour = true;
78862 } else if(contours.type !== 'constraint') {
78863 // sanity checks on manually-supplied start/end/size
78864 var start = contours.start;
78865 var end = contours.end;
78866 var inputContours = trace._input.contours;
78867
78868 if(start > end) {
78869 contours.start = inputContours.start = end;
78870 end = contours.end = inputContours.end = start;
78871 start = contours.start;
78872 }
78873
78874 if(!(contours.size > 0)) {
78875 var sizeOut;
78876 if(start === end) sizeOut = 1;
78877 else sizeOut = autoContours(start, end, trace.ncontours).dtick;
78878
78879 inputContours.size = contours.size = sizeOut;
78880 }
78881 }
78882};
78883
78884
78885/*
78886 * autoContours: make a dummy axis object with dtick we can use
78887 * as contours.size, and if needed we can use Axes.tickFirst
78888 * with this axis object to calculate the start and end too
78889 *
78890 * start: the value to start the contours at
78891 * end: the value to end at (must be > start)
78892 * ncontours: max number of contours to make, like roughDTick
78893 *
78894 * returns: an axis object
78895 */
78896function autoContours(start, end, ncontours) {
78897 var dummyAx = {
78898 type: 'linear',
78899 range: [start, end]
78900 };
78901
78902 Axes.autoTicks(
78903 dummyAx,
78904 (end - start) / (ncontours || 15)
78905 );
78906
78907 return dummyAx;
78908}
78909
78910},{"../../lib":178,"../../plots/cartesian/axes":222}],328:[function(_dereq_,module,exports){
78911/**
78912* Copyright 2012-2020, Plotly, Inc.
78913* All rights reserved.
78914*
78915* This source code is licensed under the MIT license found in the
78916* LICENSE file in the root directory of this source tree.
78917*/
78918
78919
78920'use strict';
78921
78922var d3 = _dereq_('d3');
78923
78924var Drawing = _dereq_('../../components/drawing');
78925var heatmapStyle = _dereq_('../heatmap/style');
78926
78927var makeColorMap = _dereq_('./make_color_map');
78928
78929
78930module.exports = function style(gd) {
78931 var contours = d3.select(gd).selectAll('g.contour');
78932
78933 contours.style('opacity', function(d) {
78934 return d[0].trace.opacity;
78935 });
78936
78937 contours.each(function(d) {
78938 var c = d3.select(this);
78939 var trace = d[0].trace;
78940 var contours = trace.contours;
78941 var line = trace.line;
78942 var cs = contours.size || 1;
78943 var start = contours.start;
78944
78945 // for contourcarpet only - is this a constraint-type contour trace?
78946 var isConstraintType = contours.type === 'constraint';
78947 var colorLines = !isConstraintType && contours.coloring === 'lines';
78948 var colorFills = !isConstraintType && contours.coloring === 'fill';
78949
78950 var colorMap = (colorLines || colorFills) ? makeColorMap(trace) : null;
78951
78952 c.selectAll('g.contourlevel').each(function(d) {
78953 d3.select(this).selectAll('path')
78954 .call(Drawing.lineGroupStyle,
78955 line.width,
78956 colorLines ? colorMap(d.level) : line.color,
78957 line.dash);
78958 });
78959
78960 var labelFont = contours.labelfont;
78961 c.selectAll('g.contourlabels text').each(function(d) {
78962 Drawing.font(d3.select(this), {
78963 family: labelFont.family,
78964 size: labelFont.size,
78965 color: labelFont.color || (colorLines ? colorMap(d.level) : line.color)
78966 });
78967 });
78968
78969 if(isConstraintType) {
78970 c.selectAll('g.contourfill path')
78971 .style('fill', trace.fillcolor);
78972 } else if(colorFills) {
78973 var firstFill;
78974
78975 c.selectAll('g.contourfill path')
78976 .style('fill', function(d) {
78977 if(firstFill === undefined) firstFill = d.level;
78978 return colorMap(d.level + 0.5 * cs);
78979 });
78980
78981 if(firstFill === undefined) firstFill = start;
78982
78983 c.selectAll('g.contourbg path')
78984 .style('fill', colorMap(firstFill - 0.5 * cs));
78985 }
78986 });
78987
78988 heatmapStyle(gd);
78989};
78990
78991},{"../../components/drawing":74,"../heatmap/style":342,"./make_color_map":324,"d3":16}],329:[function(_dereq_,module,exports){
78992/**
78993* Copyright 2012-2020, Plotly, Inc.
78994* All rights reserved.
78995*
78996* This source code is licensed under the MIT license found in the
78997* LICENSE file in the root directory of this source tree.
78998*/
78999
79000
79001'use strict';
79002
79003var colorscaleDefaults = _dereq_('../../components/colorscale/defaults');
79004var handleLabelDefaults = _dereq_('./label_defaults');
79005
79006
79007module.exports = function handleStyleDefaults(traceIn, traceOut, coerce, layout, opts) {
79008 var coloring = coerce('contours.coloring');
79009
79010 var showLines;
79011 var lineColor = '';
79012 if(coloring === 'fill') showLines = coerce('contours.showlines');
79013
79014 if(showLines !== false) {
79015 if(coloring !== 'lines') lineColor = coerce('line.color', '#000');
79016 coerce('line.width', 0.5);
79017 coerce('line.dash');
79018 }
79019
79020 if(coloring !== 'none') {
79021 // plots/plots always coerces showlegend to true, but in this case
79022 // we default to false and (by default) show a colorbar instead
79023 if(traceIn.showlegend !== true) traceOut.showlegend = false;
79024 traceOut._dfltShowLegend = false;
79025
79026 colorscaleDefaults(
79027 traceIn, traceOut, layout, coerce, {prefix: '', cLetter: 'z'}
79028 );
79029 }
79030
79031 coerce('line.smoothing');
79032
79033 handleLabelDefaults(coerce, layout, lineColor, opts);
79034};
79035
79036},{"../../components/colorscale/defaults":62,"./label_defaults":323}],330:[function(_dereq_,module,exports){
79037/**
79038* Copyright 2012-2020, Plotly, Inc.
79039* All rights reserved.
79040*
79041* This source code is licensed under the MIT license found in the
79042* LICENSE file in the root directory of this source tree.
79043*/
79044
79045'use strict';
79046
79047var scatterAttrs = _dereq_('../scatter/attributes');
79048var baseAttrs = _dereq_('../../plots/attributes');
79049var hovertemplateAttrs = _dereq_('../../plots/template_attributes').hovertemplateAttrs;
79050var colorScaleAttrs = _dereq_('../../components/colorscale/attributes');
79051var FORMAT_LINK = _dereq_('../../constants/docs').FORMAT_LINK;
79052
79053var extendFlat = _dereq_('../../lib/extend').extendFlat;
79054
79055module.exports = extendFlat({
79056 z: {
79057 valType: 'data_array',
79058 editType: 'calc',
79059
79060 },
79061 x: extendFlat({}, scatterAttrs.x, {impliedEdits: {xtype: 'array'}}),
79062 x0: extendFlat({}, scatterAttrs.x0, {impliedEdits: {xtype: 'scaled'}}),
79063 dx: extendFlat({}, scatterAttrs.dx, {impliedEdits: {xtype: 'scaled'}}),
79064 y: extendFlat({}, scatterAttrs.y, {impliedEdits: {ytype: 'array'}}),
79065 y0: extendFlat({}, scatterAttrs.y0, {impliedEdits: {ytype: 'scaled'}}),
79066 dy: extendFlat({}, scatterAttrs.dy, {impliedEdits: {ytype: 'scaled'}}),
79067
79068 text: {
79069 valType: 'data_array',
79070 editType: 'calc',
79071
79072 },
79073 hovertext: {
79074 valType: 'data_array',
79075 editType: 'calc',
79076
79077 },
79078 transpose: {
79079 valType: 'boolean',
79080 dflt: false,
79081
79082 editType: 'calc',
79083
79084 },
79085 xtype: {
79086 valType: 'enumerated',
79087 values: ['array', 'scaled'],
79088
79089 editType: 'calc+clearAxisTypes',
79090
79091 },
79092 ytype: {
79093 valType: 'enumerated',
79094 values: ['array', 'scaled'],
79095
79096 editType: 'calc+clearAxisTypes',
79097
79098 },
79099 zsmooth: {
79100 valType: 'enumerated',
79101 values: ['fast', 'best', false],
79102 dflt: false,
79103
79104 editType: 'calc',
79105
79106 },
79107 hoverongaps: {
79108 valType: 'boolean',
79109 dflt: true,
79110
79111 editType: 'none',
79112
79113 },
79114 connectgaps: {
79115 valType: 'boolean',
79116
79117 editType: 'calc',
79118
79119 },
79120 xgap: {
79121 valType: 'number',
79122 dflt: 0,
79123 min: 0,
79124
79125 editType: 'plot',
79126
79127 },
79128 ygap: {
79129 valType: 'number',
79130 dflt: 0,
79131 min: 0,
79132
79133 editType: 'plot',
79134
79135 },
79136 zhoverformat: {
79137 valType: 'string',
79138 dflt: '',
79139
79140 editType: 'none',
79141
79142 },
79143 hovertemplate: hovertemplateAttrs(),
79144 showlegend: extendFlat({}, baseAttrs.showlegend, {dflt: false})
79145}, {
79146 transforms: undefined
79147},
79148 colorScaleAttrs('', {cLetter: 'z', autoColorDflt: false})
79149);
79150
79151},{"../../components/colorscale/attributes":59,"../../constants/docs":155,"../../lib/extend":173,"../../plots/attributes":219,"../../plots/template_attributes":264,"../scatter/attributes":389}],331:[function(_dereq_,module,exports){
79152/**
79153* Copyright 2012-2020, Plotly, Inc.
79154* All rights reserved.
79155*
79156* This source code is licensed under the MIT license found in the
79157* LICENSE file in the root directory of this source tree.
79158*/
79159
79160'use strict';
79161
79162var Registry = _dereq_('../../registry');
79163var Lib = _dereq_('../../lib');
79164var Axes = _dereq_('../../plots/cartesian/axes');
79165
79166var histogram2dCalc = _dereq_('../histogram2d/calc');
79167var colorscaleCalc = _dereq_('../../components/colorscale/calc');
79168var convertColumnData = _dereq_('./convert_column_xyz');
79169var clean2dArray = _dereq_('./clean_2d_array');
79170var interp2d = _dereq_('./interp2d');
79171var findEmpties = _dereq_('./find_empties');
79172var makeBoundArray = _dereq_('./make_bound_array');
79173var BADNUM = _dereq_('../../constants/numerical').BADNUM;
79174
79175module.exports = function calc(gd, trace) {
79176 // prepare the raw data
79177 // run makeCalcdata on x and y even for heatmaps, in case of category mappings
79178 var xa = Axes.getFromId(gd, trace.xaxis || 'x');
79179 var ya = Axes.getFromId(gd, trace.yaxis || 'y');
79180 var isContour = Registry.traceIs(trace, 'contour');
79181 var isHist = Registry.traceIs(trace, 'histogram');
79182 var isGL2D = Registry.traceIs(trace, 'gl2d');
79183 var zsmooth = isContour ? 'best' : trace.zsmooth;
79184 var x;
79185 var x0;
79186 var dx;
79187 var y;
79188 var y0;
79189 var dy;
79190 var z;
79191 var i;
79192 var binned;
79193
79194 // cancel minimum tick spacings (only applies to bars and boxes)
79195 xa._minDtick = 0;
79196 ya._minDtick = 0;
79197
79198 if(isHist) {
79199 binned = histogram2dCalc(gd, trace);
79200 x = binned.x;
79201 x0 = binned.x0;
79202 dx = binned.dx;
79203 y = binned.y;
79204 y0 = binned.y0;
79205 dy = binned.dy;
79206 z = binned.z;
79207 } else {
79208 var zIn = trace.z;
79209 if(Lib.isArray1D(zIn)) {
79210 convertColumnData(trace, xa, ya, 'x', 'y', ['z']);
79211 x = trace._x;
79212 y = trace._y;
79213 zIn = trace._z;
79214 } else {
79215 x = trace._x = trace.x ? xa.makeCalcdata(trace, 'x') : [];
79216 y = trace._y = trace.y ? ya.makeCalcdata(trace, 'y') : [];
79217 }
79218
79219 x0 = trace.x0;
79220 dx = trace.dx;
79221 y0 = trace.y0;
79222 dy = trace.dy;
79223
79224 z = clean2dArray(zIn, trace, xa, ya);
79225 }
79226
79227 if(xa.rangebreaks || ya.rangebreaks) {
79228 z = dropZonBreaks(x, y, z);
79229
79230 if(!isHist) {
79231 x = skipBreaks(x);
79232 y = skipBreaks(y);
79233
79234 trace._x = x;
79235 trace._y = y;
79236 }
79237 }
79238
79239 if(!isHist && (isContour || trace.connectgaps)) {
79240 trace._emptypoints = findEmpties(z);
79241 interp2d(z, trace._emptypoints);
79242 }
79243
79244 function noZsmooth(msg) {
79245 zsmooth = trace._input.zsmooth = trace.zsmooth = false;
79246 Lib.warn('cannot use zsmooth: "fast": ' + msg);
79247 }
79248
79249 // check whether we really can smooth (ie all boxes are about the same size)
79250 if(zsmooth === 'fast') {
79251 if(xa.type === 'log' || ya.type === 'log') {
79252 noZsmooth('log axis found');
79253 } else if(!isHist) {
79254 if(x.length) {
79255 var avgdx = (x[x.length - 1] - x[0]) / (x.length - 1);
79256 var maxErrX = Math.abs(avgdx / 100);
79257 for(i = 0; i < x.length - 1; i++) {
79258 if(Math.abs(x[i + 1] - x[i] - avgdx) > maxErrX) {
79259 noZsmooth('x scale is not linear');
79260 break;
79261 }
79262 }
79263 }
79264 if(y.length && zsmooth === 'fast') {
79265 var avgdy = (y[y.length - 1] - y[0]) / (y.length - 1);
79266 var maxErrY = Math.abs(avgdy / 100);
79267 for(i = 0; i < y.length - 1; i++) {
79268 if(Math.abs(y[i + 1] - y[i] - avgdy) > maxErrY) {
79269 noZsmooth('y scale is not linear');
79270 break;
79271 }
79272 }
79273 }
79274 }
79275 }
79276
79277 // create arrays of brick boundaries, to be used by autorange and heatmap.plot
79278 var xlen = Lib.maxRowLength(z);
79279 var xIn = trace.xtype === 'scaled' ? '' : x;
79280 var xArray = makeBoundArray(trace, xIn, x0, dx, xlen, xa);
79281 var yIn = trace.ytype === 'scaled' ? '' : y;
79282 var yArray = makeBoundArray(trace, yIn, y0, dy, z.length, ya);
79283
79284 // handled in gl2d convert step
79285 if(!isGL2D) {
79286 trace._extremes[xa._id] = Axes.findExtremes(xa, xArray);
79287 trace._extremes[ya._id] = Axes.findExtremes(ya, yArray);
79288 }
79289
79290 var cd0 = {
79291 x: xArray,
79292 y: yArray,
79293 z: z,
79294 text: trace._text || trace.text,
79295 hovertext: trace._hovertext || trace.hovertext
79296 };
79297
79298 if(xIn && xIn.length === xArray.length - 1) cd0.xCenter = xIn;
79299 if(yIn && yIn.length === yArray.length - 1) cd0.yCenter = yIn;
79300
79301 if(isHist) {
79302 cd0.xRanges = binned.xRanges;
79303 cd0.yRanges = binned.yRanges;
79304 cd0.pts = binned.pts;
79305 }
79306
79307 if(!isContour) {
79308 colorscaleCalc(gd, trace, {vals: z, cLetter: 'z'});
79309 }
79310
79311 if(isContour && trace.contours && trace.contours.coloring === 'heatmap') {
79312 var dummyTrace = {
79313 type: trace.type === 'contour' ? 'heatmap' : 'histogram2d',
79314 xcalendar: trace.xcalendar,
79315 ycalendar: trace.ycalendar
79316 };
79317 cd0.xfill = makeBoundArray(dummyTrace, xIn, x0, dx, xlen, xa);
79318 cd0.yfill = makeBoundArray(dummyTrace, yIn, y0, dy, z.length, ya);
79319 }
79320
79321 return [cd0];
79322};
79323
79324function skipBreaks(a) {
79325 var b = [];
79326 var len = a.length;
79327 for(var i = 0; i < len; i++) {
79328 var v = a[i];
79329 if(v !== BADNUM) b.push(v);
79330 }
79331 return b;
79332}
79333
79334function dropZonBreaks(x, y, z) {
79335 var newZ = [];
79336 var k = -1;
79337 for(var i = 0; i < z.length; i++) {
79338 if(y[i] === BADNUM) continue;
79339 k++;
79340 newZ[k] = [];
79341 for(var j = 0; j < z[i].length; j++) {
79342 if(x[j] === BADNUM) continue;
79343
79344 newZ[k].push(z[i][j]);
79345 }
79346 }
79347 return newZ;
79348}
79349
79350},{"../../components/colorscale/calc":60,"../../constants/numerical":158,"../../lib":178,"../../plots/cartesian/axes":222,"../../registry":269,"../histogram2d/calc":359,"./clean_2d_array":332,"./convert_column_xyz":334,"./find_empties":336,"./interp2d":339,"./make_bound_array":340}],332:[function(_dereq_,module,exports){
79351/**
79352* Copyright 2012-2020, Plotly, Inc.
79353* All rights reserved.
79354*
79355* This source code is licensed under the MIT license found in the
79356* LICENSE file in the root directory of this source tree.
79357*/
79358
79359'use strict';
79360
79361var isNumeric = _dereq_('fast-isnumeric');
79362var Lib = _dereq_('../../lib');
79363var BADNUM = _dereq_('../../constants/numerical').BADNUM;
79364
79365module.exports = function clean2dArray(zOld, trace, xa, ya) {
79366 var rowlen, collen, getCollen, old2new, i, j;
79367
79368 function cleanZvalue(v) {
79369 if(!isNumeric(v)) return undefined;
79370 return +v;
79371 }
79372
79373 if(trace && trace.transpose) {
79374 rowlen = 0;
79375 for(i = 0; i < zOld.length; i++) rowlen = Math.max(rowlen, zOld[i].length);
79376 if(rowlen === 0) return false;
79377 getCollen = function(zOld) { return zOld.length; };
79378 old2new = function(zOld, i, j) { return (zOld[j] || [])[i]; };
79379 } else {
79380 rowlen = zOld.length;
79381 getCollen = function(zOld, i) { return zOld[i].length; };
79382 old2new = function(zOld, i, j) { return (zOld[i] || [])[j]; };
79383 }
79384
79385 var padOld2new = function(zOld, i, j) {
79386 if(i === BADNUM || j === BADNUM) return BADNUM;
79387 return old2new(zOld, i, j);
79388 };
79389
79390 function axisMapping(ax) {
79391 if(trace && trace.type !== 'carpet' && trace.type !== 'contourcarpet' &&
79392 ax && ax.type === 'category' && trace['_' + ax._id.charAt(0)].length) {
79393 var axLetter = ax._id.charAt(0);
79394 var axMapping = {};
79395 var traceCategories = trace['_' + axLetter + 'CategoryMap'] || trace[axLetter];
79396 for(i = 0; i < traceCategories.length; i++) {
79397 axMapping[traceCategories[i]] = i;
79398 }
79399 return function(i) {
79400 var ind = axMapping[ax._categories[i]];
79401 return ind + 1 ? ind : BADNUM;
79402 };
79403 } else {
79404 return Lib.identity;
79405 }
79406 }
79407
79408 var xMap = axisMapping(xa);
79409 var yMap = axisMapping(ya);
79410
79411 if(ya && ya.type === 'category') rowlen = ya._categories.length;
79412 var zNew = new Array(rowlen);
79413
79414 for(i = 0; i < rowlen; i++) {
79415 if(xa && xa.type === 'category') {
79416 collen = xa._categories.length;
79417 } else {
79418 collen = getCollen(zOld, i);
79419 }
79420 zNew[i] = new Array(collen);
79421 for(j = 0; j < collen; j++) zNew[i][j] = cleanZvalue(padOld2new(zOld, yMap(i), xMap(j)));
79422 }
79423
79424 return zNew;
79425};
79426
79427},{"../../constants/numerical":158,"../../lib":178,"fast-isnumeric":18}],333:[function(_dereq_,module,exports){
79428/**
79429* Copyright 2012-2020, Plotly, Inc.
79430* All rights reserved.
79431*
79432* This source code is licensed under the MIT license found in the
79433* LICENSE file in the root directory of this source tree.
79434*/
79435
79436'use strict';
79437
79438module.exports = {
79439 min: 'zmin',
79440 max: 'zmax'
79441};
79442
79443},{}],334:[function(_dereq_,module,exports){
79444/**
79445* Copyright 2012-2020, Plotly, Inc.
79446* All rights reserved.
79447*
79448* This source code is licensed under the MIT license found in the
79449* LICENSE file in the root directory of this source tree.
79450*/
79451
79452
79453'use strict';
79454
79455var Lib = _dereq_('../../lib');
79456var BADNUM = _dereq_('../../constants/numerical').BADNUM;
79457
79458module.exports = function convertColumnData(trace, ax1, ax2, var1Name, var2Name, arrayVarNames) {
79459 var colLen = trace._length;
79460 var col1 = ax1.makeCalcdata(trace, var1Name);
79461 var col2 = ax2.makeCalcdata(trace, var2Name);
79462 var textCol = trace.text;
79463 var hasColumnText = (textCol !== undefined && Lib.isArray1D(textCol));
79464 var hoverTextCol = trace.hovertext;
79465 var hasColumnHoverText = (hoverTextCol !== undefined && Lib.isArray1D(hoverTextCol));
79466 var i, j;
79467
79468 var col1dv = Lib.distinctVals(col1);
79469 var col1vals = col1dv.vals;
79470 var col2dv = Lib.distinctVals(col2);
79471 var col2vals = col2dv.vals;
79472 var newArrays = [];
79473 var text;
79474 var hovertext;
79475
79476 var nI = col2vals.length;
79477 var nJ = col1vals.length;
79478
79479 for(i = 0; i < arrayVarNames.length; i++) {
79480 newArrays[i] = Lib.init2dArray(nI, nJ);
79481 }
79482
79483 if(hasColumnText) {
79484 text = Lib.init2dArray(nI, nJ);
79485 }
79486 if(hasColumnHoverText) {
79487 hovertext = Lib.init2dArray(nI, nJ);
79488 }
79489
79490 var after2before = Lib.init2dArray(nI, nJ);
79491
79492 for(i = 0; i < colLen; i++) {
79493 if(col1[i] !== BADNUM && col2[i] !== BADNUM) {
79494 var i1 = Lib.findBin(col1[i] + col1dv.minDiff / 2, col1vals);
79495 var i2 = Lib.findBin(col2[i] + col2dv.minDiff / 2, col2vals);
79496
79497 for(j = 0; j < arrayVarNames.length; j++) {
79498 var arrayVarName = arrayVarNames[j];
79499 var arrayVar = trace[arrayVarName];
79500 var newArray = newArrays[j];
79501 newArray[i2][i1] = arrayVar[i];
79502 after2before[i2][i1] = i;
79503 }
79504
79505 if(hasColumnText) text[i2][i1] = textCol[i];
79506 if(hasColumnHoverText) hovertext[i2][i1] = hoverTextCol[i];
79507 }
79508 }
79509
79510 trace['_' + var1Name] = col1vals;
79511 trace['_' + var2Name] = col2vals;
79512 for(j = 0; j < arrayVarNames.length; j++) {
79513 trace['_' + arrayVarNames[j]] = newArrays[j];
79514 }
79515 if(hasColumnText) trace._text = text;
79516 if(hasColumnHoverText) trace._hovertext = hovertext;
79517
79518 if(ax1 && ax1.type === 'category') {
79519 trace['_' + var1Name + 'CategoryMap'] = col1vals.map(function(v) { return ax1._categories[v];});
79520 }
79521
79522 if(ax2 && ax2.type === 'category') {
79523 trace['_' + var2Name + 'CategoryMap'] = col2vals.map(function(v) { return ax2._categories[v];});
79524 }
79525
79526 trace._after2before = after2before;
79527};
79528
79529},{"../../constants/numerical":158,"../../lib":178}],335:[function(_dereq_,module,exports){
79530/**
79531* Copyright 2012-2020, Plotly, Inc.
79532* All rights reserved.
79533*
79534* This source code is licensed under the MIT license found in the
79535* LICENSE file in the root directory of this source tree.
79536*/
79537
79538
79539'use strict';
79540
79541var Lib = _dereq_('../../lib');
79542
79543var handleXYZDefaults = _dereq_('./xyz_defaults');
79544var handleStyleDefaults = _dereq_('./style_defaults');
79545var colorscaleDefaults = _dereq_('../../components/colorscale/defaults');
79546var attributes = _dereq_('./attributes');
79547
79548
79549module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
79550 function coerce(attr, dflt) {
79551 return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
79552 }
79553
79554 var validData = handleXYZDefaults(traceIn, traceOut, coerce, layout);
79555 if(!validData) {
79556 traceOut.visible = false;
79557 return;
79558 }
79559
79560 coerce('text');
79561 coerce('hovertext');
79562 coerce('hovertemplate');
79563
79564 handleStyleDefaults(traceIn, traceOut, coerce, layout);
79565
79566 coerce('hoverongaps');
79567 coerce('connectgaps', Lib.isArray1D(traceOut.z) && (traceOut.zsmooth !== false));
79568
79569 colorscaleDefaults(traceIn, traceOut, layout, coerce, {prefix: '', cLetter: 'z'});
79570};
79571
79572},{"../../components/colorscale/defaults":62,"../../lib":178,"./attributes":330,"./style_defaults":343,"./xyz_defaults":344}],336:[function(_dereq_,module,exports){
79573/**
79574* Copyright 2012-2020, Plotly, Inc.
79575* All rights reserved.
79576*
79577* This source code is licensed under the MIT license found in the
79578* LICENSE file in the root directory of this source tree.
79579*/
79580
79581'use strict';
79582
79583var maxRowLength = _dereq_('../../lib').maxRowLength;
79584
79585/* Return a list of empty points in 2D array z
79586 * each empty point z[i][j] gives an array [i, j, neighborCount]
79587 * neighborCount is the count of 4 nearest neighbors that DO exist
79588 * this is to give us an order of points to evaluate for interpolation.
79589 * if no neighbors exist, we iteratively look for neighbors that HAVE
79590 * neighbors, and add a fractional neighborCount
79591 */
79592module.exports = function findEmpties(z) {
79593 var empties = [];
79594 var neighborHash = {};
79595 var noNeighborList = [];
79596 var nextRow = z[0];
79597 var row = [];
79598 var blank = [0, 0, 0];
79599 var rowLength = maxRowLength(z);
79600 var prevRow;
79601 var i;
79602 var j;
79603 var thisPt;
79604 var p;
79605 var neighborCount;
79606 var newNeighborHash;
79607 var foundNewNeighbors;
79608
79609 for(i = 0; i < z.length; i++) {
79610 prevRow = row;
79611 row = nextRow;
79612 nextRow = z[i + 1] || [];
79613 for(j = 0; j < rowLength; j++) {
79614 if(row[j] === undefined) {
79615 neighborCount = (row[j - 1] !== undefined ? 1 : 0) +
79616 (row[j + 1] !== undefined ? 1 : 0) +
79617 (prevRow[j] !== undefined ? 1 : 0) +
79618 (nextRow[j] !== undefined ? 1 : 0);
79619
79620 if(neighborCount) {
79621 // for this purpose, don't count off-the-edge points
79622 // as undefined neighbors
79623 if(i === 0) neighborCount++;
79624 if(j === 0) neighborCount++;
79625 if(i === z.length - 1) neighborCount++;
79626 if(j === row.length - 1) neighborCount++;
79627
79628 // if all neighbors that could exist do, we don't
79629 // need this for finding farther neighbors
79630 if(neighborCount < 4) {
79631 neighborHash[[i, j]] = [i, j, neighborCount];
79632 }
79633
79634 empties.push([i, j, neighborCount]);
79635 } else noNeighborList.push([i, j]);
79636 }
79637 }
79638 }
79639
79640 while(noNeighborList.length) {
79641 newNeighborHash = {};
79642 foundNewNeighbors = false;
79643
79644 // look for cells that now have neighbors but didn't before
79645 for(p = noNeighborList.length - 1; p >= 0; p--) {
79646 thisPt = noNeighborList[p];
79647 i = thisPt[0];
79648 j = thisPt[1];
79649
79650 neighborCount = ((neighborHash[[i - 1, j]] || blank)[2] +
79651 (neighborHash[[i + 1, j]] || blank)[2] +
79652 (neighborHash[[i, j - 1]] || blank)[2] +
79653 (neighborHash[[i, j + 1]] || blank)[2]) / 20;
79654
79655 if(neighborCount) {
79656 newNeighborHash[thisPt] = [i, j, neighborCount];
79657 noNeighborList.splice(p, 1);
79658 foundNewNeighbors = true;
79659 }
79660 }
79661
79662 if(!foundNewNeighbors) {
79663 throw 'findEmpties iterated with no new neighbors';
79664 }
79665
79666 // put these new cells into the main neighbor list
79667 for(thisPt in newNeighborHash) {
79668 neighborHash[thisPt] = newNeighborHash[thisPt];
79669 empties.push(newNeighborHash[thisPt]);
79670 }
79671 }
79672
79673 // sort the full list in descending order of neighbor count
79674 return empties.sort(function(a, b) { return b[2] - a[2]; });
79675};
79676
79677},{"../../lib":178}],337:[function(_dereq_,module,exports){
79678/**
79679* Copyright 2012-2020, Plotly, Inc.
79680* All rights reserved.
79681*
79682* This source code is licensed under the MIT license found in the
79683* LICENSE file in the root directory of this source tree.
79684*/
79685
79686'use strict';
79687
79688var Fx = _dereq_('../../components/fx');
79689var Lib = _dereq_('../../lib');
79690var Axes = _dereq_('../../plots/cartesian/axes');
79691var extractOpts = _dereq_('../../components/colorscale').extractOpts;
79692
79693module.exports = function hoverPoints(pointData, xval, yval, hovermode, hoverLayer, contour) {
79694 var cd0 = pointData.cd[0];
79695 var trace = cd0.trace;
79696 var xa = pointData.xa;
79697 var ya = pointData.ya;
79698 var x = cd0.x;
79699 var y = cd0.y;
79700 var z = cd0.z;
79701 var xc = cd0.xCenter;
79702 var yc = cd0.yCenter;
79703 var zmask = cd0.zmask;
79704 var zhoverformat = trace.zhoverformat;
79705 var x2 = x;
79706 var y2 = y;
79707
79708 var xl, yl, nx, ny;
79709
79710 if(pointData.index !== false) {
79711 try {
79712 nx = Math.round(pointData.index[1]);
79713 ny = Math.round(pointData.index[0]);
79714 } catch(e) {
79715 Lib.error('Error hovering on heatmap, ' +
79716 'pointNumber must be [row,col], found:', pointData.index);
79717 return;
79718 }
79719 if(nx < 0 || nx >= z[0].length || ny < 0 || ny > z.length) {
79720 return;
79721 }
79722 } else if(Fx.inbox(xval - x[0], xval - x[x.length - 1], 0) > 0 ||
79723 Fx.inbox(yval - y[0], yval - y[y.length - 1], 0) > 0) {
79724 return;
79725 } else {
79726 if(contour) {
79727 var i2;
79728 x2 = [2 * x[0] - x[1]];
79729
79730 for(i2 = 1; i2 < x.length; i2++) {
79731 x2.push((x[i2] + x[i2 - 1]) / 2);
79732 }
79733 x2.push([2 * x[x.length - 1] - x[x.length - 2]]);
79734
79735 y2 = [2 * y[0] - y[1]];
79736 for(i2 = 1; i2 < y.length; i2++) {
79737 y2.push((y[i2] + y[i2 - 1]) / 2);
79738 }
79739 y2.push([2 * y[y.length - 1] - y[y.length - 2]]);
79740 }
79741 nx = Math.max(0, Math.min(x2.length - 2, Lib.findBin(xval, x2)));
79742 ny = Math.max(0, Math.min(y2.length - 2, Lib.findBin(yval, y2)));
79743 }
79744
79745 var x0 = xa.c2p(x[nx]);
79746 var x1 = xa.c2p(x[nx + 1]);
79747 var y0 = ya.c2p(y[ny]);
79748 var y1 = ya.c2p(y[ny + 1]);
79749
79750 if(contour) {
79751 x1 = x0;
79752 xl = x[nx];
79753 y1 = y0;
79754 yl = y[ny];
79755 } else {
79756 xl = xc ? xc[nx] : ((x[nx] + x[nx + 1]) / 2);
79757 yl = yc ? yc[ny] : ((y[ny] + y[ny + 1]) / 2);
79758
79759 if(xa && xa.type === 'category') xl = x[nx];
79760 if(ya && ya.type === 'category') yl = y[ny];
79761
79762 if(trace.zsmooth) {
79763 x0 = x1 = xa.c2p(xl);
79764 y0 = y1 = ya.c2p(yl);
79765 }
79766 }
79767
79768 var zVal = z[ny][nx];
79769 if(zmask && !zmask[ny][nx]) zVal = undefined;
79770
79771 if(zVal === undefined && !trace.hoverongaps) return;
79772
79773 var text;
79774 if(Array.isArray(cd0.hovertext) && Array.isArray(cd0.hovertext[ny])) {
79775 text = cd0.hovertext[ny][nx];
79776 } else if(Array.isArray(cd0.text) && Array.isArray(cd0.text[ny])) {
79777 text = cd0.text[ny][nx];
79778 }
79779
79780 // dummy axis for formatting the z value
79781 var cOpts = extractOpts(trace);
79782 var dummyAx = {
79783 type: 'linear',
79784 range: [cOpts.min, cOpts.max],
79785 hoverformat: zhoverformat,
79786 _separators: xa._separators,
79787 _numFormat: xa._numFormat
79788 };
79789 var zLabel = Axes.tickText(dummyAx, zVal, 'hover').text;
79790
79791 return [Lib.extendFlat(pointData, {
79792 index: trace._after2before ? trace._after2before[ny][nx] : [ny, nx],
79793 // never let a 2D override 1D type as closest point
79794 distance: pointData.maxHoverDistance,
79795 spikeDistance: pointData.maxSpikeDistance,
79796 x0: x0,
79797 x1: x1,
79798 y0: y0,
79799 y1: y1,
79800 xLabelVal: xl,
79801 yLabelVal: yl,
79802 zLabelVal: zVal,
79803 zLabel: zLabel,
79804 text: text
79805 })];
79806};
79807
79808},{"../../components/colorscale":64,"../../components/fx":92,"../../lib":178,"../../plots/cartesian/axes":222}],338:[function(_dereq_,module,exports){
79809/**
79810* Copyright 2012-2020, Plotly, Inc.
79811* All rights reserved.
79812*
79813* This source code is licensed under the MIT license found in the
79814* LICENSE file in the root directory of this source tree.
79815*/
79816
79817'use strict';
79818
79819module.exports = {
79820 attributes: _dereq_('./attributes'),
79821 supplyDefaults: _dereq_('./defaults'),
79822 calc: _dereq_('./calc'),
79823 plot: _dereq_('./plot'),
79824 colorbar: _dereq_('./colorbar'),
79825 style: _dereq_('./style'),
79826 hoverPoints: _dereq_('./hover'),
79827
79828 moduleType: 'trace',
79829 name: 'heatmap',
79830 basePlotModule: _dereq_('../../plots/cartesian'),
79831 categories: ['cartesian', 'svg', '2dMap', 'showLegend'],
79832 meta: {
79833
79834 }
79835};
79836
79837},{"../../plots/cartesian":235,"./attributes":330,"./calc":331,"./colorbar":333,"./defaults":335,"./hover":337,"./plot":341,"./style":342}],339:[function(_dereq_,module,exports){
79838/**
79839* Copyright 2012-2020, Plotly, Inc.
79840* All rights reserved.
79841*
79842* This source code is licensed under the MIT license found in the
79843* LICENSE file in the root directory of this source tree.
79844*/
79845
79846'use strict';
79847
79848var Lib = _dereq_('../../lib');
79849
79850var INTERPTHRESHOLD = 1e-2;
79851var NEIGHBORSHIFTS = [[-1, 0], [1, 0], [0, -1], [0, 1]];
79852
79853function correctionOvershoot(maxFractionalChange) {
79854 // start with less overshoot, until we know it's converging,
79855 // then ramp up the overshoot for faster convergence
79856 return 0.5 - 0.25 * Math.min(1, maxFractionalChange * 0.5);
79857}
79858
79859/*
79860 * interp2d: Fill in missing data from a 2D array using an iterative
79861 * poisson equation solver with zero-derivative BC at edges.
79862 * Amazingly, this just amounts to repeatedly averaging all the existing
79863 * nearest neighbors, at least if we don't take x/y scaling into account,
79864 * which is the right approach here where x and y may not even have the
79865 * same units.
79866 *
79867 * @param {array of arrays} z
79868 * The 2D array to fill in. Will be mutated here. Assumed to already be
79869 * cleaned, so all entries are numbers except gaps, which are `undefined`.
79870 * @param {array of arrays} emptyPoints
79871 * Each entry [i, j, neighborCount] for empty points z[i][j] and the number
79872 * of neighbors that are *not* missing. Assumed to be sorted from most to
79873 * least neighbors, as produced by heatmap/find_empties.
79874 */
79875module.exports = function interp2d(z, emptyPoints) {
79876 var maxFractionalChange = 1;
79877 var i;
79878
79879 // one pass to fill in a starting value for all the empties
79880 iterateInterp2d(z, emptyPoints);
79881
79882 // we're don't need to iterate lone empties - remove them
79883 for(i = 0; i < emptyPoints.length; i++) {
79884 if(emptyPoints[i][2] < 4) break;
79885 }
79886 // but don't remove these points from the original array,
79887 // we'll use them for masking, so make a copy.
79888 emptyPoints = emptyPoints.slice(i);
79889
79890 for(i = 0; i < 100 && maxFractionalChange > INTERPTHRESHOLD; i++) {
79891 maxFractionalChange = iterateInterp2d(z, emptyPoints,
79892 correctionOvershoot(maxFractionalChange));
79893 }
79894 if(maxFractionalChange > INTERPTHRESHOLD) {
79895 Lib.log('interp2d didn\'t converge quickly', maxFractionalChange);
79896 }
79897
79898 return z;
79899};
79900
79901function iterateInterp2d(z, emptyPoints, overshoot) {
79902 var maxFractionalChange = 0;
79903 var thisPt;
79904 var i;
79905 var j;
79906 var p;
79907 var q;
79908 var neighborShift;
79909 var neighborRow;
79910 var neighborVal;
79911 var neighborCount;
79912 var neighborSum;
79913 var initialVal;
79914 var minNeighbor;
79915 var maxNeighbor;
79916
79917 for(p = 0; p < emptyPoints.length; p++) {
79918 thisPt = emptyPoints[p];
79919 i = thisPt[0];
79920 j = thisPt[1];
79921 initialVal = z[i][j];
79922 neighborSum = 0;
79923 neighborCount = 0;
79924
79925 for(q = 0; q < 4; q++) {
79926 neighborShift = NEIGHBORSHIFTS[q];
79927 neighborRow = z[i + neighborShift[0]];
79928 if(!neighborRow) continue;
79929 neighborVal = neighborRow[j + neighborShift[1]];
79930 if(neighborVal !== undefined) {
79931 if(neighborSum === 0) {
79932 minNeighbor = maxNeighbor = neighborVal;
79933 } else {
79934 minNeighbor = Math.min(minNeighbor, neighborVal);
79935 maxNeighbor = Math.max(maxNeighbor, neighborVal);
79936 }
79937 neighborCount++;
79938 neighborSum += neighborVal;
79939 }
79940 }
79941
79942 if(neighborCount === 0) {
79943 throw 'iterateInterp2d order is wrong: no defined neighbors';
79944 }
79945
79946 // this is the laplace equation interpolation:
79947 // each point is just the average of its neighbors
79948 // note that this ignores differential x/y scaling
79949 // which I think is the right approach, since we
79950 // don't know what that scaling means
79951 z[i][j] = neighborSum / neighborCount;
79952
79953 if(initialVal === undefined) {
79954 if(neighborCount < 4) maxFractionalChange = 1;
79955 } else {
79956 // we can make large empty regions converge faster
79957 // if we overshoot the change vs the previous value
79958 z[i][j] = (1 + overshoot) * z[i][j] - overshoot * initialVal;
79959
79960 if(maxNeighbor > minNeighbor) {
79961 maxFractionalChange = Math.max(maxFractionalChange,
79962 Math.abs(z[i][j] - initialVal) / (maxNeighbor - minNeighbor));
79963 }
79964 }
79965 }
79966
79967 return maxFractionalChange;
79968}
79969
79970},{"../../lib":178}],340:[function(_dereq_,module,exports){
79971/**
79972* Copyright 2012-2020, Plotly, Inc.
79973* All rights reserved.
79974*
79975* This source code is licensed under the MIT license found in the
79976* LICENSE file in the root directory of this source tree.
79977*/
79978
79979'use strict';
79980
79981var Registry = _dereq_('../../registry');
79982var isArrayOrTypedArray = _dereq_('../../lib').isArrayOrTypedArray;
79983
79984module.exports = function makeBoundArray(trace, arrayIn, v0In, dvIn, numbricks, ax) {
79985 var arrayOut = [];
79986 var isContour = Registry.traceIs(trace, 'contour');
79987 var isHist = Registry.traceIs(trace, 'histogram');
79988 var isGL2D = Registry.traceIs(trace, 'gl2d');
79989 var v0;
79990 var dv;
79991 var i;
79992
79993 var isArrayOfTwoItemsOrMore = isArrayOrTypedArray(arrayIn) && arrayIn.length > 1;
79994
79995 if(isArrayOfTwoItemsOrMore && !isHist && (ax.type !== 'category')) {
79996 var len = arrayIn.length;
79997
79998 // given vals are brick centers
79999 // hopefully length === numbricks, but use this method even if too few are supplied
80000 // and extend it linearly based on the last two points
80001 if(len <= numbricks) {
80002 // contour plots only want the centers
80003 if(isContour || isGL2D) arrayOut = arrayIn.slice(0, numbricks);
80004 else if(numbricks === 1) {
80005 arrayOut = [arrayIn[0] - 0.5, arrayIn[0] + 0.5];
80006 } else {
80007 arrayOut = [1.5 * arrayIn[0] - 0.5 * arrayIn[1]];
80008
80009 for(i = 1; i < len; i++) {
80010 arrayOut.push((arrayIn[i - 1] + arrayIn[i]) * 0.5);
80011 }
80012
80013 arrayOut.push(1.5 * arrayIn[len - 1] - 0.5 * arrayIn[len - 2]);
80014 }
80015
80016 if(len < numbricks) {
80017 var lastPt = arrayOut[arrayOut.length - 1];
80018 var delta = lastPt - arrayOut[arrayOut.length - 2];
80019
80020 for(i = len; i < numbricks; i++) {
80021 lastPt += delta;
80022 arrayOut.push(lastPt);
80023 }
80024 }
80025 } else {
80026 // hopefully length === numbricks+1, but do something regardless:
80027 // given vals are brick boundaries
80028 return isContour ?
80029 arrayIn.slice(0, numbricks) : // we must be strict for contours
80030 arrayIn.slice(0, numbricks + 1);
80031 }
80032 } else {
80033 var calendar = trace[ax._id.charAt(0) + 'calendar'];
80034
80035 if(isHist) {
80036 v0 = ax.r2c(v0In, 0, calendar);
80037 } else {
80038 if(isArrayOrTypedArray(arrayIn) && arrayIn.length === 1) {
80039 v0 = arrayIn[0];
80040 } else if(v0In === undefined) {
80041 v0 = 0;
80042 } else {
80043 var fn = ax.type === 'log' ? ax.d2c : ax.r2c;
80044 v0 = fn(v0In, 0, calendar);
80045 }
80046 }
80047
80048 dv = dvIn || 1;
80049
80050 for(i = (isContour || isGL2D) ? 0 : -0.5; i < numbricks; i++) {
80051 arrayOut.push(v0 + dv * i);
80052 }
80053 }
80054
80055 return arrayOut;
80056};
80057
80058},{"../../lib":178,"../../registry":269}],341:[function(_dereq_,module,exports){
80059/**
80060* Copyright 2012-2020, Plotly, Inc.
80061* All rights reserved.
80062*
80063* This source code is licensed under the MIT license found in the
80064* LICENSE file in the root directory of this source tree.
80065*/
80066
80067
80068'use strict';
80069
80070var d3 = _dereq_('d3');
80071var tinycolor = _dereq_('tinycolor2');
80072
80073var Registry = _dereq_('../../registry');
80074var Lib = _dereq_('../../lib');
80075var makeColorScaleFuncFromTrace = _dereq_('../../components/colorscale').makeColorScaleFuncFromTrace;
80076var xmlnsNamespaces = _dereq_('../../constants/xmlns_namespaces');
80077
80078module.exports = function(gd, plotinfo, cdheatmaps, heatmapLayer) {
80079 var xa = plotinfo.xaxis;
80080 var ya = plotinfo.yaxis;
80081
80082 Lib.makeTraceGroups(heatmapLayer, cdheatmaps, 'hm').each(function(cd) {
80083 var plotGroup = d3.select(this);
80084 var cd0 = cd[0];
80085 var trace = cd0.trace;
80086
80087 var z = cd0.z;
80088 var x = cd0.x;
80089 var y = cd0.y;
80090 var xc = cd0.xCenter;
80091 var yc = cd0.yCenter;
80092 var isContour = Registry.traceIs(trace, 'contour');
80093 var zsmooth = isContour ? 'best' : trace.zsmooth;
80094
80095 // get z dims
80096 var m = z.length;
80097 var n = Lib.maxRowLength(z);
80098 var xrev = false;
80099 var yrev = false;
80100
80101 var left, right, temp, top, bottom, i;
80102
80103 // TODO: if there are multiple overlapping categorical heatmaps,
80104 // or if we allow category sorting, then the categories may not be
80105 // sequential... may need to reorder and/or expand z
80106
80107 // Get edges of png in pixels (xa.c2p() maps axes coordinates to pixel coordinates)
80108 // figure out if either axis is reversed (y is usually reversed, in pixel coords)
80109 // also clip the image to maximum 50% outside the visible plot area
80110 // bigger image lets you pan more naturally, but slows performance.
80111 // TODO: use low-resolution images outside the visible plot for panning
80112 // these while loops find the first and last brick bounds that are defined
80113 // (in case of log of a negative)
80114 i = 0;
80115 while(left === undefined && i < x.length - 1) {
80116 left = xa.c2p(x[i]);
80117 i++;
80118 }
80119 i = x.length - 1;
80120 while(right === undefined && i > 0) {
80121 right = xa.c2p(x[i]);
80122 i--;
80123 }
80124
80125 if(right < left) {
80126 temp = right;
80127 right = left;
80128 left = temp;
80129 xrev = true;
80130 }
80131
80132 i = 0;
80133 while(top === undefined && i < y.length - 1) {
80134 top = ya.c2p(y[i]);
80135 i++;
80136 }
80137 i = y.length - 1;
80138 while(bottom === undefined && i > 0) {
80139 bottom = ya.c2p(y[i]);
80140 i--;
80141 }
80142
80143 if(bottom < top) {
80144 temp = top;
80145 top = bottom;
80146 bottom = temp;
80147 yrev = true;
80148 }
80149
80150 // for contours with heatmap fill, we generate the boundaries based on
80151 // brick centers but then use the brick edges for drawing the bricks
80152 if(isContour) {
80153 xc = x;
80154 yc = y;
80155 x = cd0.xfill;
80156 y = cd0.yfill;
80157 }
80158
80159 // make an image that goes at most half a screen off either side, to keep
80160 // time reasonable when you zoom in. if zsmooth is true/fast, don't worry
80161 // about this, because zooming doesn't increase number of pixels
80162 // if zsmooth is best, don't include anything off screen because it takes too long
80163 if(zsmooth !== 'fast') {
80164 var extra = zsmooth === 'best' ? 0 : 0.5;
80165 left = Math.max(-extra * xa._length, left);
80166 right = Math.min((1 + extra) * xa._length, right);
80167 top = Math.max(-extra * ya._length, top);
80168 bottom = Math.min((1 + extra) * ya._length, bottom);
80169 }
80170
80171 var imageWidth = Math.round(right - left);
80172 var imageHeight = Math.round(bottom - top);
80173
80174 // setup image nodes
80175
80176 // if image is entirely off-screen, don't even draw it
80177 var isOffScreen = (imageWidth <= 0 || imageHeight <= 0);
80178
80179 if(isOffScreen) {
80180 var noImage = plotGroup.selectAll('image').data([]);
80181 noImage.exit().remove();
80182 return;
80183 }
80184
80185 // generate image data
80186
80187 var canvasW, canvasH;
80188 if(zsmooth === 'fast') {
80189 canvasW = n;
80190 canvasH = m;
80191 } else {
80192 canvasW = imageWidth;
80193 canvasH = imageHeight;
80194 }
80195
80196 var canvas = document.createElement('canvas');
80197 canvas.width = canvasW;
80198 canvas.height = canvasH;
80199 var context = canvas.getContext('2d');
80200
80201 var sclFunc = makeColorScaleFuncFromTrace(trace, {noNumericCheck: true, returnArray: true});
80202
80203 // map brick boundaries to image pixels
80204 var xpx,
80205 ypx;
80206 if(zsmooth === 'fast') {
80207 xpx = xrev ?
80208 function(index) { return n - 1 - index; } :
80209 Lib.identity;
80210 ypx = yrev ?
80211 function(index) { return m - 1 - index; } :
80212 Lib.identity;
80213 } else {
80214 xpx = function(index) {
80215 return Lib.constrain(Math.round(xa.c2p(x[index]) - left),
80216 0, imageWidth);
80217 };
80218 ypx = function(index) {
80219 return Lib.constrain(Math.round(ya.c2p(y[index]) - top),
80220 0, imageHeight);
80221 };
80222 }
80223
80224 // build the pixel map brick-by-brick
80225 // cruise through z-matrix row-by-row
80226 // build a brick at each z-matrix value
80227 var yi = ypx(0);
80228 var yb = [yi, yi];
80229 var xbi = xrev ? 0 : 1;
80230 var ybi = yrev ? 0 : 1;
80231 // for collecting an average luminosity of the heatmap
80232 var pixcount = 0;
80233 var rcount = 0;
80234 var gcount = 0;
80235 var bcount = 0;
80236
80237 var xb, j, xi, v, row, c;
80238
80239 function setColor(v, pixsize) {
80240 if(v !== undefined) {
80241 var c = sclFunc(v);
80242 c[0] = Math.round(c[0]);
80243 c[1] = Math.round(c[1]);
80244 c[2] = Math.round(c[2]);
80245
80246 pixcount += pixsize;
80247 rcount += c[0] * pixsize;
80248 gcount += c[1] * pixsize;
80249 bcount += c[2] * pixsize;
80250 return c;
80251 }
80252 return [0, 0, 0, 0];
80253 }
80254
80255 function interpColor(r0, r1, xinterp, yinterp) {
80256 var z00 = r0[xinterp.bin0];
80257 if(z00 === undefined) return setColor(undefined, 1);
80258
80259 var z01 = r0[xinterp.bin1];
80260 var z10 = r1[xinterp.bin0];
80261 var z11 = r1[xinterp.bin1];
80262 var dx = (z01 - z00) || 0;
80263 var dy = (z10 - z00) || 0;
80264 var dxy;
80265
80266 // the bilinear interpolation term needs different calculations
80267 // for all the different permutations of missing data
80268 // among the neighbors of the main point, to ensure
80269 // continuity across brick boundaries.
80270 if(z01 === undefined) {
80271 if(z11 === undefined) dxy = 0;
80272 else if(z10 === undefined) dxy = 2 * (z11 - z00);
80273 else dxy = (2 * z11 - z10 - z00) * 2 / 3;
80274 } else if(z11 === undefined) {
80275 if(z10 === undefined) dxy = 0;
80276 else dxy = (2 * z00 - z01 - z10) * 2 / 3;
80277 } else if(z10 === undefined) dxy = (2 * z11 - z01 - z00) * 2 / 3;
80278 else dxy = (z11 + z00 - z01 - z10);
80279
80280 return setColor(z00 + xinterp.frac * dx + yinterp.frac * (dy + xinterp.frac * dxy));
80281 }
80282
80283 if(zsmooth) { // best or fast, works fastest with imageData
80284 var pxIndex = 0;
80285 var pixels;
80286
80287 try {
80288 pixels = new Uint8Array(imageWidth * imageHeight * 4);
80289 } catch(e) {
80290 pixels = new Array(imageWidth * imageHeight * 4);
80291 }
80292
80293 if(zsmooth === 'best') {
80294 var xForPx = xc || x;
80295 var yForPx = yc || y;
80296 var xPixArray = new Array(xForPx.length);
80297 var yPixArray = new Array(yForPx.length);
80298 var xinterpArray = new Array(imageWidth);
80299 var findInterpX = xc ? findInterpFromCenters : findInterp;
80300 var findInterpY = yc ? findInterpFromCenters : findInterp;
80301 var yinterp, r0, r1;
80302
80303 // first make arrays of x and y pixel locations of brick boundaries
80304 for(i = 0; i < xForPx.length; i++) xPixArray[i] = Math.round(xa.c2p(xForPx[i]) - left);
80305 for(i = 0; i < yForPx.length; i++) yPixArray[i] = Math.round(ya.c2p(yForPx[i]) - top);
80306
80307 // then make arrays of interpolations
80308 // (bin0=closest, bin1=next, frac=fractional dist.)
80309 for(i = 0; i < imageWidth; i++) xinterpArray[i] = findInterpX(i, xPixArray);
80310
80311 // now do the interpolations and fill the png
80312 for(j = 0; j < imageHeight; j++) {
80313 yinterp = findInterpY(j, yPixArray);
80314 r0 = z[yinterp.bin0];
80315 r1 = z[yinterp.bin1];
80316 for(i = 0; i < imageWidth; i++, pxIndex += 4) {
80317 c = interpColor(r0, r1, xinterpArray[i], yinterp);
80318 putColor(pixels, pxIndex, c);
80319 }
80320 }
80321 } else { // zsmooth = fast
80322 for(j = 0; j < m; j++) {
80323 row = z[j];
80324 yb = ypx(j);
80325 for(i = 0; i < imageWidth; i++) {
80326 c = setColor(row[i], 1);
80327 pxIndex = (yb * imageWidth + xpx(i)) * 4;
80328 putColor(pixels, pxIndex, c);
80329 }
80330 }
80331 }
80332
80333 var imageData = context.createImageData(imageWidth, imageHeight);
80334 try {
80335 imageData.data.set(pixels);
80336 } catch(e) {
80337 var pxArray = imageData.data;
80338 var dlen = pxArray.length;
80339 for(j = 0; j < dlen; j ++) {
80340 pxArray[j] = pixels[j];
80341 }
80342 }
80343
80344 context.putImageData(imageData, 0, 0);
80345 } else { // zsmooth = false -> filling potentially large bricks works fastest with fillRect
80346 // gaps do not need to be exact integers, but if they *are* we will get
80347 // cleaner edges by rounding at least one edge
80348 var xGap = trace.xgap;
80349 var yGap = trace.ygap;
80350 var xGapLeft = Math.floor(xGap / 2);
80351 var yGapTop = Math.floor(yGap / 2);
80352
80353 for(j = 0; j < m; j++) {
80354 row = z[j];
80355 yb.reverse();
80356 yb[ybi] = ypx(j + 1);
80357 if(yb[0] === yb[1] || yb[0] === undefined || yb[1] === undefined) {
80358 continue;
80359 }
80360 xi = xpx(0);
80361 xb = [xi, xi];
80362 for(i = 0; i < n; i++) {
80363 // build one color brick!
80364 xb.reverse();
80365 xb[xbi] = xpx(i + 1);
80366 if(xb[0] === xb[1] || xb[0] === undefined || xb[1] === undefined) {
80367 continue;
80368 }
80369 v = row[i];
80370 c = setColor(v, (xb[1] - xb[0]) * (yb[1] - yb[0]));
80371 context.fillStyle = 'rgba(' + c.join(',') + ')';
80372
80373 context.fillRect(xb[0] + xGapLeft, yb[0] + yGapTop,
80374 xb[1] - xb[0] - xGap, yb[1] - yb[0] - yGap);
80375 }
80376 }
80377 }
80378
80379 rcount = Math.round(rcount / pixcount);
80380 gcount = Math.round(gcount / pixcount);
80381 bcount = Math.round(bcount / pixcount);
80382 var avgColor = tinycolor('rgb(' + rcount + ',' + gcount + ',' + bcount + ')');
80383
80384 gd._hmpixcount = (gd._hmpixcount||0) + pixcount;
80385 gd._hmlumcount = (gd._hmlumcount||0) + pixcount * avgColor.getLuminance();
80386
80387 var image3 = plotGroup.selectAll('image')
80388 .data(cd);
80389
80390 image3.enter().append('svg:image').attr({
80391 xmlns: xmlnsNamespaces.svg,
80392 preserveAspectRatio: 'none'
80393 });
80394
80395 image3.attr({
80396 height: imageHeight,
80397 width: imageWidth,
80398 x: left,
80399 y: top,
80400 'xlink:href': canvas.toDataURL('image/png')
80401 });
80402 });
80403};
80404
80405// get interpolated bin value. Returns {bin0:closest bin, frac:fractional dist to next, bin1:next bin}
80406function findInterp(pixel, pixArray) {
80407 var maxBin = pixArray.length - 2;
80408 var bin = Lib.constrain(Lib.findBin(pixel, pixArray), 0, maxBin);
80409 var pix0 = pixArray[bin];
80410 var pix1 = pixArray[bin + 1];
80411 var interp = Lib.constrain(bin + (pixel - pix0) / (pix1 - pix0) - 0.5, 0, maxBin);
80412 var bin0 = Math.round(interp);
80413 var frac = Math.abs(interp - bin0);
80414
80415 if(!interp || interp === maxBin || !frac) {
80416 return {
80417 bin0: bin0,
80418 bin1: bin0,
80419 frac: 0
80420 };
80421 }
80422 return {
80423 bin0: bin0,
80424 frac: frac,
80425 bin1: Math.round(bin0 + frac / (interp - bin0))
80426 };
80427}
80428
80429function findInterpFromCenters(pixel, centerPixArray) {
80430 var maxBin = centerPixArray.length - 1;
80431 var bin = Lib.constrain(Lib.findBin(pixel, centerPixArray), 0, maxBin);
80432 var pix0 = centerPixArray[bin];
80433 var pix1 = centerPixArray[bin + 1];
80434 var frac = ((pixel - pix0) / (pix1 - pix0)) || 0;
80435 if(frac <= 0) {
80436 return {
80437 bin0: bin,
80438 bin1: bin,
80439 frac: 0
80440 };
80441 }
80442 if(frac < 0.5) {
80443 return {
80444 bin0: bin,
80445 bin1: bin + 1,
80446 frac: frac
80447 };
80448 }
80449 return {
80450 bin0: bin + 1,
80451 bin1: bin,
80452 frac: 1 - frac
80453 };
80454}
80455
80456function putColor(pixels, pxIndex, c) {
80457 pixels[pxIndex] = c[0];
80458 pixels[pxIndex + 1] = c[1];
80459 pixels[pxIndex + 2] = c[2];
80460 pixels[pxIndex + 3] = Math.round(c[3] * 255);
80461}
80462
80463},{"../../components/colorscale":64,"../../constants/xmlns_namespaces":159,"../../lib":178,"../../registry":269,"d3":16,"tinycolor2":35}],342:[function(_dereq_,module,exports){
80464/**
80465* Copyright 2012-2020, Plotly, Inc.
80466* All rights reserved.
80467*
80468* This source code is licensed under the MIT license found in the
80469* LICENSE file in the root directory of this source tree.
80470*/
80471
80472
80473'use strict';
80474
80475var d3 = _dereq_('d3');
80476
80477module.exports = function style(gd) {
80478 d3.select(gd).selectAll('.hm image')
80479 .style('opacity', function(d) {
80480 return d.trace.opacity;
80481 });
80482};
80483
80484},{"d3":16}],343:[function(_dereq_,module,exports){
80485/**
80486* Copyright 2012-2020, Plotly, Inc.
80487* All rights reserved.
80488*
80489* This source code is licensed under the MIT license found in the
80490* LICENSE file in the root directory of this source tree.
80491*/
80492
80493
80494'use strict';
80495
80496module.exports = function handleStyleDefaults(traceIn, traceOut, coerce) {
80497 var zsmooth = coerce('zsmooth');
80498 if(zsmooth === false) {
80499 // ensure that xgap and ygap are coerced only when zsmooth allows them to have an effect.
80500 coerce('xgap');
80501 coerce('ygap');
80502 }
80503
80504 coerce('zhoverformat');
80505};
80506
80507},{}],344:[function(_dereq_,module,exports){
80508/**
80509* Copyright 2012-2020, Plotly, Inc.
80510* All rights reserved.
80511*
80512* This source code is licensed under the MIT license found in the
80513* LICENSE file in the root directory of this source tree.
80514*/
80515
80516'use strict';
80517
80518var isNumeric = _dereq_('fast-isnumeric');
80519var Lib = _dereq_('../../lib');
80520
80521var Registry = _dereq_('../../registry');
80522
80523module.exports = function handleXYZDefaults(traceIn, traceOut, coerce, layout, xName, yName) {
80524 var z = coerce('z');
80525 xName = xName || 'x';
80526 yName = yName || 'y';
80527 var x, y;
80528
80529 if(z === undefined || !z.length) return 0;
80530
80531 if(Lib.isArray1D(traceIn.z)) {
80532 x = coerce(xName);
80533 y = coerce(yName);
80534
80535 var xlen = Lib.minRowLength(x);
80536 var ylen = Lib.minRowLength(y);
80537
80538 // column z must be accompanied by xName and yName arrays
80539 if(xlen === 0 || ylen === 0) return 0;
80540
80541 traceOut._length = Math.min(xlen, ylen, z.length);
80542 } else {
80543 x = coordDefaults(xName, coerce);
80544 y = coordDefaults(yName, coerce);
80545
80546 // TODO put z validation elsewhere
80547 if(!isValidZ(z)) return 0;
80548
80549 coerce('transpose');
80550
80551 traceOut._length = null;
80552 }
80553
80554 var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleTraceDefaults');
80555 handleCalendarDefaults(traceIn, traceOut, [xName, yName], layout);
80556
80557 return true;
80558};
80559
80560function coordDefaults(coordStr, coerce) {
80561 var coord = coerce(coordStr);
80562 var coordType = coord ? coerce(coordStr + 'type', 'array') : 'scaled';
80563
80564 if(coordType === 'scaled') {
80565 coerce(coordStr + '0');
80566 coerce('d' + coordStr);
80567 }
80568
80569 return coord;
80570}
80571
80572function isValidZ(z) {
80573 var allRowsAreArrays = true;
80574 var oneRowIsFilled = false;
80575 var hasOneNumber = false;
80576 var zi;
80577
80578 /*
80579 * Without this step:
80580 *
80581 * hasOneNumber = false breaks contour but not heatmap
80582 * allRowsAreArrays = false breaks contour but not heatmap
80583 * oneRowIsFilled = false breaks both
80584 */
80585
80586 for(var i = 0; i < z.length; i++) {
80587 zi = z[i];
80588 if(!Lib.isArrayOrTypedArray(zi)) {
80589 allRowsAreArrays = false;
80590 break;
80591 }
80592 if(zi.length > 0) oneRowIsFilled = true;
80593 for(var j = 0; j < zi.length; j++) {
80594 if(isNumeric(zi[j])) {
80595 hasOneNumber = true;
80596 break;
80597 }
80598 }
80599 }
80600
80601 return (allRowsAreArrays && oneRowIsFilled && hasOneNumber);
80602}
80603
80604},{"../../lib":178,"../../registry":269,"fast-isnumeric":18}],345:[function(_dereq_,module,exports){
80605/**
80606* Copyright 2012-2020, Plotly, Inc.
80607* All rights reserved.
80608*
80609* This source code is licensed under the MIT license found in the
80610* LICENSE file in the root directory of this source tree.
80611*/
80612
80613'use strict';
80614
80615var barAttrs = _dereq_('../bar/attributes');
80616var hovertemplateAttrs = _dereq_('../../plots/template_attributes').hovertemplateAttrs;
80617var makeBinAttrs = _dereq_('./bin_attributes');
80618var constants = _dereq_('./constants');
80619var extendFlat = _dereq_('../../lib/extend').extendFlat;
80620
80621module.exports = {
80622 x: {
80623 valType: 'data_array',
80624 editType: 'calc+clearAxisTypes',
80625
80626 },
80627 y: {
80628 valType: 'data_array',
80629 editType: 'calc+clearAxisTypes',
80630
80631 },
80632
80633 text: extendFlat({}, barAttrs.text, {
80634
80635 }),
80636 hovertext: extendFlat({}, barAttrs.hovertext, {
80637
80638 }),
80639 orientation: barAttrs.orientation,
80640
80641 histfunc: {
80642 valType: 'enumerated',
80643 values: ['count', 'sum', 'avg', 'min', 'max'],
80644
80645 dflt: 'count',
80646 editType: 'calc',
80647
80648 },
80649 histnorm: {
80650 valType: 'enumerated',
80651 values: ['', 'percent', 'probability', 'density', 'probability density'],
80652 dflt: '',
80653
80654 editType: 'calc',
80655
80656 },
80657
80658 cumulative: {
80659 enabled: {
80660 valType: 'boolean',
80661 dflt: false,
80662
80663 editType: 'calc',
80664
80665 },
80666
80667 direction: {
80668 valType: 'enumerated',
80669 values: ['increasing', 'decreasing'],
80670 dflt: 'increasing',
80671
80672 editType: 'calc',
80673
80674 },
80675
80676 currentbin: {
80677 valType: 'enumerated',
80678 values: ['include', 'exclude', 'half'],
80679 dflt: 'include',
80680
80681 editType: 'calc',
80682
80683 },
80684 editType: 'calc'
80685 },
80686 nbinsx: {
80687 valType: 'integer',
80688 min: 0,
80689 dflt: 0,
80690
80691 editType: 'calc',
80692
80693 },
80694 xbins: makeBinAttrs('x', true),
80695
80696 nbinsy: {
80697 valType: 'integer',
80698 min: 0,
80699 dflt: 0,
80700
80701 editType: 'calc',
80702
80703 },
80704 ybins: makeBinAttrs('y', true),
80705 autobinx: {
80706 valType: 'boolean',
80707 dflt: null,
80708
80709 editType: 'calc',
80710
80711 },
80712 autobiny: {
80713 valType: 'boolean',
80714 dflt: null,
80715
80716 editType: 'calc',
80717
80718 },
80719
80720 bingroup: {
80721 valType: 'string',
80722
80723 dflt: '',
80724 editType: 'calc',
80725
80726 },
80727
80728 hovertemplate: hovertemplateAttrs({}, {
80729 keys: constants.eventDataKeys
80730 }),
80731
80732 marker: barAttrs.marker,
80733
80734 offsetgroup: barAttrs.offsetgroup,
80735 alignmentgroup: barAttrs.alignmentgroup,
80736
80737 selected: barAttrs.selected,
80738 unselected: barAttrs.unselected,
80739
80740 _deprecated: {
80741 bardir: barAttrs._deprecated.bardir
80742 }
80743};
80744
80745},{"../../lib/extend":173,"../../plots/template_attributes":264,"../bar/attributes":279,"./bin_attributes":347,"./constants":351}],346:[function(_dereq_,module,exports){
80746/**
80747* Copyright 2012-2020, Plotly, Inc.
80748* All rights reserved.
80749*
80750* This source code is licensed under the MIT license found in the
80751* LICENSE file in the root directory of this source tree.
80752*/
80753
80754
80755'use strict';
80756
80757
80758module.exports = function doAvg(size, counts) {
80759 var nMax = size.length;
80760 var total = 0;
80761 for(var i = 0; i < nMax; i++) {
80762 if(counts[i]) {
80763 size[i] /= counts[i];
80764 total += size[i];
80765 } else size[i] = null;
80766 }
80767 return total;
80768};
80769
80770},{}],347:[function(_dereq_,module,exports){
80771/**
80772* Copyright 2012-2020, Plotly, Inc.
80773* All rights reserved.
80774*
80775* This source code is licensed under the MIT license found in the
80776* LICENSE file in the root directory of this source tree.
80777*/
80778
80779'use strict';
80780
80781module.exports = function makeBinAttrs(axLetter, match) {
80782 return {
80783 start: {
80784 valType: 'any', // for date axes
80785
80786 editType: 'calc',
80787
80788 },
80789 end: {
80790 valType: 'any', // for date axes
80791
80792 editType: 'calc',
80793
80794 },
80795 size: {
80796 valType: 'any', // for date axes
80797
80798 editType: 'calc',
80799
80800 },
80801 editType: 'calc'
80802 };
80803};
80804
80805},{}],348:[function(_dereq_,module,exports){
80806/**
80807* Copyright 2012-2020, Plotly, Inc.
80808* All rights reserved.
80809*
80810* This source code is licensed under the MIT license found in the
80811* LICENSE file in the root directory of this source tree.
80812*/
80813
80814
80815'use strict';
80816
80817var isNumeric = _dereq_('fast-isnumeric');
80818
80819
80820module.exports = {
80821 count: function(n, i, size) {
80822 size[n]++;
80823 return 1;
80824 },
80825
80826 sum: function(n, i, size, counterData) {
80827 var v = counterData[i];
80828 if(isNumeric(v)) {
80829 v = Number(v);
80830 size[n] += v;
80831 return v;
80832 }
80833 return 0;
80834 },
80835
80836 avg: function(n, i, size, counterData, counts) {
80837 var v = counterData[i];
80838 if(isNumeric(v)) {
80839 v = Number(v);
80840 size[n] += v;
80841 counts[n]++;
80842 }
80843 return 0;
80844 },
80845
80846 min: function(n, i, size, counterData) {
80847 var v = counterData[i];
80848 if(isNumeric(v)) {
80849 v = Number(v);
80850 if(!isNumeric(size[n])) {
80851 size[n] = v;
80852 return v;
80853 } else if(size[n] > v) {
80854 var delta = v - size[n];
80855 size[n] = v;
80856 return delta;
80857 }
80858 }
80859 return 0;
80860 },
80861
80862 max: function(n, i, size, counterData) {
80863 var v = counterData[i];
80864 if(isNumeric(v)) {
80865 v = Number(v);
80866 if(!isNumeric(size[n])) {
80867 size[n] = v;
80868 return v;
80869 } else if(size[n] < v) {
80870 var delta = v - size[n];
80871 size[n] = v;
80872 return delta;
80873 }
80874 }
80875 return 0;
80876 }
80877};
80878
80879},{"fast-isnumeric":18}],349:[function(_dereq_,module,exports){
80880/**
80881* Copyright 2012-2020, Plotly, Inc.
80882* All rights reserved.
80883*
80884* This source code is licensed under the MIT license found in the
80885* LICENSE file in the root directory of this source tree.
80886*/
80887
80888
80889'use strict';
80890
80891var numConstants = _dereq_('../../constants/numerical');
80892var oneYear = numConstants.ONEAVGYEAR;
80893var oneMonth = numConstants.ONEAVGMONTH;
80894var oneDay = numConstants.ONEDAY;
80895var oneHour = numConstants.ONEHOUR;
80896var oneMin = numConstants.ONEMIN;
80897var oneSec = numConstants.ONESEC;
80898var tickIncrement = _dereq_('../../plots/cartesian/axes').tickIncrement;
80899
80900
80901/*
80902 * make a function that will find rounded bin edges
80903 * @param {number} leftGap: how far from the left edge of any bin is the closest data value?
80904 * @param {number} rightGap: how far from the right edge of any bin is the closest data value?
80905 * @param {Array[number]} binEdges: the actual edge values used in binning
80906 * @param {object} pa: the position axis
80907 * @param {string} calendar: the data calendar
80908 *
80909 * @return {function(v, isRightEdge)}:
80910 * find the start (isRightEdge is falsy) or end (truthy) label value for a bin edge `v`
80911 */
80912module.exports = function getBinSpanLabelRound(leftGap, rightGap, binEdges, pa, calendar) {
80913 // the rounding digit is the largest digit that changes in *all* of 4 regions:
80914 // - inside the rightGap before binEdges[0] (shifted 10% to the left)
80915 // - inside the leftGap after binEdges[0] (expanded by 10% of rightGap on each end)
80916 // - same for binEdges[1]
80917 var dv0 = -1.1 * rightGap;
80918 var dv1 = -0.1 * rightGap;
80919 var dv2 = leftGap - dv1;
80920 var edge0 = binEdges[0];
80921 var edge1 = binEdges[1];
80922 var leftDigit = Math.min(
80923 biggestDigitChanged(edge0 + dv1, edge0 + dv2, pa, calendar),
80924 biggestDigitChanged(edge1 + dv1, edge1 + dv2, pa, calendar)
80925 );
80926 var rightDigit = Math.min(
80927 biggestDigitChanged(edge0 + dv0, edge0 + dv1, pa, calendar),
80928 biggestDigitChanged(edge1 + dv0, edge1 + dv1, pa, calendar)
80929 );
80930
80931 // normally we try to make the label for the right edge different from
80932 // the left edge label, so it's unambiguous which bin gets data on the edge.
80933 // but if this results in more than 3 extra digits (or for dates, more than
80934 // 2 fields ie hr&min or min&sec, which is 3600x), it'll be more clutter than
80935 // useful so keep the label cleaner instead
80936 var digit, disambiguateEdges;
80937 if(leftDigit > rightDigit && rightDigit < Math.abs(edge1 - edge0) / 4000) {
80938 digit = leftDigit;
80939 disambiguateEdges = false;
80940 } else {
80941 digit = Math.min(leftDigit, rightDigit);
80942 disambiguateEdges = true;
80943 }
80944
80945 if(pa.type === 'date' && digit > oneDay) {
80946 var dashExclude = (digit === oneYear) ? 1 : 6;
80947 var increment = (digit === oneYear) ? 'M12' : 'M1';
80948
80949 return function(v, isRightEdge) {
80950 var dateStr = pa.c2d(v, oneYear, calendar);
80951 var dashPos = dateStr.indexOf('-', dashExclude);
80952 if(dashPos > 0) dateStr = dateStr.substr(0, dashPos);
80953 var roundedV = pa.d2c(dateStr, 0, calendar);
80954
80955 if(roundedV < v) {
80956 var nextV = tickIncrement(roundedV, increment, false, calendar);
80957 if((roundedV + nextV) / 2 < v + leftGap) roundedV = nextV;
80958 }
80959
80960 if(isRightEdge && disambiguateEdges) {
80961 return tickIncrement(roundedV, increment, true, calendar);
80962 }
80963
80964 return roundedV;
80965 };
80966 }
80967
80968 return function(v, isRightEdge) {
80969 var roundedV = digit * Math.round(v / digit);
80970 // if we rounded down and we could round up and still be < leftGap
80971 // (or what leftGap values round to), do that
80972 if(roundedV + (digit / 10) < v && roundedV + (digit * 0.9) < v + leftGap) {
80973 roundedV += digit;
80974 }
80975 // finally for the right edge back off one digit - but only if we can do that
80976 // and not clip off any data that's potentially in the bin
80977 if(isRightEdge && disambiguateEdges) {
80978 roundedV -= digit;
80979 }
80980 return roundedV;
80981 };
80982};
80983
80984/*
80985 * Find the largest digit that changes within a (calcdata) region [v1, v2]
80986 * if dates, "digit" means date/time part when it's bigger than a second
80987 * returns the unit value to round to this digit, eg 0.01 to round to hundredths, or
80988 * 100 to round to hundreds. returns oneMonth or oneYear for month or year rounding,
80989 * so that Math.min will work, rather than 'M1' and 'M12'
80990 */
80991function biggestDigitChanged(v1, v2, pa, calendar) {
80992 // are we crossing zero? can't say anything.
80993 // in principle this doesn't apply to dates but turns out this doesn't matter.
80994 if(v1 * v2 <= 0) return Infinity;
80995
80996 var dv = Math.abs(v2 - v1);
80997 var isDate = pa.type === 'date';
80998 var digit = biggestGuaranteedDigitChanged(dv, isDate);
80999 // see if a larger digit also changed
81000 for(var i = 0; i < 10; i++) {
81001 // numbers: next digit needs to be >10x but <100x then gets rounded down.
81002 // dates: next digit can be as much as 60x (then rounded down)
81003 var nextDigit = biggestGuaranteedDigitChanged(digit * 80, isDate);
81004 // if we get to years, the chain stops
81005 if(digit === nextDigit) break;
81006 if(didDigitChange(nextDigit, v1, v2, isDate, pa, calendar)) digit = nextDigit;
81007 else break;
81008 }
81009 return digit;
81010}
81011
81012/*
81013 * Find the largest digit that *definitely* changes in a region [v, v + dv] for any v
81014 * for nonuniform date regions (months/years) pick the largest
81015 */
81016function biggestGuaranteedDigitChanged(dv, isDate) {
81017 if(isDate && dv > oneSec) {
81018 // this is supposed to be the biggest *guaranteed* change
81019 // so compare to the longest month and year across any calendar,
81020 // and we'll iterate back up later
81021 // note: does not support rounding larger than one year. We could add
81022 // that if anyone wants it, but seems unusual and not strictly necessary.
81023 if(dv > oneDay) {
81024 if(dv > oneYear * 1.1) return oneYear;
81025 if(dv > oneMonth * 1.1) return oneMonth;
81026 return oneDay;
81027 }
81028
81029 if(dv > oneHour) return oneHour;
81030 if(dv > oneMin) return oneMin;
81031 return oneSec;
81032 }
81033 return Math.pow(10, Math.floor(Math.log(dv) / Math.LN10));
81034}
81035
81036function didDigitChange(digit, v1, v2, isDate, pa, calendar) {
81037 if(isDate && digit > oneDay) {
81038 var dateParts1 = dateParts(v1, pa, calendar);
81039 var dateParts2 = dateParts(v2, pa, calendar);
81040 var parti = (digit === oneYear) ? 0 : 1;
81041 return dateParts1[parti] !== dateParts2[parti];
81042 }
81043 return Math.floor(v2 / digit) - Math.floor(v1 / digit) > 0.1;
81044}
81045
81046function dateParts(v, pa, calendar) {
81047 var parts = pa.c2d(v, oneYear, calendar).split('-');
81048 if(parts[0] === '') {
81049 parts.unshift();
81050 parts[0] = '-' + parts[0];
81051 }
81052 return parts;
81053}
81054
81055},{"../../constants/numerical":158,"../../plots/cartesian/axes":222}],350:[function(_dereq_,module,exports){
81056/**
81057* Copyright 2012-2020, Plotly, Inc.
81058* All rights reserved.
81059*
81060* This source code is licensed under the MIT license found in the
81061* LICENSE file in the root directory of this source tree.
81062*/
81063
81064'use strict';
81065
81066var isNumeric = _dereq_('fast-isnumeric');
81067
81068var Lib = _dereq_('../../lib');
81069var Registry = _dereq_('../../registry');
81070var Axes = _dereq_('../../plots/cartesian/axes');
81071
81072var arraysToCalcdata = _dereq_('../bar/arrays_to_calcdata');
81073var binFunctions = _dereq_('./bin_functions');
81074var normFunctions = _dereq_('./norm_functions');
81075var doAvg = _dereq_('./average');
81076var getBinSpanLabelRound = _dereq_('./bin_label_vals');
81077
81078function calc(gd, trace) {
81079 var pos = [];
81080 var size = [];
81081 var pa = Axes.getFromId(gd, trace.orientation === 'h' ? trace.yaxis : trace.xaxis);
81082 var mainData = trace.orientation === 'h' ? 'y' : 'x';
81083 var counterData = {x: 'y', y: 'x'}[mainData];
81084 var calendar = trace[mainData + 'calendar'];
81085 var cumulativeSpec = trace.cumulative;
81086 var i;
81087
81088 var binsAndPos = calcAllAutoBins(gd, trace, pa, mainData);
81089 var binSpec = binsAndPos[0];
81090 var pos0 = binsAndPos[1];
81091
81092 var nonuniformBins = typeof binSpec.size === 'string';
81093 var binEdges = [];
81094 var bins = nonuniformBins ? binEdges : binSpec;
81095 // make the empty bin array
81096 var inc = [];
81097 var counts = [];
81098 var inputPoints = [];
81099 var total = 0;
81100 var norm = trace.histnorm;
81101 var func = trace.histfunc;
81102 var densityNorm = norm.indexOf('density') !== -1;
81103 var i2, binEnd, n;
81104
81105 if(cumulativeSpec.enabled && densityNorm) {
81106 // we treat "cumulative" like it means "integral" if you use a density norm,
81107 // which in the end means it's the same as without "density"
81108 norm = norm.replace(/ ?density$/, '');
81109 densityNorm = false;
81110 }
81111
81112 var extremeFunc = func === 'max' || func === 'min';
81113 var sizeInit = extremeFunc ? null : 0;
81114 var binFunc = binFunctions.count;
81115 var normFunc = normFunctions[norm];
81116 var isAvg = false;
81117 var pr2c = function(v) { return pa.r2c(v, 0, calendar); };
81118 var rawCounterData;
81119
81120 if(Lib.isArrayOrTypedArray(trace[counterData]) && func !== 'count') {
81121 rawCounterData = trace[counterData];
81122 isAvg = func === 'avg';
81123 binFunc = binFunctions[func];
81124 }
81125
81126 // create the bins (and any extra arrays needed)
81127 // assume more than 1e6 bins is an error, so we don't crash the browser
81128 i = pr2c(binSpec.start);
81129
81130 // decrease end a little in case of rounding errors
81131 binEnd = pr2c(binSpec.end) + (i - Axes.tickIncrement(i, binSpec.size, false, calendar)) / 1e6;
81132
81133 while(i < binEnd && pos.length < 1e6) {
81134 i2 = Axes.tickIncrement(i, binSpec.size, false, calendar);
81135 pos.push((i + i2) / 2);
81136 size.push(sizeInit);
81137 inputPoints.push([]);
81138 // nonuniform bins (like months) we need to search,
81139 // rather than straight calculate the bin we're in
81140 binEdges.push(i);
81141 // nonuniform bins also need nonuniform normalization factors
81142 if(densityNorm) inc.push(1 / (i2 - i));
81143 if(isAvg) counts.push(0);
81144 // break to avoid infinite loops
81145 if(i2 <= i) break;
81146 i = i2;
81147 }
81148 binEdges.push(i);
81149
81150 // for date axes we need bin bounds to be calcdata. For nonuniform bins
81151 // we already have this, but uniform with start/end/size they're still strings.
81152 if(!nonuniformBins && pa.type === 'date') {
81153 bins = {
81154 start: pr2c(bins.start),
81155 end: pr2c(bins.end),
81156 size: bins.size
81157 };
81158 }
81159
81160 // stash left and right gaps by group
81161 if(!gd._fullLayout._roundFnOpts) gd._fullLayout._roundFnOpts = {};
81162 var groupName = trace['_' + mainData + 'bingroup'];
81163 var roundFnOpts = {leftGap: Infinity, rightGap: Infinity};
81164 if(groupName) {
81165 if(!gd._fullLayout._roundFnOpts[groupName]) gd._fullLayout._roundFnOpts[groupName] = roundFnOpts;
81166 roundFnOpts = gd._fullLayout._roundFnOpts[groupName];
81167 }
81168
81169 // bin the data
81170 // and make histogram-specific pt-number-to-cd-index map object
81171 var nMax = size.length;
81172 var uniqueValsPerBin = true;
81173 var leftGap = roundFnOpts.leftGap;
81174 var rightGap = roundFnOpts.rightGap;
81175 var ptNumber2cdIndex = {};
81176 for(i = 0; i < pos0.length; i++) {
81177 var posi = pos0[i];
81178 n = Lib.findBin(posi, bins);
81179 if(n >= 0 && n < nMax) {
81180 total += binFunc(n, i, size, rawCounterData, counts);
81181 if(uniqueValsPerBin && inputPoints[n].length && posi !== pos0[inputPoints[n][0]]) {
81182 uniqueValsPerBin = false;
81183 }
81184 inputPoints[n].push(i);
81185 ptNumber2cdIndex[i] = n;
81186
81187 leftGap = Math.min(leftGap, posi - binEdges[n]);
81188 rightGap = Math.min(rightGap, binEdges[n + 1] - posi);
81189 }
81190 }
81191 roundFnOpts.leftGap = leftGap;
81192 roundFnOpts.rightGap = rightGap;
81193
81194 var roundFn;
81195 if(!uniqueValsPerBin) {
81196 roundFn = function(v, isRightEdge) {
81197 return function() {
81198 var roundFnOpts = gd._fullLayout._roundFnOpts[groupName];
81199 return getBinSpanLabelRound(
81200 roundFnOpts.leftGap,
81201 roundFnOpts.rightGap,
81202 binEdges, pa, calendar
81203 )(v, isRightEdge);
81204 };
81205 };
81206 }
81207
81208 // average and/or normalize the data, if needed
81209 if(isAvg) total = doAvg(size, counts);
81210 if(normFunc) normFunc(size, total, inc);
81211
81212 // after all normalization etc, now we can accumulate if desired
81213 if(cumulativeSpec.enabled) cdf(size, cumulativeSpec.direction, cumulativeSpec.currentbin);
81214
81215 var seriesLen = Math.min(pos.length, size.length);
81216 var cd = [];
81217 var firstNonzero = 0;
81218 var lastNonzero = seriesLen - 1;
81219
81220 // look for empty bins at the ends to remove, so autoscale omits them
81221 for(i = 0; i < seriesLen; i++) {
81222 if(size[i]) {
81223 firstNonzero = i;
81224 break;
81225 }
81226 }
81227 for(i = seriesLen - 1; i >= firstNonzero; i--) {
81228 if(size[i]) {
81229 lastNonzero = i;
81230 break;
81231 }
81232 }
81233
81234 // create the "calculated data" to plot
81235 for(i = firstNonzero; i <= lastNonzero; i++) {
81236 if((isNumeric(pos[i]) && isNumeric(size[i]))) {
81237 var cdi = {
81238 p: pos[i],
81239 s: size[i],
81240 b: 0
81241 };
81242
81243 // setup hover and event data fields,
81244 // N.B. pts and "hover" positions ph0/ph1 don't seem to make much sense
81245 // for cumulative distributions
81246 if(!cumulativeSpec.enabled) {
81247 cdi.pts = inputPoints[i];
81248 if(uniqueValsPerBin) {
81249 cdi.ph0 = cdi.ph1 = (inputPoints[i].length) ? pos0[inputPoints[i][0]] : pos[i];
81250 } else {
81251 // Defer evaluation of ph(0|1) in crossTraceCalc
81252 trace._computePh = true;
81253 cdi.ph0 = roundFn(binEdges[i]);
81254 cdi.ph1 = roundFn(binEdges[i + 1], true);
81255 }
81256 }
81257 cd.push(cdi);
81258 }
81259 }
81260
81261 if(cd.length === 1) {
81262 // when we collapse to a single bin, calcdata no longer describes bin size
81263 // so we need to explicitly specify it
81264 cd[0].width1 = Axes.tickIncrement(cd[0].p, binSpec.size, false, calendar) - cd[0].p;
81265 }
81266
81267 arraysToCalcdata(cd, trace);
81268
81269 if(Lib.isArrayOrTypedArray(trace.selectedpoints)) {
81270 Lib.tagSelected(cd, trace, ptNumber2cdIndex);
81271 }
81272
81273 return cd;
81274}
81275
81276/*
81277 * calcAllAutoBins: we want all histograms inside the same bingroup
81278 * (see logic in Histogram.crossTraceDefaults) to share bin specs
81279 *
81280 * If the user has explicitly specified differing
81281 * bin specs, there's nothing we can do, but if possible we will try to use the
81282 * smallest bins of any of the auto values for all histograms inside the same
81283 * bingroup.
81284 */
81285function calcAllAutoBins(gd, trace, pa, mainData, _overlayEdgeCase) {
81286 var binAttr = mainData + 'bins';
81287 var fullLayout = gd._fullLayout;
81288 var groupName = trace['_' + mainData + 'bingroup'];
81289 var binOpts = fullLayout._histogramBinOpts[groupName];
81290 var isOverlay = fullLayout.barmode === 'overlay';
81291 var i, traces, tracei, calendar, pos0, autoVals, cumulativeSpec;
81292
81293 var r2c = function(v) { return pa.r2c(v, 0, calendar); };
81294 var c2r = function(v) { return pa.c2r(v, 0, calendar); };
81295
81296 var cleanBound = pa.type === 'date' ?
81297 function(v) { return (v || v === 0) ? Lib.cleanDate(v, null, calendar) : null; } :
81298 function(v) { return isNumeric(v) ? Number(v) : null; };
81299
81300 function setBound(attr, bins, newBins) {
81301 if(bins[attr + 'Found']) {
81302 bins[attr] = cleanBound(bins[attr]);
81303 if(bins[attr] === null) bins[attr] = newBins[attr];
81304 } else {
81305 autoVals[attr] = bins[attr] = newBins[attr];
81306 Lib.nestedProperty(traces[0], binAttr + '.' + attr).set(newBins[attr]);
81307 }
81308 }
81309
81310 // all but the first trace in this group has already been marked finished
81311 // clear this flag, so next time we run calc we will run autobin again
81312 if(trace['_' + mainData + 'autoBinFinished']) {
81313 delete trace['_' + mainData + 'autoBinFinished'];
81314 } else {
81315 traces = binOpts.traces;
81316 var allPos = [];
81317
81318 // Note: we're including `legendonly` traces here for autobin purposes,
81319 // so that showing & hiding from the legend won't affect bins.
81320 // But this complicates things a bit since those traces don't `calc`,
81321 // hence `isFirstVisible`.
81322 var isFirstVisible = true;
81323 var has2dMap = false;
81324 var hasHist2dContour = false;
81325 for(i = 0; i < traces.length; i++) {
81326 tracei = traces[i];
81327
81328 if(tracei.visible) {
81329 var mainDatai = binOpts.dirs[i];
81330 pos0 = tracei['_' + mainDatai + 'pos0'] = pa.makeCalcdata(tracei, mainDatai);
81331
81332 allPos = Lib.concat(allPos, pos0);
81333 delete tracei['_' + mainData + 'autoBinFinished'];
81334
81335 if(trace.visible === true) {
81336 if(isFirstVisible) {
81337 isFirstVisible = false;
81338 } else {
81339 delete tracei._autoBin;
81340 tracei['_' + mainData + 'autoBinFinished'] = 1;
81341 }
81342 if(Registry.traceIs(tracei, '2dMap')) {
81343 has2dMap = true;
81344 }
81345 if(tracei.type === 'histogram2dcontour') {
81346 hasHist2dContour = true;
81347 }
81348 }
81349 }
81350 }
81351
81352 calendar = traces[0][mainData + 'calendar'];
81353 var newBinSpec = Axes.autoBin(allPos, pa, binOpts.nbins, has2dMap, calendar, binOpts.sizeFound && binOpts.size);
81354
81355 var autoBin = traces[0]._autoBin = {};
81356 autoVals = autoBin[binOpts.dirs[0]] = {};
81357
81358 if(hasHist2dContour) {
81359 // the "true" 2nd argument reverses the tick direction (which we can't
81360 // just do with a minus sign because of month bins)
81361 if(!binOpts.size) {
81362 newBinSpec.start = c2r(Axes.tickIncrement(
81363 r2c(newBinSpec.start), newBinSpec.size, true, calendar));
81364 }
81365 if(binOpts.end === undefined) {
81366 newBinSpec.end = c2r(Axes.tickIncrement(
81367 r2c(newBinSpec.end), newBinSpec.size, false, calendar));
81368 }
81369 }
81370
81371 // Edge case: single-valued histogram overlaying others
81372 // Use them all together to calculate the bin size for the single-valued one
81373 if(isOverlay && !Registry.traceIs(trace, '2dMap') && newBinSpec._dataSpan === 0 &&
81374 pa.type !== 'category' && pa.type !== 'multicategory') {
81375 // Several single-valued histograms! Stop infinite recursion,
81376 // just return an extra flag that tells handleSingleValueOverlays
81377 // to sort out this trace too
81378 if(_overlayEdgeCase) return [newBinSpec, pos0, true];
81379
81380 newBinSpec = handleSingleValueOverlays(gd, trace, pa, mainData, binAttr);
81381 }
81382
81383 // adjust for CDF edge cases
81384 cumulativeSpec = tracei.cumulative || {};
81385 if(cumulativeSpec.enabled && (cumulativeSpec.currentbin !== 'include')) {
81386 if(cumulativeSpec.direction === 'decreasing') {
81387 newBinSpec.start = c2r(Axes.tickIncrement(
81388 r2c(newBinSpec.start), newBinSpec.size, true, calendar));
81389 } else {
81390 newBinSpec.end = c2r(Axes.tickIncrement(
81391 r2c(newBinSpec.end), newBinSpec.size, false, calendar));
81392 }
81393 }
81394
81395 binOpts.size = newBinSpec.size;
81396 if(!binOpts.sizeFound) {
81397 autoVals.size = newBinSpec.size;
81398 Lib.nestedProperty(traces[0], binAttr + '.size').set(newBinSpec.size);
81399 }
81400
81401 setBound('start', binOpts, newBinSpec);
81402 setBound('end', binOpts, newBinSpec);
81403 }
81404
81405 pos0 = trace['_' + mainData + 'pos0'];
81406 delete trace['_' + mainData + 'pos0'];
81407
81408 // Each trace can specify its own start/end, or if omitted
81409 // we ensure they're beyond the bounds of this trace's data,
81410 // and we need to make sure start is aligned with the main start
81411 var traceInputBins = trace._input[binAttr] || {};
81412 var traceBinOptsCalc = Lib.extendFlat({}, binOpts);
81413 var mainStart = binOpts.start;
81414 var startIn = pa.r2l(traceInputBins.start);
81415 var hasStart = startIn !== undefined;
81416 if((binOpts.startFound || hasStart) && startIn !== pa.r2l(mainStart)) {
81417 // We have an explicit start to reconcile across traces
81418 // if this trace has an explicit start, shift it down to a bin edge
81419 // if another trace had an explicit start, shift it down to a
81420 // bin edge past our data
81421 var traceStart = hasStart ?
81422 startIn :
81423 Lib.aggNums(Math.min, null, pos0);
81424
81425 var dummyAx = {
81426 type: (pa.type === 'category' || pa.type === 'multicategory') ? 'linear' : pa.type,
81427 r2l: pa.r2l,
81428 dtick: binOpts.size,
81429 tick0: mainStart,
81430 calendar: calendar,
81431 range: ([traceStart, Axes.tickIncrement(traceStart, binOpts.size, false, calendar)]).map(pa.l2r)
81432 };
81433 var newStart = Axes.tickFirst(dummyAx);
81434 if(newStart > pa.r2l(traceStart)) {
81435 newStart = Axes.tickIncrement(newStart, binOpts.size, true, calendar);
81436 }
81437 traceBinOptsCalc.start = pa.l2r(newStart);
81438 if(!hasStart) Lib.nestedProperty(trace, binAttr + '.start').set(traceBinOptsCalc.start);
81439 }
81440
81441 var mainEnd = binOpts.end;
81442 var endIn = pa.r2l(traceInputBins.end);
81443 var hasEnd = endIn !== undefined;
81444 if((binOpts.endFound || hasEnd) && endIn !== pa.r2l(mainEnd)) {
81445 // Reconciling an explicit end is easier, as it doesn't need to
81446 // match bin edges
81447 var traceEnd = hasEnd ?
81448 endIn :
81449 Lib.aggNums(Math.max, null, pos0);
81450
81451 traceBinOptsCalc.end = pa.l2r(traceEnd);
81452 if(!hasEnd) Lib.nestedProperty(trace, binAttr + '.start').set(traceBinOptsCalc.end);
81453 }
81454
81455 // Backward compatibility for one-time autobinning.
81456 // autobin: true is handled in cleanData, but autobin: false
81457 // needs to be here where we have determined the values.
81458 var autoBinAttr = 'autobin' + mainData;
81459 if(trace._input[autoBinAttr] === false) {
81460 trace._input[binAttr] = Lib.extendFlat({}, trace[binAttr] || {});
81461 delete trace._input[autoBinAttr];
81462 delete trace[autoBinAttr];
81463 }
81464
81465 return [traceBinOptsCalc, pos0];
81466}
81467
81468/*
81469 * Adjust single-value histograms in overlay mode to make as good a
81470 * guess as we can at autobin values the user would like.
81471 *
81472 * Returns the binSpec for the trace that sparked all this
81473 */
81474function handleSingleValueOverlays(gd, trace, pa, mainData, binAttr) {
81475 var fullLayout = gd._fullLayout;
81476 var overlaidTraceGroup = getConnectedHistograms(gd, trace);
81477 var pastThisTrace = false;
81478 var minSize = Infinity;
81479 var singleValuedTraces = [trace];
81480 var i, tracei, binOpts;
81481
81482 // first collect all the:
81483 // - min bin size from all multi-valued traces
81484 // - single-valued traces
81485 for(i = 0; i < overlaidTraceGroup.length; i++) {
81486 tracei = overlaidTraceGroup[i];
81487
81488 if(tracei === trace) {
81489 pastThisTrace = true;
81490 } else if(!pastThisTrace) {
81491 // This trace has already had its autobins calculated, so either:
81492 // - it is part of a bingroup
81493 // - it is NOT a single-valued trace
81494 binOpts = fullLayout._histogramBinOpts[tracei['_' + mainData + 'bingroup']];
81495 minSize = Math.min(minSize, binOpts.size || tracei[binAttr].size);
81496 } else {
81497 var resulti = calcAllAutoBins(gd, tracei, pa, mainData, true);
81498 var binSpeci = resulti[0];
81499 var isSingleValued = resulti[2];
81500
81501 // so we can use this result when we get to tracei in the normal
81502 // course of events, mark it as done and put _pos0 back
81503 tracei['_' + mainData + 'autoBinFinished'] = 1;
81504 tracei['_' + mainData + 'pos0'] = resulti[1];
81505
81506 if(isSingleValued) {
81507 singleValuedTraces.push(tracei);
81508 } else {
81509 minSize = Math.min(minSize, binSpeci.size);
81510 }
81511 }
81512 }
81513
81514 // find the real data values for each single-valued trace
81515 // hunt through pos0 for the first valid value
81516 var dataVals = new Array(singleValuedTraces.length);
81517 for(i = 0; i < singleValuedTraces.length; i++) {
81518 var pos0 = singleValuedTraces[i]['_' + mainData + 'pos0'];
81519 for(var j = 0; j < pos0.length; j++) {
81520 if(pos0[j] !== undefined) {
81521 dataVals[i] = pos0[j];
81522 break;
81523 }
81524 }
81525 }
81526
81527 // are ALL traces are single-valued? use the min difference between
81528 // all of their values (which defaults to 1 if there's still only one)
81529 if(!isFinite(minSize)) {
81530 minSize = Lib.distinctVals(dataVals).minDiff;
81531 }
81532
81533 // now apply the min size we found to all single-valued traces
81534 for(i = 0; i < singleValuedTraces.length; i++) {
81535 tracei = singleValuedTraces[i];
81536 var calendar = tracei[mainData + 'calendar'];
81537
81538 var newBins = {
81539 start: pa.c2r(dataVals[i] - minSize / 2, 0, calendar),
81540 end: pa.c2r(dataVals[i] + minSize / 2, 0, calendar),
81541 size: minSize
81542 };
81543
81544 tracei._input[binAttr] = tracei[binAttr] = newBins;
81545
81546 binOpts = fullLayout._histogramBinOpts[tracei['_' + mainData + 'bingroup']];
81547 if(binOpts) Lib.extendFlat(binOpts, newBins);
81548 }
81549
81550 return trace[binAttr];
81551}
81552
81553/*
81554 * Return an array of histograms that share axes and orientation.
81555 *
81556 * Only considers histograms. In principle we could include bars in a
81557 * similar way to how we do manually binned histograms, though this
81558 * would have tons of edge cases and value judgments to make.
81559 */
81560function getConnectedHistograms(gd, trace) {
81561 var xid = trace.xaxis;
81562 var yid = trace.yaxis;
81563 var orientation = trace.orientation;
81564
81565 var out = [];
81566 var fullData = gd._fullData;
81567 for(var i = 0; i < fullData.length; i++) {
81568 var tracei = fullData[i];
81569 if(tracei.type === 'histogram' &&
81570 tracei.visible === true &&
81571 tracei.orientation === orientation &&
81572 tracei.xaxis === xid && tracei.yaxis === yid
81573 ) {
81574 out.push(tracei);
81575 }
81576 }
81577
81578 return out;
81579}
81580
81581function cdf(size, direction, currentBin) {
81582 var i, vi, prevSum;
81583
81584 function firstHalfPoint(i) {
81585 prevSum = size[i];
81586 size[i] /= 2;
81587 }
81588
81589 function nextHalfPoint(i) {
81590 vi = size[i];
81591 size[i] = prevSum + vi / 2;
81592 prevSum += vi;
81593 }
81594
81595 if(currentBin === 'half') {
81596 if(direction === 'increasing') {
81597 firstHalfPoint(0);
81598 for(i = 1; i < size.length; i++) {
81599 nextHalfPoint(i);
81600 }
81601 } else {
81602 firstHalfPoint(size.length - 1);
81603 for(i = size.length - 2; i >= 0; i--) {
81604 nextHalfPoint(i);
81605 }
81606 }
81607 } else if(direction === 'increasing') {
81608 for(i = 1; i < size.length; i++) {
81609 size[i] += size[i - 1];
81610 }
81611
81612 // 'exclude' is identical to 'include' just shifted one bin over
81613 if(currentBin === 'exclude') {
81614 size.unshift(0);
81615 size.pop();
81616 }
81617 } else {
81618 for(i = size.length - 2; i >= 0; i--) {
81619 size[i] += size[i + 1];
81620 }
81621
81622 if(currentBin === 'exclude') {
81623 size.push(0);
81624 size.shift();
81625 }
81626 }
81627}
81628
81629module.exports = {
81630 calc: calc,
81631 calcAllAutoBins: calcAllAutoBins
81632};
81633
81634},{"../../lib":178,"../../plots/cartesian/axes":222,"../../registry":269,"../bar/arrays_to_calcdata":278,"./average":346,"./bin_functions":348,"./bin_label_vals":349,"./norm_functions":357,"fast-isnumeric":18}],351:[function(_dereq_,module,exports){
81635/**
81636* Copyright 2012-2020, Plotly, Inc.
81637* All rights reserved.
81638*
81639* This source code is licensed under the MIT license found in the
81640* LICENSE file in the root directory of this source tree.
81641*/
81642
81643
81644'use strict';
81645
81646module.exports = {
81647 eventDataKeys: ['binNumber']
81648};
81649
81650},{}],352:[function(_dereq_,module,exports){
81651/**
81652* Copyright 2012-2020, Plotly, Inc.
81653* All rights reserved.
81654*
81655* This source code is licensed under the MIT license found in the
81656* LICENSE file in the root directory of this source tree.
81657*/
81658
81659'use strict';
81660
81661var Lib = _dereq_('../../lib');
81662var axisIds = _dereq_('../../plots/cartesian/axis_ids');
81663
81664var traceIs = _dereq_('../../registry').traceIs;
81665var handleGroupingDefaults = _dereq_('../bar/defaults').handleGroupingDefaults;
81666
81667var nestedProperty = Lib.nestedProperty;
81668var getAxisGroup = axisIds.getAxisGroup;
81669
81670var BINATTRS = [
81671 {aStr: {x: 'xbins.start', y: 'ybins.start'}, name: 'start'},
81672 {aStr: {x: 'xbins.end', y: 'ybins.end'}, name: 'end'},
81673 {aStr: {x: 'xbins.size', y: 'ybins.size'}, name: 'size'},
81674 {aStr: {x: 'nbinsx', y: 'nbinsy'}, name: 'nbins'}
81675];
81676
81677var BINDIRECTIONS = ['x', 'y'];
81678
81679// handle bin attrs and relink auto-determined values so fullData is complete
81680module.exports = function crossTraceDefaults(fullData, fullLayout) {
81681 var allBinOpts = fullLayout._histogramBinOpts = {};
81682 var histTraces = [];
81683 var mustMatchTracesLookup = {};
81684 var otherTracesList = [];
81685
81686 var traceOut, traces, groupName, binDir;
81687 var i, j, k;
81688
81689 function coerce(attr, dflt) {
81690 return Lib.coerce(traceOut._input, traceOut, traceOut._module.attributes, attr, dflt);
81691 }
81692
81693 function orientation2binDir(traceOut) {
81694 return traceOut.orientation === 'v' ? 'x' : 'y';
81695 }
81696
81697 function getAxisType(traceOut, binDir) {
81698 var ax = axisIds.getFromTrace({_fullLayout: fullLayout}, traceOut, binDir);
81699 return ax.type;
81700 }
81701
81702 function fillBinOpts(traceOut, groupName, binDir) {
81703 // N.B. group traces that don't have a bingroup with themselves
81704 var fallbackGroupName = traceOut.uid + '__' + binDir;
81705 if(!groupName) groupName = fallbackGroupName;
81706
81707 var axType = getAxisType(traceOut, binDir);
81708 var calendar = traceOut[binDir + 'calendar'] || '';
81709 var binOpts = allBinOpts[groupName];
81710 var needsNewItem = true;
81711
81712 if(binOpts) {
81713 if(axType === binOpts.axType && calendar === binOpts.calendar) {
81714 needsNewItem = false;
81715 binOpts.traces.push(traceOut);
81716 binOpts.dirs.push(binDir);
81717 } else {
81718 groupName = fallbackGroupName;
81719
81720 if(axType !== binOpts.axType) {
81721 Lib.warn([
81722 'Attempted to group the bins of trace', traceOut.index,
81723 'set on a', 'type:' + axType, 'axis',
81724 'with bins on', 'type:' + binOpts.axType, 'axis.'
81725 ].join(' '));
81726 }
81727 if(calendar !== binOpts.calendar) {
81728 // prohibit bingroup for traces using different calendar,
81729 // there's probably a way to make this work, but skip for now
81730 Lib.warn([
81731 'Attempted to group the bins of trace', traceOut.index,
81732 'set with a', calendar, 'calendar',
81733 'with bins',
81734 (binOpts.calendar ? 'on a ' + binOpts.calendar + ' calendar' : 'w/o a set calendar')
81735 ].join(' '));
81736 }
81737 }
81738 }
81739
81740 if(needsNewItem) {
81741 allBinOpts[groupName] = {
81742 traces: [traceOut],
81743 dirs: [binDir],
81744 axType: axType,
81745 calendar: traceOut[binDir + 'calendar'] || ''
81746 };
81747 }
81748 traceOut['_' + binDir + 'bingroup'] = groupName;
81749 }
81750
81751 for(i = 0; i < fullData.length; i++) {
81752 traceOut = fullData[i];
81753
81754 if(traceIs(traceOut, 'histogram')) {
81755 histTraces.push(traceOut);
81756
81757 // TODO: this shouldn't be relinked as it's only used within calc
81758 // https://github.com/plotly/plotly.js/issues/749
81759 delete traceOut._xautoBinFinished;
81760 delete traceOut._yautoBinFinished;
81761
81762 // N.B. need to coerce *alignmentgroup* before *bingroup*, as traces
81763 // in same alignmentgroup "have to match"
81764 if(!traceIs(traceOut, '2dMap')) {
81765 handleGroupingDefaults(traceOut._input, traceOut, fullLayout, coerce);
81766 }
81767 }
81768 }
81769
81770 var alignmentOpts = fullLayout._alignmentOpts || {};
81771
81772 // Look for traces that "have to match", that is:
81773 // - 1d histogram traces on the same subplot with same orientation under barmode:stack,
81774 // - 1d histogram traces on the same subplot with same orientation under barmode:group
81775 // - 1d histogram traces on the same position axis with the same orientation
81776 // and the same *alignmentgroup* (coerced under barmode:group)
81777 // - Once `stackgroup` gets implemented (see https://github.com/plotly/plotly.js/issues/3614),
81778 // traces within the same stackgroup will also "have to match"
81779 for(i = 0; i < histTraces.length; i++) {
81780 traceOut = histTraces[i];
81781 groupName = '';
81782
81783 if(!traceIs(traceOut, '2dMap')) {
81784 binDir = orientation2binDir(traceOut);
81785
81786 if(fullLayout.barmode === 'group' && traceOut.alignmentgroup) {
81787 var pa = traceOut[binDir + 'axis'];
81788 var aGroupId = getAxisGroup(fullLayout, pa) + traceOut.orientation;
81789 if((alignmentOpts[aGroupId] || {})[traceOut.alignmentgroup]) {
81790 groupName = aGroupId;
81791 }
81792 }
81793
81794 if(!groupName && fullLayout.barmode !== 'overlay') {
81795 groupName = (
81796 getAxisGroup(fullLayout, traceOut.xaxis) +
81797 getAxisGroup(fullLayout, traceOut.yaxis) +
81798 orientation2binDir(traceOut)
81799 );
81800 }
81801 }
81802
81803 if(groupName) {
81804 if(!mustMatchTracesLookup[groupName]) {
81805 mustMatchTracesLookup[groupName] = [];
81806 }
81807 mustMatchTracesLookup[groupName].push(traceOut);
81808 } else {
81809 otherTracesList.push(traceOut);
81810 }
81811 }
81812
81813 // Setup binOpts for traces that have to match,
81814 // if the traces have a valid bingroup, use that
81815 // if not use axis+binDir groupName
81816 for(groupName in mustMatchTracesLookup) {
81817 traces = mustMatchTracesLookup[groupName];
81818
81819 // no need to 'force' anything when a single
81820 // trace is detected as "must match"
81821 if(traces.length === 1) {
81822 otherTracesList.push(traces[0]);
81823 continue;
81824 }
81825
81826 var binGroupFound = false;
81827 for(i = 0; i < traces.length; i++) {
81828 traceOut = traces[i];
81829 binGroupFound = coerce('bingroup');
81830 break;
81831 }
81832
81833 groupName = binGroupFound || groupName;
81834
81835 for(i = 0; i < traces.length; i++) {
81836 traceOut = traces[i];
81837 var bingroupIn = traceOut._input.bingroup;
81838 if(bingroupIn && bingroupIn !== groupName) {
81839 Lib.warn([
81840 'Trace', traceOut.index, 'must match',
81841 'within bingroup', groupName + '.',
81842 'Ignoring its bingroup:', bingroupIn, 'setting.'
81843 ].join(' '));
81844 }
81845 traceOut.bingroup = groupName;
81846
81847 // N.B. no need to worry about 2dMap case
81848 // (where both bin direction are set in each trace)
81849 // as 2dMap trace never "have to match"
81850 fillBinOpts(traceOut, groupName, orientation2binDir(traceOut));
81851 }
81852 }
81853
81854 // setup binOpts for traces that can but don't have to match,
81855 // notice that these traces can be matched with traces that have to match
81856 for(i = 0; i < otherTracesList.length; i++) {
81857 traceOut = otherTracesList[i];
81858
81859 var binGroup = coerce('bingroup');
81860
81861 if(traceIs(traceOut, '2dMap')) {
81862 for(k = 0; k < 2; k++) {
81863 binDir = BINDIRECTIONS[k];
81864 var binGroupInDir = coerce(binDir + 'bingroup',
81865 binGroup ? binGroup + '__' + binDir : null
81866 );
81867 fillBinOpts(traceOut, binGroupInDir, binDir);
81868 }
81869 } else {
81870 fillBinOpts(traceOut, binGroup, orientation2binDir(traceOut));
81871 }
81872 }
81873
81874 // coerce bin attrs!
81875 for(groupName in allBinOpts) {
81876 var binOpts = allBinOpts[groupName];
81877 traces = binOpts.traces;
81878
81879 for(j = 0; j < BINATTRS.length; j++) {
81880 var attrSpec = BINATTRS[j];
81881 var attr = attrSpec.name;
81882 var aStr;
81883 var autoVals;
81884
81885 // nbins(x|y) is moot if we have a size. This depends on
81886 // nbins coming after size in binAttrs.
81887 if(attr === 'nbins' && binOpts.sizeFound) continue;
81888
81889 for(i = 0; i < traces.length; i++) {
81890 traceOut = traces[i];
81891 binDir = binOpts.dirs[i];
81892 aStr = attrSpec.aStr[binDir];
81893
81894 if(nestedProperty(traceOut._input, aStr).get() !== undefined) {
81895 binOpts[attr] = coerce(aStr);
81896 binOpts[attr + 'Found'] = true;
81897 break;
81898 }
81899
81900 autoVals = (traceOut._autoBin || {})[binDir] || {};
81901 if(autoVals[attr]) {
81902 // if this is the *first* autoval
81903 nestedProperty(traceOut, aStr).set(autoVals[attr]);
81904 }
81905 }
81906
81907 // start and end we need to coerce anyway, after having collected the
81908 // first of each into binOpts, in case a trace wants to restrict its
81909 // data to a certain range
81910 if(attr === 'start' || attr === 'end') {
81911 for(; i < traces.length; i++) {
81912 traceOut = traces[i];
81913 if(traceOut['_' + binDir + 'bingroup']) {
81914 autoVals = (traceOut._autoBin || {})[binDir] || {};
81915 coerce(aStr, autoVals[attr]);
81916 }
81917 }
81918 }
81919
81920 if(attr === 'nbins' && !binOpts.sizeFound && !binOpts.nbinsFound) {
81921 traceOut = traces[0];
81922 binOpts[attr] = coerce(aStr);
81923 }
81924 }
81925 }
81926};
81927
81928},{"../../lib":178,"../../plots/cartesian/axis_ids":225,"../../registry":269,"../bar/defaults":283}],353:[function(_dereq_,module,exports){
81929/**
81930* Copyright 2012-2020, Plotly, Inc.
81931* All rights reserved.
81932*
81933* This source code is licensed under the MIT license found in the
81934* LICENSE file in the root directory of this source tree.
81935*/
81936
81937'use strict';
81938
81939var Registry = _dereq_('../../registry');
81940var Lib = _dereq_('../../lib');
81941var Color = _dereq_('../../components/color');
81942
81943var handleStyleDefaults = _dereq_('../bar/style_defaults');
81944var attributes = _dereq_('./attributes');
81945
81946module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
81947 function coerce(attr, dflt) {
81948 return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
81949 }
81950
81951 var x = coerce('x');
81952 var y = coerce('y');
81953
81954 var cumulative = coerce('cumulative.enabled');
81955 if(cumulative) {
81956 coerce('cumulative.direction');
81957 coerce('cumulative.currentbin');
81958 }
81959
81960 coerce('text');
81961 coerce('hovertext');
81962 coerce('hovertemplate');
81963
81964 var orientation = coerce('orientation', (y && !x) ? 'h' : 'v');
81965 var sampleLetter = orientation === 'v' ? 'x' : 'y';
81966 var aggLetter = orientation === 'v' ? 'y' : 'x';
81967
81968 var len = (x && y) ?
81969 Math.min(Lib.minRowLength(x) && Lib.minRowLength(y)) :
81970 Lib.minRowLength(traceOut[sampleLetter] || []);
81971
81972 if(!len) {
81973 traceOut.visible = false;
81974 return;
81975 }
81976
81977 traceOut._length = len;
81978
81979 var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleTraceDefaults');
81980 handleCalendarDefaults(traceIn, traceOut, ['x', 'y'], layout);
81981
81982 var hasAggregationData = traceOut[aggLetter];
81983 if(hasAggregationData) coerce('histfunc');
81984 coerce('histnorm');
81985
81986 // Note: bin defaults are now handled in Histogram.crossTraceDefaults
81987 // autobin(x|y) are only included here to appease Plotly.validate
81988 coerce('autobin' + sampleLetter);
81989
81990 handleStyleDefaults(traceIn, traceOut, coerce, defaultColor, layout);
81991
81992 Lib.coerceSelectionMarkerOpacity(traceOut, coerce);
81993
81994 var lineColor = (traceOut.marker.line || {}).color;
81995
81996 // override defaultColor for error bars with defaultLine
81997 var errorBarsSupplyDefaults = Registry.getComponentMethod('errorbars', 'supplyDefaults');
81998 errorBarsSupplyDefaults(traceIn, traceOut, lineColor || Color.defaultLine, {axis: 'y'});
81999 errorBarsSupplyDefaults(traceIn, traceOut, lineColor || Color.defaultLine, {axis: 'x', inherit: 'y'});
82000};
82001
82002},{"../../components/color":52,"../../lib":178,"../../registry":269,"../bar/style_defaults":294,"./attributes":345}],354:[function(_dereq_,module,exports){
82003/**
82004* Copyright 2012-2020, Plotly, Inc.
82005* All rights reserved.
82006*
82007* This source code is licensed under the MIT license found in the
82008* LICENSE file in the root directory of this source tree.
82009*/
82010
82011'use strict';
82012
82013module.exports = function eventData(out, pt, trace, cd, pointNumber) {
82014 // standard cartesian event data
82015 out.x = 'xVal' in pt ? pt.xVal : pt.x;
82016 out.y = 'yVal' in pt ? pt.yVal : pt.y;
82017
82018 // for 2d histograms
82019 if('zLabelVal' in pt) out.z = pt.zLabelVal;
82020
82021 if(pt.xa) out.xaxis = pt.xa;
82022 if(pt.ya) out.yaxis = pt.ya;
82023
82024 // specific to histogram - CDFs do not have pts (yet?)
82025 if(!(trace.cumulative || {}).enabled) {
82026 var pts = Array.isArray(pointNumber) ?
82027 cd[0].pts[pointNumber[0]][pointNumber[1]] :
82028 cd[pointNumber].pts;
82029
82030 out.pointNumbers = pts;
82031 out.binNumber = out.pointNumber;
82032 delete out.pointNumber;
82033 delete out.pointIndex;
82034
82035 var pointIndices;
82036 if(trace._indexToPoints) {
82037 pointIndices = [];
82038 for(var i = 0; i < pts.length; i++) {
82039 pointIndices = pointIndices.concat(trace._indexToPoints[pts[i]]);
82040 }
82041 } else {
82042 pointIndices = pts;
82043 }
82044
82045 out.pointIndices = pointIndices;
82046 }
82047
82048 return out;
82049};
82050
82051},{}],355:[function(_dereq_,module,exports){
82052/**
82053* Copyright 2012-2020, Plotly, Inc.
82054* All rights reserved.
82055*
82056* This source code is licensed under the MIT license found in the
82057* LICENSE file in the root directory of this source tree.
82058*/
82059
82060
82061'use strict';
82062
82063var barHover = _dereq_('../bar/hover').hoverPoints;
82064var hoverLabelText = _dereq_('../../plots/cartesian/axes').hoverLabelText;
82065
82066module.exports = function hoverPoints(pointData, xval, yval, hovermode) {
82067 var pts = barHover(pointData, xval, yval, hovermode);
82068
82069 if(!pts) return;
82070
82071 pointData = pts[0];
82072 var di = pointData.cd[pointData.index];
82073 var trace = pointData.cd[0].trace;
82074
82075 if(!trace.cumulative.enabled) {
82076 var posLetter = trace.orientation === 'h' ? 'y' : 'x';
82077
82078 pointData[posLetter + 'Label'] = hoverLabelText(pointData[posLetter + 'a'], di.ph0, di.ph1);
82079 }
82080
82081 return pts;
82082};
82083
82084},{"../../plots/cartesian/axes":222,"../bar/hover":286}],356:[function(_dereq_,module,exports){
82085/**
82086* Copyright 2012-2020, Plotly, Inc.
82087* All rights reserved.
82088*
82089* This source code is licensed under the MIT license found in the
82090* LICENSE file in the root directory of this source tree.
82091*/
82092
82093'use strict';
82094
82095/**
82096 * Histogram has its own attribute, defaults and calc steps,
82097 * but uses bar's plot to display
82098 * and bar's crossTraceCalc (formerly known as setPositions) for stacking and grouping
82099 */
82100
82101/**
82102 * histogram errorBarsOK is debatable, but it's put in for backward compat.
82103 * there are use cases for it - sqrt for a simple histogram works right now,
82104 * constant and % work but they're not so meaningful. I guess it could be cool
82105 * to allow quadrature combination of errors in summed histograms...
82106 */
82107
82108module.exports = {
82109 attributes: _dereq_('./attributes'),
82110 layoutAttributes: _dereq_('../bar/layout_attributes'),
82111 supplyDefaults: _dereq_('./defaults'),
82112 crossTraceDefaults: _dereq_('./cross_trace_defaults'),
82113 supplyLayoutDefaults: _dereq_('../bar/layout_defaults'),
82114 calc: _dereq_('./calc').calc,
82115 crossTraceCalc: _dereq_('../bar/cross_trace_calc').crossTraceCalc,
82116 plot: _dereq_('../bar/plot').plot,
82117 layerName: 'barlayer',
82118 style: _dereq_('../bar/style').style,
82119 styleOnSelect: _dereq_('../bar/style').styleOnSelect,
82120 colorbar: _dereq_('../scatter/marker_colorbar'),
82121 hoverPoints: _dereq_('./hover'),
82122 selectPoints: _dereq_('../bar/select'),
82123 eventData: _dereq_('./event_data'),
82124
82125 moduleType: 'trace',
82126 name: 'histogram',
82127 basePlotModule: _dereq_('../../plots/cartesian'),
82128 categories: ['bar-like', 'cartesian', 'svg', 'bar', 'histogram', 'oriented', 'errorBarsOK', 'showLegend'],
82129 meta: {
82130
82131 }
82132};
82133
82134},{"../../plots/cartesian":235,"../bar/cross_trace_calc":282,"../bar/layout_attributes":288,"../bar/layout_defaults":289,"../bar/plot":290,"../bar/select":291,"../bar/style":293,"../scatter/marker_colorbar":407,"./attributes":345,"./calc":350,"./cross_trace_defaults":352,"./defaults":353,"./event_data":354,"./hover":355}],357:[function(_dereq_,module,exports){
82135/**
82136* Copyright 2012-2020, Plotly, Inc.
82137* All rights reserved.
82138*
82139* This source code is licensed under the MIT license found in the
82140* LICENSE file in the root directory of this source tree.
82141*/
82142
82143
82144'use strict';
82145
82146
82147module.exports = {
82148 percent: function(size, total) {
82149 var nMax = size.length;
82150 var norm = 100 / total;
82151 for(var n = 0; n < nMax; n++) size[n] *= norm;
82152 },
82153 probability: function(size, total) {
82154 var nMax = size.length;
82155 for(var n = 0; n < nMax; n++) size[n] /= total;
82156 },
82157 density: function(size, total, inc, yinc) {
82158 var nMax = size.length;
82159 yinc = yinc || 1;
82160 for(var n = 0; n < nMax; n++) size[n] *= inc[n] * yinc;
82161 },
82162 'probability density': function(size, total, inc, yinc) {
82163 var nMax = size.length;
82164 if(yinc) total /= yinc;
82165 for(var n = 0; n < nMax; n++) size[n] *= inc[n] / total;
82166 }
82167};
82168
82169},{}],358:[function(_dereq_,module,exports){
82170/**
82171* Copyright 2012-2020, Plotly, Inc.
82172* All rights reserved.
82173*
82174* This source code is licensed under the MIT license found in the
82175* LICENSE file in the root directory of this source tree.
82176*/
82177
82178'use strict';
82179
82180var histogramAttrs = _dereq_('../histogram/attributes');
82181var makeBinAttrs = _dereq_('../histogram/bin_attributes');
82182var heatmapAttrs = _dereq_('../heatmap/attributes');
82183var baseAttrs = _dereq_('../../plots/attributes');
82184var hovertemplateAttrs = _dereq_('../../plots/template_attributes').hovertemplateAttrs;
82185var colorScaleAttrs = _dereq_('../../components/colorscale/attributes');
82186
82187var extendFlat = _dereq_('../../lib/extend').extendFlat;
82188
82189module.exports = extendFlat(
82190 {
82191 x: histogramAttrs.x,
82192 y: histogramAttrs.y,
82193
82194 z: {
82195 valType: 'data_array',
82196 editType: 'calc',
82197
82198 },
82199 marker: {
82200 color: {
82201 valType: 'data_array',
82202 editType: 'calc',
82203
82204 },
82205 editType: 'calc'
82206 },
82207
82208 histnorm: histogramAttrs.histnorm,
82209 histfunc: histogramAttrs.histfunc,
82210 nbinsx: histogramAttrs.nbinsx,
82211 xbins: makeBinAttrs('x'),
82212 nbinsy: histogramAttrs.nbinsy,
82213 ybins: makeBinAttrs('y'),
82214 autobinx: histogramAttrs.autobinx,
82215 autobiny: histogramAttrs.autobiny,
82216
82217 bingroup: extendFlat({}, histogramAttrs.bingroup, {
82218
82219 }),
82220 xbingroup: extendFlat({}, histogramAttrs.bingroup, {
82221
82222 }),
82223 ybingroup: extendFlat({}, histogramAttrs.bingroup, {
82224
82225 }),
82226
82227 xgap: heatmapAttrs.xgap,
82228 ygap: heatmapAttrs.ygap,
82229 zsmooth: heatmapAttrs.zsmooth,
82230 zhoverformat: heatmapAttrs.zhoverformat,
82231 hovertemplate: hovertemplateAttrs({}, {keys: 'z'}),
82232 showlegend: extendFlat({}, baseAttrs.showlegend, {dflt: false})
82233 },
82234 colorScaleAttrs('', {cLetter: 'z', autoColorDflt: false})
82235);
82236
82237},{"../../components/colorscale/attributes":59,"../../lib/extend":173,"../../plots/attributes":219,"../../plots/template_attributes":264,"../heatmap/attributes":330,"../histogram/attributes":345,"../histogram/bin_attributes":347}],359:[function(_dereq_,module,exports){
82238/**
82239* Copyright 2012-2020, Plotly, Inc.
82240* All rights reserved.
82241*
82242* This source code is licensed under the MIT license found in the
82243* LICENSE file in the root directory of this source tree.
82244*/
82245
82246'use strict';
82247
82248var Lib = _dereq_('../../lib');
82249var Axes = _dereq_('../../plots/cartesian/axes');
82250
82251var binFunctions = _dereq_('../histogram/bin_functions');
82252var normFunctions = _dereq_('../histogram/norm_functions');
82253var doAvg = _dereq_('../histogram/average');
82254var getBinSpanLabelRound = _dereq_('../histogram/bin_label_vals');
82255var calcAllAutoBins = _dereq_('../histogram/calc').calcAllAutoBins;
82256
82257module.exports = function calc(gd, trace) {
82258 var xa = Axes.getFromId(gd, trace.xaxis);
82259 var ya = Axes.getFromId(gd, trace.yaxis);
82260
82261 var xcalendar = trace.xcalendar;
82262 var ycalendar = trace.ycalendar;
82263 var xr2c = function(v) { return xa.r2c(v, 0, xcalendar); };
82264 var yr2c = function(v) { return ya.r2c(v, 0, ycalendar); };
82265 var xc2r = function(v) { return xa.c2r(v, 0, xcalendar); };
82266 var yc2r = function(v) { return ya.c2r(v, 0, ycalendar); };
82267
82268 var i, j, n, m;
82269
82270 // calculate the bins
82271 var xBinsAndPos = calcAllAutoBins(gd, trace, xa, 'x');
82272 var xBinSpec = xBinsAndPos[0];
82273 var xPos0 = xBinsAndPos[1];
82274 var yBinsAndPos = calcAllAutoBins(gd, trace, ya, 'y');
82275 var yBinSpec = yBinsAndPos[0];
82276 var yPos0 = yBinsAndPos[1];
82277
82278 var serieslen = trace._length;
82279 if(xPos0.length > serieslen) xPos0.splice(serieslen, xPos0.length - serieslen);
82280 if(yPos0.length > serieslen) yPos0.splice(serieslen, yPos0.length - serieslen);
82281
82282 // make the empty bin array & scale the map
82283 var z = [];
82284 var onecol = [];
82285 var zerocol = [];
82286 var nonuniformBinsX = typeof xBinSpec.size === 'string';
82287 var nonuniformBinsY = typeof yBinSpec.size === 'string';
82288 var xEdges = [];
82289 var yEdges = [];
82290 var xbins = nonuniformBinsX ? xEdges : xBinSpec;
82291 var ybins = nonuniformBinsY ? yEdges : yBinSpec;
82292 var total = 0;
82293 var counts = [];
82294 var inputPoints = [];
82295 var norm = trace.histnorm;
82296 var func = trace.histfunc;
82297 var densitynorm = norm.indexOf('density') !== -1;
82298 var extremefunc = func === 'max' || func === 'min';
82299 var sizeinit = extremefunc ? null : 0;
82300 var binfunc = binFunctions.count;
82301 var normfunc = normFunctions[norm];
82302 var doavg = false;
82303 var xinc = [];
82304 var yinc = [];
82305
82306 // set a binning function other than count?
82307 // for binning functions: check first for 'z',
82308 // then 'mc' in case we had a colored scatter plot
82309 // and want to transfer these colors to the 2D histo
82310 // TODO: axe this, make it the responsibility of the app changing type? or an impliedEdit?
82311 var rawCounterData = ('z' in trace) ?
82312 trace.z :
82313 (('marker' in trace && Array.isArray(trace.marker.color)) ?
82314 trace.marker.color : '');
82315 if(rawCounterData && func !== 'count') {
82316 doavg = func === 'avg';
82317 binfunc = binFunctions[func];
82318 }
82319
82320 // decrease end a little in case of rounding errors
82321 var xBinSize = xBinSpec.size;
82322 var xBinStart = xr2c(xBinSpec.start);
82323 var xBinEnd = xr2c(xBinSpec.end) +
82324 (xBinStart - Axes.tickIncrement(xBinStart, xBinSize, false, xcalendar)) / 1e6;
82325
82326 for(i = xBinStart; i < xBinEnd; i = Axes.tickIncrement(i, xBinSize, false, xcalendar)) {
82327 onecol.push(sizeinit);
82328 xEdges.push(i);
82329 if(doavg) zerocol.push(0);
82330 }
82331 xEdges.push(i);
82332
82333 var nx = onecol.length;
82334 var dx = (i - xBinStart) / nx;
82335 var x0 = xc2r(xBinStart + dx / 2);
82336
82337 var yBinSize = yBinSpec.size;
82338 var yBinStart = yr2c(yBinSpec.start);
82339 var yBinEnd = yr2c(yBinSpec.end) +
82340 (yBinStart - Axes.tickIncrement(yBinStart, yBinSize, false, ycalendar)) / 1e6;
82341
82342 for(i = yBinStart; i < yBinEnd; i = Axes.tickIncrement(i, yBinSize, false, ycalendar)) {
82343 z.push(onecol.slice());
82344 yEdges.push(i);
82345 var ipCol = new Array(nx);
82346 for(j = 0; j < nx; j++) ipCol[j] = [];
82347 inputPoints.push(ipCol);
82348 if(doavg) counts.push(zerocol.slice());
82349 }
82350 yEdges.push(i);
82351
82352 var ny = z.length;
82353 var dy = (i - yBinStart) / ny;
82354 var y0 = yc2r(yBinStart + dy / 2);
82355
82356 if(densitynorm) {
82357 xinc = makeIncrements(onecol.length, xbins, dx, nonuniformBinsX);
82358 yinc = makeIncrements(z.length, ybins, dy, nonuniformBinsY);
82359 }
82360
82361 // for date axes we need bin bounds to be calcdata. For nonuniform bins
82362 // we already have this, but uniform with start/end/size they're still strings.
82363 if(!nonuniformBinsX && xa.type === 'date') xbins = binsToCalc(xr2c, xbins);
82364 if(!nonuniformBinsY && ya.type === 'date') ybins = binsToCalc(yr2c, ybins);
82365
82366 // put data into bins
82367 var uniqueValsPerX = true;
82368 var uniqueValsPerY = true;
82369 var xVals = new Array(nx);
82370 var yVals = new Array(ny);
82371 var xGapLow = Infinity;
82372 var xGapHigh = Infinity;
82373 var yGapLow = Infinity;
82374 var yGapHigh = Infinity;
82375 for(i = 0; i < serieslen; i++) {
82376 var xi = xPos0[i];
82377 var yi = yPos0[i];
82378 n = Lib.findBin(xi, xbins);
82379 m = Lib.findBin(yi, ybins);
82380 if(n >= 0 && n < nx && m >= 0 && m < ny) {
82381 total += binfunc(n, i, z[m], rawCounterData, counts[m]);
82382 inputPoints[m][n].push(i);
82383
82384 if(uniqueValsPerX) {
82385 if(xVals[n] === undefined) xVals[n] = xi;
82386 else if(xVals[n] !== xi) uniqueValsPerX = false;
82387 }
82388 if(uniqueValsPerY) {
82389 if(yVals[m] === undefined) yVals[m] = yi;
82390 else if(yVals[m] !== yi) uniqueValsPerY = false;
82391 }
82392
82393 xGapLow = Math.min(xGapLow, xi - xEdges[n]);
82394 xGapHigh = Math.min(xGapHigh, xEdges[n + 1] - xi);
82395 yGapLow = Math.min(yGapLow, yi - yEdges[m]);
82396 yGapHigh = Math.min(yGapHigh, yEdges[m + 1] - yi);
82397 }
82398 }
82399 // normalize, if needed
82400 if(doavg) {
82401 for(m = 0; m < ny; m++) total += doAvg(z[m], counts[m]);
82402 }
82403 if(normfunc) {
82404 for(m = 0; m < ny; m++) normfunc(z[m], total, xinc, yinc[m]);
82405 }
82406
82407 return {
82408 x: xPos0,
82409 xRanges: getRanges(xEdges, uniqueValsPerX && xVals, xGapLow, xGapHigh, xa, xcalendar),
82410 x0: x0,
82411 dx: dx,
82412 y: yPos0,
82413 yRanges: getRanges(yEdges, uniqueValsPerY && yVals, yGapLow, yGapHigh, ya, ycalendar),
82414 y0: y0,
82415 dy: dy,
82416 z: z,
82417 pts: inputPoints
82418 };
82419};
82420
82421function makeIncrements(len, bins, dv, nonuniform) {
82422 var out = new Array(len);
82423 var i;
82424 if(nonuniform) {
82425 for(i = 0; i < len; i++) out[i] = 1 / (bins[i + 1] - bins[i]);
82426 } else {
82427 var inc = 1 / dv;
82428 for(i = 0; i < len; i++) out[i] = inc;
82429 }
82430 return out;
82431}
82432
82433function binsToCalc(r2c, bins) {
82434 return {
82435 start: r2c(bins.start),
82436 end: r2c(bins.end),
82437 size: bins.size
82438 };
82439}
82440
82441function getRanges(edges, uniqueVals, gapLow, gapHigh, ax, calendar) {
82442 var i;
82443 var len = edges.length - 1;
82444 var out = new Array(len);
82445 var roundFn = getBinSpanLabelRound(gapLow, gapHigh, edges, ax, calendar);
82446
82447 for(i = 0; i < len; i++) {
82448 var v = (uniqueVals || [])[i];
82449 out[i] = v === undefined ?
82450 [roundFn(edges[i]), roundFn(edges[i + 1], true)] :
82451 [v, v];
82452 }
82453 return out;
82454}
82455
82456},{"../../lib":178,"../../plots/cartesian/axes":222,"../histogram/average":346,"../histogram/bin_functions":348,"../histogram/bin_label_vals":349,"../histogram/calc":350,"../histogram/norm_functions":357}],360:[function(_dereq_,module,exports){
82457/**
82458* Copyright 2012-2020, Plotly, Inc.
82459* All rights reserved.
82460*
82461* This source code is licensed under the MIT license found in the
82462* LICENSE file in the root directory of this source tree.
82463*/
82464
82465
82466'use strict';
82467
82468var Lib = _dereq_('../../lib');
82469
82470var handleSampleDefaults = _dereq_('./sample_defaults');
82471var handleStyleDefaults = _dereq_('../heatmap/style_defaults');
82472var colorscaleDefaults = _dereq_('../../components/colorscale/defaults');
82473var attributes = _dereq_('./attributes');
82474
82475
82476module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
82477 function coerce(attr, dflt) {
82478 return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
82479 }
82480
82481 handleSampleDefaults(traceIn, traceOut, coerce, layout);
82482 if(traceOut.visible === false) return;
82483
82484 handleStyleDefaults(traceIn, traceOut, coerce, layout);
82485 colorscaleDefaults(traceIn, traceOut, layout, coerce, {prefix: '', cLetter: 'z'});
82486 coerce('hovertemplate');
82487};
82488
82489},{"../../components/colorscale/defaults":62,"../../lib":178,"../heatmap/style_defaults":343,"./attributes":358,"./sample_defaults":363}],361:[function(_dereq_,module,exports){
82490/**
82491* Copyright 2012-2020, Plotly, Inc.
82492* All rights reserved.
82493*
82494* This source code is licensed under the MIT license found in the
82495* LICENSE file in the root directory of this source tree.
82496*/
82497
82498
82499'use strict';
82500
82501var heatmapHover = _dereq_('../heatmap/hover');
82502var hoverLabelText = _dereq_('../../plots/cartesian/axes').hoverLabelText;
82503
82504module.exports = function hoverPoints(pointData, xval, yval, hovermode, hoverLayer, contour) {
82505 var pts = heatmapHover(pointData, xval, yval, hovermode, hoverLayer, contour);
82506
82507 if(!pts) return;
82508
82509 pointData = pts[0];
82510 var indices = pointData.index;
82511 var ny = indices[0];
82512 var nx = indices[1];
82513 var cd0 = pointData.cd[0];
82514 var xRange = cd0.xRanges[nx];
82515 var yRange = cd0.yRanges[ny];
82516
82517 pointData.xLabel = hoverLabelText(pointData.xa, xRange[0], xRange[1]);
82518 pointData.yLabel = hoverLabelText(pointData.ya, yRange[0], yRange[1]);
82519
82520 return pts;
82521};
82522
82523},{"../../plots/cartesian/axes":222,"../heatmap/hover":337}],362:[function(_dereq_,module,exports){
82524/**
82525* Copyright 2012-2020, Plotly, Inc.
82526* All rights reserved.
82527*
82528* This source code is licensed under the MIT license found in the
82529* LICENSE file in the root directory of this source tree.
82530*/
82531
82532'use strict';
82533
82534module.exports = {
82535 attributes: _dereq_('./attributes'),
82536 supplyDefaults: _dereq_('./defaults'),
82537 crossTraceDefaults: _dereq_('../histogram/cross_trace_defaults'),
82538 calc: _dereq_('../heatmap/calc'),
82539 plot: _dereq_('../heatmap/plot'),
82540 layerName: 'heatmaplayer',
82541 colorbar: _dereq_('../heatmap/colorbar'),
82542 style: _dereq_('../heatmap/style'),
82543 hoverPoints: _dereq_('./hover'),
82544 eventData: _dereq_('../histogram/event_data'),
82545
82546 moduleType: 'trace',
82547 name: 'histogram2d',
82548 basePlotModule: _dereq_('../../plots/cartesian'),
82549 categories: ['cartesian', 'svg', '2dMap', 'histogram', 'showLegend'],
82550 meta: {
82551
82552
82553 }
82554};
82555
82556},{"../../plots/cartesian":235,"../heatmap/calc":331,"../heatmap/colorbar":333,"../heatmap/plot":341,"../heatmap/style":342,"../histogram/cross_trace_defaults":352,"../histogram/event_data":354,"./attributes":358,"./defaults":360,"./hover":361}],363:[function(_dereq_,module,exports){
82557/**
82558* Copyright 2012-2020, Plotly, Inc.
82559* All rights reserved.
82560*
82561* This source code is licensed under the MIT license found in the
82562* LICENSE file in the root directory of this source tree.
82563*/
82564
82565'use strict';
82566
82567var Registry = _dereq_('../../registry');
82568var Lib = _dereq_('../../lib');
82569
82570module.exports = function handleSampleDefaults(traceIn, traceOut, coerce, layout) {
82571 var x = coerce('x');
82572 var y = coerce('y');
82573 var xlen = Lib.minRowLength(x);
82574 var ylen = Lib.minRowLength(y);
82575
82576 // we could try to accept x0 and dx, etc...
82577 // but that's a pretty weird use case.
82578 // for now require both x and y explicitly specified.
82579 if(!xlen || !ylen) {
82580 traceOut.visible = false;
82581 return;
82582 }
82583
82584 traceOut._length = Math.min(xlen, ylen);
82585
82586 var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleTraceDefaults');
82587 handleCalendarDefaults(traceIn, traceOut, ['x', 'y'], layout);
82588
82589 // if marker.color is an array, we can use it in aggregation instead of z
82590 var hasAggregationData = coerce('z') || coerce('marker.color');
82591
82592 if(hasAggregationData) coerce('histfunc');
82593 coerce('histnorm');
82594
82595 // Note: bin defaults are now handled in Histogram2D.crossTraceDefaults
82596 // autobin(x|y) are only included here to appease Plotly.validate
82597 coerce('autobinx');
82598 coerce('autobiny');
82599};
82600
82601},{"../../lib":178,"../../registry":269}],364:[function(_dereq_,module,exports){
82602/**
82603* Copyright 2012-2020, Plotly, Inc.
82604* All rights reserved.
82605*
82606* This source code is licensed under the MIT license found in the
82607* LICENSE file in the root directory of this source tree.
82608*/
82609
82610'use strict';
82611
82612var histogram2dAttrs = _dereq_('../histogram2d/attributes');
82613var contourAttrs = _dereq_('../contour/attributes');
82614var colorScaleAttrs = _dereq_('../../components/colorscale/attributes');
82615
82616var extendFlat = _dereq_('../../lib/extend').extendFlat;
82617
82618module.exports = extendFlat({
82619 x: histogram2dAttrs.x,
82620 y: histogram2dAttrs.y,
82621 z: histogram2dAttrs.z,
82622 marker: histogram2dAttrs.marker,
82623
82624 histnorm: histogram2dAttrs.histnorm,
82625 histfunc: histogram2dAttrs.histfunc,
82626 nbinsx: histogram2dAttrs.nbinsx,
82627 xbins: histogram2dAttrs.xbins,
82628 nbinsy: histogram2dAttrs.nbinsy,
82629 ybins: histogram2dAttrs.ybins,
82630 autobinx: histogram2dAttrs.autobinx,
82631 autobiny: histogram2dAttrs.autobiny,
82632
82633 bingroup: histogram2dAttrs.bingroup,
82634 xbingroup: histogram2dAttrs.xbingroup,
82635 ybingroup: histogram2dAttrs.ybingroup,
82636
82637 autocontour: contourAttrs.autocontour,
82638 ncontours: contourAttrs.ncontours,
82639 contours: contourAttrs.contours,
82640 line: {
82641 color: contourAttrs.line.color,
82642 width: extendFlat({}, contourAttrs.line.width, {
82643 dflt: 0.5,
82644
82645 }),
82646 dash: contourAttrs.line.dash,
82647 smoothing: contourAttrs.line.smoothing,
82648 editType: 'plot'
82649 },
82650 zhoverformat: histogram2dAttrs.zhoverformat,
82651 hovertemplate: histogram2dAttrs.hovertemplate
82652},
82653 colorScaleAttrs('', {
82654 cLetter: 'z',
82655 editTypeOverride: 'calc'
82656 })
82657);
82658
82659},{"../../components/colorscale/attributes":59,"../../lib/extend":173,"../contour/attributes":308,"../histogram2d/attributes":358}],365:[function(_dereq_,module,exports){
82660/**
82661* Copyright 2012-2020, Plotly, Inc.
82662* All rights reserved.
82663*
82664* This source code is licensed under the MIT license found in the
82665* LICENSE file in the root directory of this source tree.
82666*/
82667
82668
82669'use strict';
82670
82671var Lib = _dereq_('../../lib');
82672
82673var handleSampleDefaults = _dereq_('../histogram2d/sample_defaults');
82674var handleContoursDefaults = _dereq_('../contour/contours_defaults');
82675var handleStyleDefaults = _dereq_('../contour/style_defaults');
82676var attributes = _dereq_('./attributes');
82677
82678
82679module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
82680 function coerce(attr, dflt) {
82681 return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
82682 }
82683
82684 function coerce2(attr) {
82685 return Lib.coerce2(traceIn, traceOut, attributes, attr);
82686 }
82687
82688 handleSampleDefaults(traceIn, traceOut, coerce, layout);
82689 if(traceOut.visible === false) return;
82690
82691 handleContoursDefaults(traceIn, traceOut, coerce, coerce2);
82692 handleStyleDefaults(traceIn, traceOut, coerce, layout);
82693 coerce('hovertemplate');
82694};
82695
82696},{"../../lib":178,"../contour/contours_defaults":315,"../contour/style_defaults":329,"../histogram2d/sample_defaults":363,"./attributes":364}],366:[function(_dereq_,module,exports){
82697/**
82698* Copyright 2012-2020, Plotly, Inc.
82699* All rights reserved.
82700*
82701* This source code is licensed under the MIT license found in the
82702* LICENSE file in the root directory of this source tree.
82703*/
82704
82705'use strict';
82706
82707module.exports = {
82708 attributes: _dereq_('./attributes'),
82709 supplyDefaults: _dereq_('./defaults'),
82710 crossTraceDefaults: _dereq_('../histogram/cross_trace_defaults'),
82711 calc: _dereq_('../contour/calc'),
82712 plot: _dereq_('../contour/plot').plot,
82713 layerName: 'contourlayer',
82714 style: _dereq_('../contour/style'),
82715 colorbar: _dereq_('../contour/colorbar'),
82716 hoverPoints: _dereq_('../contour/hover'),
82717
82718 moduleType: 'trace',
82719 name: 'histogram2dcontour',
82720 basePlotModule: _dereq_('../../plots/cartesian'),
82721 categories: ['cartesian', 'svg', '2dMap', 'contour', 'histogram', 'showLegend'],
82722 meta: {
82723
82724
82725 }
82726};
82727
82728},{"../../plots/cartesian":235,"../contour/calc":309,"../contour/colorbar":311,"../contour/hover":321,"../contour/plot":326,"../contour/style":328,"../histogram/cross_trace_defaults":352,"./attributes":364,"./defaults":365}],367:[function(_dereq_,module,exports){
82729/**
82730* Copyright 2012-2020, Plotly, Inc.
82731* All rights reserved.
82732*
82733* This source code is licensed under the MIT license found in the
82734* LICENSE file in the root directory of this source tree.
82735*/
82736
82737'use strict';
82738
82739var baseAttrs = _dereq_('../../plots/attributes');
82740var hovertemplateAttrs = _dereq_('../../plots/template_attributes').hovertemplateAttrs;
82741var extendFlat = _dereq_('../../lib/extend').extendFlat;
82742var colormodel = _dereq_('./constants').colormodel;
82743
82744var cm = ['rgb', 'rgba', 'hsl', 'hsla'];
82745var zminDesc = [];
82746var zmaxDesc = [];
82747for(var i = 0; i < cm.length; i++) {
82748 zminDesc.push('For the `' + cm[i] + '` colormodel, it is [' + colormodel[cm[i]].min.join(', ') + '].');
82749 zmaxDesc.push('For the `' + cm[i] + '` colormodel, it is [' + colormodel[cm[i]].max.join(', ') + '].');
82750}
82751
82752module.exports = extendFlat({
82753 z: {
82754 valType: 'data_array',
82755
82756 editType: 'calc',
82757
82758 },
82759 colormodel: {
82760 valType: 'enumerated',
82761 values: cm,
82762 dflt: 'rgb',
82763
82764 editType: 'calc',
82765
82766 },
82767 zmin: {
82768 valType: 'info_array',
82769 items: [
82770 {valType: 'number', editType: 'calc'},
82771 {valType: 'number', editType: 'calc'},
82772 {valType: 'number', editType: 'calc'},
82773 {valType: 'number', editType: 'calc'}
82774 ],
82775
82776 editType: 'calc',
82777
82778 },
82779 zmax: {
82780 valType: 'info_array',
82781 items: [
82782 {valType: 'number', editType: 'calc'},
82783 {valType: 'number', editType: 'calc'},
82784 {valType: 'number', editType: 'calc'},
82785 {valType: 'number', editType: 'calc'}
82786 ],
82787
82788 editType: 'calc',
82789
82790 },
82791 x0: {
82792 valType: 'any',
82793 dflt: 0,
82794
82795 editType: 'calc+clearAxisTypes',
82796
82797 },
82798 y0: {
82799 valType: 'any',
82800 dflt: 0,
82801
82802 editType: 'calc+clearAxisTypes',
82803
82804 },
82805 dx: {
82806 valType: 'number',
82807 dflt: 1,
82808
82809 editType: 'calc',
82810
82811 },
82812 dy: {
82813 valType: 'number',
82814 dflt: 1,
82815
82816 editType: 'calc',
82817
82818 },
82819 text: {
82820 valType: 'data_array',
82821 editType: 'plot',
82822
82823 },
82824 hovertext: {
82825 valType: 'data_array',
82826 editType: 'plot',
82827
82828 },
82829 hoverinfo: extendFlat({}, baseAttrs.hoverinfo, {
82830 flags: ['x', 'y', 'z', 'color', 'name', 'text'],
82831 dflt: 'x+y+z+text+name'
82832 }),
82833 hovertemplate: hovertemplateAttrs({}, {
82834 keys: ['z', 'color', 'colormodel']
82835 }),
82836
82837 transforms: undefined
82838});
82839
82840},{"../../lib/extend":173,"../../plots/attributes":219,"../../plots/template_attributes":264,"./constants":369}],368:[function(_dereq_,module,exports){
82841/**
82842* Copyright 2012-2020, Plotly, Inc.
82843* All rights reserved.
82844*
82845* This source code is licensed under the MIT license found in the
82846* LICENSE file in the root directory of this source tree.
82847*/
82848
82849'use strict';
82850
82851var Lib = _dereq_('../../lib');
82852var constants = _dereq_('./constants');
82853var isNumeric = _dereq_('fast-isnumeric');
82854var Axes = _dereq_('../../plots/cartesian/axes');
82855var maxRowLength = _dereq_('../../lib').maxRowLength;
82856
82857module.exports = function calc(gd, trace) {
82858 var xa = Axes.getFromId(gd, trace.xaxis || 'x');
82859 var ya = Axes.getFromId(gd, trace.yaxis || 'y');
82860
82861 var x0 = xa.d2c(trace.x0) - trace.dx / 2;
82862 var y0 = ya.d2c(trace.y0) - trace.dy / 2;
82863 var h = trace.z.length;
82864 var w = maxRowLength(trace.z);
82865
82866 // Set axis range
82867 var i;
82868 var xrange = [x0, x0 + w * trace.dx];
82869 var yrange = [y0, y0 + h * trace.dy];
82870 if(xa && xa.type === 'log') for(i = 0; i < w; i++) xrange.push(x0 + i * trace.dx);
82871 if(ya && ya.type === 'log') for(i = 0; i < h; i++) yrange.push(y0 + i * trace.dy);
82872 trace._extremes[xa._id] = Axes.findExtremes(xa, xrange);
82873 trace._extremes[ya._id] = Axes.findExtremes(ya, yrange);
82874 trace._scaler = makeScaler(trace);
82875
82876 var cd0 = {
82877 x0: x0,
82878 y0: y0,
82879 z: trace.z,
82880 w: w,
82881 h: h
82882 };
82883 return [cd0];
82884};
82885
82886function scale(zero, ratio, min, max) {
82887 return function(c) {
82888 return Lib.constrain((c - zero) * ratio, min, max);
82889 };
82890}
82891
82892function constrain(min, max) {
82893 return function(c) { return Lib.constrain(c, min, max);};
82894}
82895
82896// Generate a function to scale color components according to zmin/zmax and the colormodel
82897function makeScaler(trace) {
82898 var colormodel = trace.colormodel;
82899 var n = colormodel.length;
82900 var cr = constants.colormodel[colormodel];
82901
82902 trace._sArray = [];
82903 // Loop over all color components
82904 for(var k = 0; k < n; k++) {
82905 if(cr.min[k] !== trace.zmin[k] || cr.max[k] !== trace.zmax[k]) {
82906 trace._sArray.push(scale(
82907 trace.zmin[k],
82908 (cr.max[k] - cr.min[k]) / (trace.zmax[k] - trace.zmin[k]),
82909 cr.min[k],
82910 cr.max[k]
82911 ));
82912 } else {
82913 trace._sArray.push(constrain(cr.min[k], cr.max[k]));
82914 }
82915 }
82916
82917 return function(pixel) {
82918 var c = pixel.slice(0, n);
82919 for(var k = 0; k < n; k++) {
82920 var ck = c[k];
82921 if(!isNumeric(ck)) return false;
82922 c[k] = trace._sArray[k](ck);
82923 }
82924 return c;
82925 };
82926}
82927
82928},{"../../lib":178,"../../plots/cartesian/axes":222,"./constants":369,"fast-isnumeric":18}],369:[function(_dereq_,module,exports){
82929/**
82930* Copyright 2012-2020, Plotly, Inc.
82931* All rights reserved.
82932*
82933* This source code is licensed under the MIT license found in the
82934* LICENSE file in the root directory of this source tree.
82935*/
82936
82937'use strict';
82938
82939module.exports = {
82940 colormodel: {
82941 rgb: {
82942 min: [0, 0, 0],
82943 max: [255, 255, 255],
82944 fmt: function(c) {return c.slice(0, 3);},
82945 suffix: ['', '', '']
82946 },
82947 rgba: {
82948 min: [0, 0, 0, 0],
82949 max: [255, 255, 255, 1],
82950 fmt: function(c) {return c.slice(0, 4);},
82951 suffix: ['', '', '', '']
82952 },
82953 hsl: {
82954 min: [0, 0, 0],
82955 max: [360, 100, 100],
82956 fmt: function(c) {
82957 var p = c.slice(0, 3);
82958 p[1] = p[1] + '%';
82959 p[2] = p[2] + '%';
82960 return p;
82961 },
82962 suffix: ['°', '%', '%']
82963 },
82964 hsla: {
82965 min: [0, 0, 0, 0],
82966 max: [360, 100, 100, 1],
82967 fmt: function(c) {
82968 var p = c.slice(0, 4);
82969 p[1] = p[1] + '%';
82970 p[2] = p[2] + '%';
82971 return p;
82972 },
82973 suffix: ['°', '%', '%', '']
82974 }
82975 }
82976};
82977
82978},{}],370:[function(_dereq_,module,exports){
82979/**
82980* Copyright 2012-2020, Plotly, Inc.
82981* All rights reserved.
82982*
82983* This source code is licensed under the MIT license found in the
82984* LICENSE file in the root directory of this source tree.
82985*/
82986
82987'use strict';
82988
82989var Lib = _dereq_('../../lib');
82990var attributes = _dereq_('./attributes');
82991var constants = _dereq_('./constants');
82992
82993module.exports = function supplyDefaults(traceIn, traceOut) {
82994 function coerce(attr, dflt) {
82995 return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
82996 }
82997 var z = coerce('z');
82998 if(z === undefined || !z.length || !z[0] || !z[0].length) {
82999 traceOut.visible = false;
83000 return;
83001 }
83002
83003 coerce('x0');
83004 coerce('y0');
83005 coerce('dx');
83006 coerce('dy');
83007 var colormodel = coerce('colormodel');
83008
83009 coerce('zmin', constants.colormodel[colormodel].min);
83010 coerce('zmax', constants.colormodel[colormodel].max);
83011
83012 coerce('text');
83013 coerce('hovertext');
83014 coerce('hovertemplate');
83015
83016 traceOut._length = null;
83017};
83018
83019},{"../../lib":178,"./attributes":367,"./constants":369}],371:[function(_dereq_,module,exports){
83020/**
83021* Copyright 2012-2020, Plotly, Inc.
83022* All rights reserved.
83023*
83024* This source code is licensed under the MIT license found in the
83025* LICENSE file in the root directory of this source tree.
83026*/
83027
83028'use strict';
83029
83030module.exports = function eventData(out, pt) {
83031 if('xVal' in pt) out.x = pt.xVal;
83032 if('yVal' in pt) out.y = pt.yVal;
83033 if(pt.xa) out.xaxis = pt.xa;
83034 if(pt.ya) out.yaxis = pt.ya;
83035 out.color = pt.color;
83036 out.colormodel = pt.trace.colormodel;
83037 return out;
83038};
83039
83040},{}],372:[function(_dereq_,module,exports){
83041/**
83042* Copyright 2012-2020, Plotly, Inc.
83043* All rights reserved.
83044*
83045* This source code is licensed under the MIT license found in the
83046* LICENSE file in the root directory of this source tree.
83047*/
83048
83049'use strict';
83050
83051var Fx = _dereq_('../../components/fx');
83052var Lib = _dereq_('../../lib');
83053var constants = _dereq_('./constants');
83054
83055module.exports = function hoverPoints(pointData, xval, yval) {
83056 var cd0 = pointData.cd[0];
83057 var trace = cd0.trace;
83058 var xa = pointData.xa;
83059 var ya = pointData.ya;
83060
83061 // Return early if not on image
83062 if(Fx.inbox(xval - cd0.x0, xval - (cd0.x0 + cd0.w * trace.dx), 0) > 0 ||
83063 Fx.inbox(yval - cd0.y0, yval - (cd0.y0 + cd0.h * trace.dy), 0) > 0) {
83064 return;
83065 }
83066
83067 // Find nearest pixel's index
83068 var nx = Math.floor((xval - cd0.x0) / trace.dx);
83069 var ny = Math.floor(Math.abs(yval - cd0.y0) / trace.dy);
83070
83071 // return early if pixel is undefined
83072 if(!cd0.z[ny][nx]) return;
83073
83074 var hoverinfo = cd0.hi || trace.hoverinfo;
83075 var fmtColor;
83076 if(hoverinfo) {
83077 var parts = hoverinfo.split('+');
83078 if(parts.indexOf('all') !== -1) parts = ['color'];
83079 if(parts.indexOf('color') !== -1) fmtColor = true;
83080 }
83081
83082 var colormodel = trace.colormodel;
83083 var dims = colormodel.length;
83084 var c = trace._scaler(cd0.z[ny][nx]);
83085 var s = constants.colormodel[colormodel].suffix;
83086
83087 var colorstring = [];
83088 if(trace.hovertemplate || fmtColor) {
83089 colorstring.push('[' + [c[0] + s[0], c[1] + s[1], c[2] + s[2]].join(', '));
83090 if(dims === 4) colorstring.push(', ' + c[3] + s[3]);
83091 colorstring.push(']');
83092 colorstring = colorstring.join('');
83093 pointData.extraText = colormodel.toUpperCase() + ': ' + colorstring;
83094 }
83095
83096 var text;
83097 if(Array.isArray(trace.hovertext) && Array.isArray(trace.hovertext[ny])) {
83098 text = trace.hovertext[ny][nx];
83099 } else if(Array.isArray(trace.text) && Array.isArray(trace.text[ny])) {
83100 text = trace.text[ny][nx];
83101 }
83102
83103 // TODO: for color model with 3 dims, display something useful for hovertemplate `%{color[3]}`
83104 var py = ya.c2p(cd0.y0 + (ny + 0.5) * trace.dy);
83105 var xVal = cd0.x0 + (nx + 0.5) * trace.dx;
83106 var yVal = cd0.y0 + (ny + 0.5) * trace.dy;
83107 var zLabel = '[' + cd0.z[ny][nx].slice(0, trace.colormodel.length).join(', ') + ']';
83108 return [Lib.extendFlat(pointData, {
83109 index: [ny, nx],
83110 x0: xa.c2p(cd0.x0 + nx * trace.dx),
83111 x1: xa.c2p(cd0.x0 + (nx + 1) * trace.dx),
83112 y0: py,
83113 y1: py,
83114 color: c,
83115 xVal: xVal,
83116 xLabelVal: xVal,
83117 yVal: yVal,
83118 yLabelVal: yVal,
83119 zLabelVal: zLabel,
83120 text: text,
83121 hovertemplateLabels: {
83122 'zLabel': zLabel,
83123 'colorLabel': colorstring,
83124 'color[0]Label': c[0] + s[0],
83125 'color[1]Label': c[1] + s[1],
83126 'color[2]Label': c[2] + s[2],
83127 'color[3]Label': c[3] + s[3]
83128 }
83129 })];
83130};
83131
83132},{"../../components/fx":92,"../../lib":178,"./constants":369}],373:[function(_dereq_,module,exports){
83133/**
83134* Copyright 2012-2020, Plotly, Inc.
83135* All rights reserved.
83136*
83137* This source code is licensed under the MIT license found in the
83138* LICENSE file in the root directory of this source tree.
83139*/
83140
83141'use strict';
83142
83143module.exports = {
83144 attributes: _dereq_('./attributes'),
83145 supplyDefaults: _dereq_('./defaults'),
83146 calc: _dereq_('./calc'),
83147 plot: _dereq_('./plot'),
83148 style: _dereq_('./style'),
83149 hoverPoints: _dereq_('./hover'),
83150 eventData: _dereq_('./event_data'),
83151
83152 moduleType: 'trace',
83153 name: 'image',
83154 basePlotModule: _dereq_('../../plots/cartesian'),
83155 categories: ['cartesian', 'svg', '2dMap', 'noSortingByValue'],
83156 animatable: false,
83157 meta: {
83158
83159 }
83160};
83161
83162},{"../../plots/cartesian":235,"./attributes":367,"./calc":368,"./defaults":370,"./event_data":371,"./hover":372,"./plot":374,"./style":375}],374:[function(_dereq_,module,exports){
83163/**
83164* Copyright 2012-2020, Plotly, Inc.
83165* All rights reserved.
83166*
83167* This source code is licensed under the MIT license found in the
83168* LICENSE file in the root directory of this source tree.
83169*/
83170
83171'use strict';
83172
83173var d3 = _dereq_('d3');
83174var Lib = _dereq_('../../lib');
83175var xmlnsNamespaces = _dereq_('../../constants/xmlns_namespaces');
83176var constants = _dereq_('./constants');
83177
83178module.exports = function plot(gd, plotinfo, cdimage, imageLayer) {
83179 var xa = plotinfo.xaxis;
83180 var ya = plotinfo.yaxis;
83181
83182 Lib.makeTraceGroups(imageLayer, cdimage, 'im').each(function(cd) {
83183 var plotGroup = d3.select(this);
83184 var cd0 = cd[0];
83185 var trace = cd0.trace;
83186
83187 var z = cd0.z;
83188 var x0 = cd0.x0;
83189 var y0 = cd0.y0;
83190 var w = cd0.w;
83191 var h = cd0.h;
83192 var dx = trace.dx;
83193 var dy = trace.dy;
83194
83195 var left, right, temp, top, bottom, i;
83196 // in case of log of a negative
83197 i = 0;
83198 while(left === undefined && i < w) {
83199 left = xa.c2p(x0 + i * dx);
83200 i++;
83201 }
83202 i = w;
83203 while(right === undefined && i > 0) {
83204 right = xa.c2p(x0 + i * dx);
83205 i--;
83206 }
83207 i = 0;
83208 while(top === undefined && i < h) {
83209 top = ya.c2p(y0 + i * dy);
83210 i++;
83211 }
83212 i = h;
83213 while(bottom === undefined && i > 0) {
83214 bottom = ya.c2p(y0 + i * dy);
83215 i--;
83216 }
83217
83218 if(right < left) {
83219 temp = right;
83220 right = left;
83221 left = temp;
83222 }
83223
83224 if(bottom < top) {
83225 temp = top;
83226 top = bottom;
83227 bottom = temp;
83228 }
83229
83230 // Reduce image size when zoomed in to save memory
83231 var extra = 0.5; // half the axis size
83232 left = Math.max(-extra * xa._length, left);
83233 right = Math.min((1 + extra) * xa._length, right);
83234 top = Math.max(-extra * ya._length, top);
83235 bottom = Math.min((1 + extra) * ya._length, bottom);
83236 var imageWidth = Math.round(right - left);
83237 var imageHeight = Math.round(bottom - top);
83238
83239 // if image is entirely off-screen, don't even draw it
83240 var isOffScreen = (imageWidth <= 0 || imageHeight <= 0);
83241 if(isOffScreen) {
83242 var noImage = plotGroup.selectAll('image').data([]);
83243 noImage.exit().remove();
83244 return;
83245 }
83246
83247 // Draw each pixel
83248 var canvas = document.createElement('canvas');
83249 canvas.width = imageWidth;
83250 canvas.height = imageHeight;
83251 var context = canvas.getContext('2d');
83252
83253 var ipx = function(i) {return Lib.constrain(Math.round(xa.c2p(x0 + i * dx) - left), 0, imageWidth);};
83254 var jpx = function(j) {return Lib.constrain(Math.round(ya.c2p(y0 + j * dy) - top), 0, imageHeight);};
83255
83256 var fmt = constants.colormodel[trace.colormodel].fmt;
83257 var c;
83258 for(i = 0; i < cd0.w; i++) {
83259 var ipx0 = ipx(i); var ipx1 = ipx(i + 1);
83260 if(ipx1 === ipx0 || isNaN(ipx1) || isNaN(ipx0)) continue;
83261 for(var j = 0; j < cd0.h; j++) {
83262 var jpx0 = jpx(j); var jpx1 = jpx(j + 1);
83263 if(jpx1 === jpx0 || isNaN(jpx1) || isNaN(jpx0) || !z[j][i]) continue;
83264 c = trace._scaler(z[j][i]);
83265 if(c) {
83266 context.fillStyle = trace.colormodel + '(' + fmt(c).join(',') + ')';
83267 } else {
83268 // Return a transparent pixel
83269 context.fillStyle = 'rgba(0,0,0,0)';
83270 }
83271 context.fillRect(ipx0, jpx0, ipx1 - ipx0, jpx1 - jpx0);
83272 }
83273 }
83274
83275 var image3 = plotGroup.selectAll('image')
83276 .data(cd);
83277
83278 image3.enter().append('svg:image').attr({
83279 xmlns: xmlnsNamespaces.svg,
83280 preserveAspectRatio: 'none'
83281 });
83282
83283 image3.attr({
83284 height: imageHeight,
83285 width: imageWidth,
83286 x: left,
83287 y: top,
83288 'xlink:href': canvas.toDataURL('image/png')
83289 });
83290 });
83291};
83292
83293},{"../../constants/xmlns_namespaces":159,"../../lib":178,"./constants":369,"d3":16}],375:[function(_dereq_,module,exports){
83294/**
83295* Copyright 2012-2020, Plotly, Inc.
83296* All rights reserved.
83297*
83298* This source code is licensed under the MIT license found in the
83299* LICENSE file in the root directory of this source tree.
83300*/
83301
83302'use strict';
83303
83304var d3 = _dereq_('d3');
83305
83306module.exports = function style(gd) {
83307 d3.select(gd).selectAll('.im image')
83308 .style('opacity', function(d) {
83309 return d.trace.opacity;
83310 });
83311};
83312
83313},{"d3":16}],376:[function(_dereq_,module,exports){
83314/**
83315* Copyright 2012-2020, Plotly, Inc.
83316* All rights reserved.
83317*
83318* This source code is licensed under the MIT license found in the
83319* LICENSE file in the root directory of this source tree.
83320*/
83321
83322'use strict';
83323
83324var baseAttrs = _dereq_('../../plots/attributes');
83325var domainAttrs = _dereq_('../../plots/domain').attributes;
83326var fontAttrs = _dereq_('../../plots/font_attributes');
83327var colorAttrs = _dereq_('../../components/color/attributes');
83328var hovertemplateAttrs = _dereq_('../../plots/template_attributes').hovertemplateAttrs;
83329var texttemplateAttrs = _dereq_('../../plots/template_attributes').texttemplateAttrs;
83330
83331var extendFlat = _dereq_('../../lib/extend').extendFlat;
83332
83333var textFontAttrs = fontAttrs({
83334 editType: 'plot',
83335 arrayOk: true,
83336 colorEditType: 'plot',
83337
83338});
83339
83340module.exports = {
83341 labels: {
83342 valType: 'data_array',
83343 editType: 'calc',
83344
83345 },
83346 // equivalent of x0 and dx, if label is missing
83347 label0: {
83348 valType: 'number',
83349
83350 dflt: 0,
83351 editType: 'calc',
83352
83353 },
83354 dlabel: {
83355 valType: 'number',
83356
83357 dflt: 1,
83358 editType: 'calc',
83359
83360 },
83361
83362 values: {
83363 valType: 'data_array',
83364 editType: 'calc',
83365
83366 },
83367
83368 marker: {
83369 colors: {
83370 valType: 'data_array', // TODO 'color_array' ?
83371 editType: 'calc',
83372
83373 },
83374
83375 line: {
83376 color: {
83377 valType: 'color',
83378
83379 dflt: colorAttrs.defaultLine,
83380 arrayOk: true,
83381 editType: 'style',
83382
83383 },
83384 width: {
83385 valType: 'number',
83386
83387 min: 0,
83388 dflt: 0,
83389 arrayOk: true,
83390 editType: 'style',
83391
83392 },
83393 editType: 'calc'
83394 },
83395 editType: 'calc'
83396 },
83397
83398 text: {
83399 valType: 'data_array',
83400 editType: 'plot',
83401
83402 },
83403 hovertext: {
83404 valType: 'string',
83405
83406 dflt: '',
83407 arrayOk: true,
83408 editType: 'style',
83409
83410 },
83411
83412// 'see eg:'
83413// 'https://www.e-education.psu.edu/natureofgeoinfo/sites/www.e-education.psu.edu.natureofgeoinfo/files/image/hisp_pies.gif',
83414// '(this example involves a map too - may someday be a whole trace type',
83415// 'of its own. but the point is the size of the whole pie is important.)'
83416 scalegroup: {
83417 valType: 'string',
83418
83419 dflt: '',
83420 editType: 'calc',
83421
83422 },
83423
83424 // labels (legend is handled by plots.attributes.showlegend and layout.hiddenlabels)
83425 textinfo: {
83426 valType: 'flaglist',
83427
83428 flags: ['label', 'text', 'value', 'percent'],
83429 extras: ['none'],
83430 editType: 'calc',
83431
83432 },
83433 hoverinfo: extendFlat({}, baseAttrs.hoverinfo, {
83434 flags: ['label', 'text', 'value', 'percent', 'name']
83435 }),
83436 hovertemplate: hovertemplateAttrs({}, {
83437 keys: ['label', 'color', 'value', 'percent', 'text']
83438 }),
83439 texttemplate: texttemplateAttrs({editType: 'plot'}, {
83440 keys: ['label', 'color', 'value', 'percent', 'text']
83441 }),
83442 textposition: {
83443 valType: 'enumerated',
83444
83445 values: ['inside', 'outside', 'auto', 'none'],
83446 dflt: 'auto',
83447 arrayOk: true,
83448 editType: 'plot',
83449
83450 },
83451 textfont: extendFlat({}, textFontAttrs, {
83452
83453 }),
83454 insidetextorientation: {
83455 valType: 'enumerated',
83456
83457 values: ['horizontal', 'radial', 'tangential', 'auto'],
83458 dflt: 'auto',
83459 editType: 'plot',
83460
83461 },
83462 insidetextfont: extendFlat({}, textFontAttrs, {
83463
83464 }),
83465 outsidetextfont: extendFlat({}, textFontAttrs, {
83466
83467 }),
83468 automargin: {
83469 valType: 'boolean',
83470 dflt: false,
83471
83472 editType: 'plot',
83473
83474 },
83475
83476 title: {
83477 text: {
83478 valType: 'string',
83479 dflt: '',
83480
83481 editType: 'plot',
83482
83483 },
83484 font: extendFlat({}, textFontAttrs, {
83485
83486 }),
83487 position: {
83488 valType: 'enumerated',
83489 values: [
83490 'top left', 'top center', 'top right',
83491 'middle center',
83492 'bottom left', 'bottom center', 'bottom right'
83493 ],
83494
83495 editType: 'plot',
83496
83497 },
83498
83499 editType: 'plot'
83500 },
83501
83502 // position and shape
83503 domain: domainAttrs({name: 'pie', trace: true, editType: 'calc'}),
83504
83505 hole: {
83506 valType: 'number',
83507
83508 min: 0,
83509 max: 1,
83510 dflt: 0,
83511 editType: 'calc',
83512
83513 },
83514
83515 // ordering and direction
83516 sort: {
83517 valType: 'boolean',
83518
83519 dflt: true,
83520 editType: 'calc',
83521
83522 },
83523 direction: {
83524 /**
83525 * there are two common conventions, both of which place the first
83526 * (largest, if sorted) slice with its left edge at 12 o'clock but
83527 * succeeding slices follow either cw or ccw from there.
83528 *
83529 * see http://visage.co/data-visualization-101-pie-charts/
83530 */
83531 valType: 'enumerated',
83532 values: ['clockwise', 'counterclockwise'],
83533
83534 dflt: 'counterclockwise',
83535 editType: 'calc',
83536
83537 },
83538 rotation: {
83539 valType: 'number',
83540
83541 min: -360,
83542 max: 360,
83543 dflt: 0,
83544 editType: 'calc',
83545
83546 },
83547
83548 pull: {
83549 valType: 'number',
83550
83551 min: 0,
83552 max: 1,
83553 dflt: 0,
83554 arrayOk: true,
83555 editType: 'calc',
83556
83557 },
83558
83559 _deprecated: {
83560 title: {
83561 valType: 'string',
83562 dflt: '',
83563
83564 editType: 'calc',
83565
83566 },
83567 titlefont: extendFlat({}, textFontAttrs, {
83568
83569 }),
83570 titleposition: {
83571 valType: 'enumerated',
83572 values: [
83573 'top left', 'top center', 'top right',
83574 'middle center',
83575 'bottom left', 'bottom center', 'bottom right'
83576 ],
83577
83578 editType: 'calc',
83579
83580 }
83581 }
83582};
83583
83584},{"../../components/color/attributes":51,"../../lib/extend":173,"../../plots/attributes":219,"../../plots/domain":249,"../../plots/font_attributes":250,"../../plots/template_attributes":264}],377:[function(_dereq_,module,exports){
83585/**
83586* Copyright 2012-2020, Plotly, Inc.
83587* All rights reserved.
83588*
83589* This source code is licensed under the MIT license found in the
83590* LICENSE file in the root directory of this source tree.
83591*/
83592
83593'use strict';
83594
83595var plots = _dereq_('../../plots/plots');
83596
83597exports.name = 'pie';
83598
83599exports.plot = function(gd, traces, transitionOpts, makeOnCompleteCallback) {
83600 plots.plotBasePlot(exports.name, gd, traces, transitionOpts, makeOnCompleteCallback);
83601};
83602
83603exports.clean = function(newFullData, newFullLayout, oldFullData, oldFullLayout) {
83604 plots.cleanBasePlot(exports.name, newFullData, newFullLayout, oldFullData, oldFullLayout);
83605};
83606
83607},{"../../plots/plots":256}],378:[function(_dereq_,module,exports){
83608/**
83609* Copyright 2012-2020, Plotly, Inc.
83610* All rights reserved.
83611*
83612* This source code is licensed under the MIT license found in the
83613* LICENSE file in the root directory of this source tree.
83614*/
83615
83616'use strict';
83617
83618var isNumeric = _dereq_('fast-isnumeric');
83619var tinycolor = _dereq_('tinycolor2');
83620
83621var Color = _dereq_('../../components/color');
83622
83623var extendedColorWayList = {};
83624
83625function calc(gd, trace) {
83626 var cd = [];
83627
83628 var fullLayout = gd._fullLayout;
83629 var hiddenLabels = fullLayout.hiddenlabels || [];
83630
83631 var labels = trace.labels;
83632 var colors = trace.marker.colors || [];
83633 var vals = trace.values;
83634 var len = trace._length;
83635 var hasValues = trace._hasValues && len;
83636
83637 var i, pt;
83638
83639 if(trace.dlabel) {
83640 labels = new Array(len);
83641 for(i = 0; i < len; i++) {
83642 labels[i] = String(trace.label0 + i * trace.dlabel);
83643 }
83644 }
83645
83646 var allThisTraceLabels = {};
83647 var pullColor = makePullColorFn(fullLayout['_' + trace.type + 'colormap']);
83648 var vTotal = 0;
83649 var isAggregated = false;
83650
83651 for(i = 0; i < len; i++) {
83652 var v, label, hidden;
83653 if(hasValues) {
83654 v = vals[i];
83655 if(!isNumeric(v)) continue;
83656 v = +v;
83657 if(v < 0) continue;
83658 } else v = 1;
83659
83660 label = labels[i];
83661 if(label === undefined || label === '') label = i;
83662 label = String(label);
83663
83664 var thisLabelIndex = allThisTraceLabels[label];
83665 if(thisLabelIndex === undefined) {
83666 allThisTraceLabels[label] = cd.length;
83667
83668 hidden = hiddenLabels.indexOf(label) !== -1;
83669
83670 if(!hidden) vTotal += v;
83671
83672 cd.push({
83673 v: v,
83674 label: label,
83675 color: pullColor(colors[i], label),
83676 i: i,
83677 pts: [i],
83678 hidden: hidden
83679 });
83680 } else {
83681 isAggregated = true;
83682
83683 pt = cd[thisLabelIndex];
83684 pt.v += v;
83685 pt.pts.push(i);
83686 if(!pt.hidden) vTotal += v;
83687
83688 if(pt.color === false && colors[i]) {
83689 pt.color = pullColor(colors[i], label);
83690 }
83691 }
83692 }
83693
83694 var shouldSort = (trace.type === 'funnelarea') ? isAggregated : trace.sort;
83695 if(shouldSort) cd.sort(function(a, b) { return b.v - a.v; });
83696
83697 // include the sum of all values in the first point
83698 if(cd[0]) cd[0].vTotal = vTotal;
83699
83700 return cd;
83701}
83702
83703function makePullColorFn(colorMap) {
83704 return function pullColor(color, id) {
83705 if(!color) return false;
83706
83707 color = tinycolor(color);
83708 if(!color.isValid()) return false;
83709
83710 color = Color.addOpacity(color, color.getAlpha());
83711 if(!colorMap[id]) colorMap[id] = color;
83712
83713 return color;
83714 };
83715}
83716
83717/*
83718 * `calc` filled in (and collated) explicit colors.
83719 * Now we need to propagate these explicit colors to other traces,
83720 * and fill in default colors.
83721 * This is done after sorting, so we pick defaults
83722 * in the order slices will be displayed
83723 */
83724function crossTraceCalc(gd, plotinfo) { // TODO: should we name the second argument opts?
83725 var desiredType = (plotinfo || {}).type;
83726 if(!desiredType) desiredType = 'pie';
83727
83728 var fullLayout = gd._fullLayout;
83729 var calcdata = gd.calcdata;
83730 var colorWay = fullLayout[desiredType + 'colorway'];
83731 var colorMap = fullLayout['_' + desiredType + 'colormap'];
83732
83733 if(fullLayout['extend' + desiredType + 'colors']) {
83734 colorWay = generateExtendedColors(colorWay, extendedColorWayList);
83735 }
83736 var dfltColorCount = 0;
83737
83738 for(var i = 0; i < calcdata.length; i++) {
83739 var cd = calcdata[i];
83740 var traceType = cd[0].trace.type;
83741 if(traceType !== desiredType) continue;
83742
83743 for(var j = 0; j < cd.length; j++) {
83744 var pt = cd[j];
83745 if(pt.color === false) {
83746 // have we seen this label and assigned a color to it in a previous trace?
83747 if(colorMap[pt.label]) {
83748 pt.color = colorMap[pt.label];
83749 } else {
83750 colorMap[pt.label] = pt.color = colorWay[dfltColorCount % colorWay.length];
83751 dfltColorCount++;
83752 }
83753 }
83754 }
83755 }
83756}
83757
83758/**
83759 * pick a default color from the main default set, augmented by
83760 * itself lighter then darker before repeating
83761 */
83762function generateExtendedColors(colorList, extendedColorWays) {
83763 var i;
83764 var colorString = JSON.stringify(colorList);
83765 var colors = extendedColorWays[colorString];
83766 if(!colors) {
83767 colors = colorList.slice();
83768
83769 for(i = 0; i < colorList.length; i++) {
83770 colors.push(tinycolor(colorList[i]).lighten(20).toHexString());
83771 }
83772
83773 for(i = 0; i < colorList.length; i++) {
83774 colors.push(tinycolor(colorList[i]).darken(20).toHexString());
83775 }
83776 extendedColorWays[colorString] = colors;
83777 }
83778
83779 return colors;
83780}
83781
83782module.exports = {
83783 calc: calc,
83784 crossTraceCalc: crossTraceCalc,
83785
83786 makePullColorFn: makePullColorFn,
83787 generateExtendedColors: generateExtendedColors
83788};
83789
83790},{"../../components/color":52,"fast-isnumeric":18,"tinycolor2":35}],379:[function(_dereq_,module,exports){
83791/**
83792* Copyright 2012-2020, Plotly, Inc.
83793* All rights reserved.
83794*
83795* This source code is licensed under the MIT license found in the
83796* LICENSE file in the root directory of this source tree.
83797*/
83798
83799'use strict';
83800
83801var isNumeric = _dereq_('fast-isnumeric');
83802var Lib = _dereq_('../../lib');
83803var attributes = _dereq_('./attributes');
83804var handleDomainDefaults = _dereq_('../../plots/domain').defaults;
83805var handleText = _dereq_('../bar/defaults').handleText;
83806
83807function handleLabelsAndValues(labels, values) {
83808 var hasLabels = Array.isArray(labels);
83809 var hasValues = Lib.isArrayOrTypedArray(values);
83810 var len = Math.min(
83811 hasLabels ? labels.length : Infinity,
83812 hasValues ? values.length : Infinity
83813 );
83814
83815 if(!isFinite(len)) len = 0;
83816
83817 if(len && hasValues) {
83818 var hasPositive;
83819 for(var i = 0; i < len; i++) {
83820 var v = values[i];
83821 if(isNumeric(v) && v > 0) {
83822 hasPositive = true;
83823 break;
83824 }
83825 }
83826 if(!hasPositive) len = 0;
83827 }
83828
83829 return {
83830 hasLabels: hasLabels,
83831 hasValues: hasValues,
83832 len: len
83833 };
83834}
83835
83836function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
83837 function coerce(attr, dflt) {
83838 return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
83839 }
83840
83841 var labels = coerce('labels');
83842 var values = coerce('values');
83843
83844 var res = handleLabelsAndValues(labels, values);
83845 var len = res.len;
83846 traceOut._hasLabels = res.hasLabels;
83847 traceOut._hasValues = res.hasValues;
83848
83849 if(!traceOut._hasLabels &&
83850 traceOut._hasValues
83851 ) {
83852 coerce('label0');
83853 coerce('dlabel');
83854 }
83855
83856 if(!len) {
83857 traceOut.visible = false;
83858 return;
83859 }
83860 traceOut._length = len;
83861
83862 var lineWidth = coerce('marker.line.width');
83863 if(lineWidth) coerce('marker.line.color');
83864
83865 coerce('marker.colors');
83866
83867 coerce('scalegroup');
83868 // TODO: hole needs to be coerced to the same value within a scaleegroup
83869
83870 var textData = coerce('text');
83871 var textTemplate = coerce('texttemplate');
83872 var textInfo;
83873 if(!textTemplate) textInfo = coerce('textinfo', Array.isArray(textData) ? 'text+percent' : 'percent');
83874
83875 coerce('hovertext');
83876 coerce('hovertemplate');
83877
83878 if(textTemplate || (textInfo && textInfo !== 'none')) {
83879 var textposition = coerce('textposition');
83880 handleText(traceIn, traceOut, layout, coerce, textposition, {
83881 moduleHasSelected: false,
83882 moduleHasUnselected: false,
83883 moduleHasConstrain: false,
83884 moduleHasCliponaxis: false,
83885 moduleHasTextangle: false,
83886 moduleHasInsideanchor: false
83887 });
83888
83889 var hasBoth = Array.isArray(textposition) || textposition === 'auto';
83890 var hasOutside = hasBoth || textposition === 'outside';
83891 if(hasOutside) {
83892 coerce('automargin');
83893 }
83894
83895 if(textposition === 'inside' || textposition === 'auto' || Array.isArray(textposition)) {
83896 coerce('insidetextorientation');
83897 }
83898 }
83899
83900 handleDomainDefaults(traceOut, layout, coerce);
83901
83902 var hole = coerce('hole');
83903 var title = coerce('title.text');
83904 if(title) {
83905 var titlePosition = coerce('title.position', hole ? 'middle center' : 'top center');
83906 if(!hole && titlePosition === 'middle center') traceOut.title.position = 'top center';
83907 Lib.coerceFont(coerce, 'title.font', layout.font);
83908 }
83909
83910 coerce('sort');
83911 coerce('direction');
83912 coerce('rotation');
83913 coerce('pull');
83914}
83915
83916module.exports = {
83917 handleLabelsAndValues: handleLabelsAndValues,
83918 supplyDefaults: supplyDefaults
83919};
83920
83921},{"../../lib":178,"../../plots/domain":249,"../bar/defaults":283,"./attributes":376,"fast-isnumeric":18}],380:[function(_dereq_,module,exports){
83922/**
83923* Copyright 2012-2020, Plotly, Inc.
83924* All rights reserved.
83925*
83926* This source code is licensed under the MIT license found in the
83927* LICENSE file in the root directory of this source tree.
83928*/
83929
83930'use strict';
83931
83932var appendArrayMultiPointValues = _dereq_('../../components/fx/helpers').appendArrayMultiPointValues;
83933
83934// Note: like other eventData routines, this creates the data for hover/unhover/click events
83935// but it has a different API and goes through a totally different pathway.
83936// So to ensure it doesn't get misused, it's not attached to the Pie module.
83937module.exports = function eventData(pt, trace) {
83938 var out = {
83939 curveNumber: trace.index,
83940 pointNumbers: pt.pts,
83941 data: trace._input,
83942 fullData: trace,
83943 label: pt.label,
83944 color: pt.color,
83945 value: pt.v,
83946 percent: pt.percent,
83947 text: pt.text,
83948
83949 // pt.v (and pt.i below) for backward compatibility
83950 v: pt.v
83951 };
83952
83953 // Only include pointNumber if it's unambiguous
83954 if(pt.pts.length === 1) out.pointNumber = out.i = pt.pts[0];
83955
83956 // Add extra data arrays to the output
83957 // notice that this is the multi-point version ('s' on the end!)
83958 // so added data will be arrays matching the pointNumbers array.
83959 appendArrayMultiPointValues(out, trace, pt.pts);
83960
83961 // don't include obsolete fields in new funnelarea traces
83962 if(trace.type === 'funnelarea') {
83963 delete out.v;
83964 delete out.i;
83965 }
83966
83967 return out;
83968};
83969
83970},{"../../components/fx/helpers":88}],381:[function(_dereq_,module,exports){
83971/**
83972* Copyright 2012-2020, Plotly, Inc.
83973* All rights reserved.
83974*
83975* This source code is licensed under the MIT license found in the
83976* LICENSE file in the root directory of this source tree.
83977*/
83978
83979'use strict';
83980
83981var Lib = _dereq_('../../lib');
83982
83983exports.formatPiePercent = function formatPiePercent(v, separators) {
83984 var vRounded = (v * 100).toPrecision(3);
83985 if(vRounded.lastIndexOf('.') !== -1) {
83986 vRounded = vRounded.replace(/[.]?0+$/, '');
83987 }
83988 return Lib.numSeparate(vRounded, separators) + '%';
83989};
83990
83991exports.formatPieValue = function formatPieValue(v, separators) {
83992 var vRounded = v.toPrecision(10);
83993 if(vRounded.lastIndexOf('.') !== -1) {
83994 vRounded = vRounded.replace(/[.]?0+$/, '');
83995 }
83996 return Lib.numSeparate(vRounded, separators);
83997};
83998
83999exports.getFirstFilled = function getFirstFilled(array, indices) {
84000 if(!Array.isArray(array)) return;
84001 for(var i = 0; i < indices.length; i++) {
84002 var v = array[indices[i]];
84003 if(v || v === 0 || v === '') return v;
84004 }
84005};
84006
84007exports.castOption = function castOption(item, indices) {
84008 if(Array.isArray(item)) return exports.getFirstFilled(item, indices);
84009 else if(item) return item;
84010};
84011
84012},{"../../lib":178}],382:[function(_dereq_,module,exports){
84013/**
84014* Copyright 2012-2020, Plotly, Inc.
84015* All rights reserved.
84016*
84017* This source code is licensed under the MIT license found in the
84018* LICENSE file in the root directory of this source tree.
84019*/
84020
84021'use strict';
84022
84023module.exports = {
84024 attributes: _dereq_('./attributes'),
84025 supplyDefaults: _dereq_('./defaults').supplyDefaults,
84026 supplyLayoutDefaults: _dereq_('./layout_defaults'),
84027 layoutAttributes: _dereq_('./layout_attributes'),
84028
84029 calc: _dereq_('./calc').calc,
84030 crossTraceCalc: _dereq_('./calc').crossTraceCalc,
84031
84032 plot: _dereq_('./plot').plot,
84033 style: _dereq_('./style'),
84034 styleOne: _dereq_('./style_one'),
84035
84036 moduleType: 'trace',
84037 name: 'pie',
84038 basePlotModule: _dereq_('./base_plot'),
84039 categories: ['pie-like', 'pie', 'showLegend'],
84040 meta: {
84041
84042 }
84043};
84044
84045},{"./attributes":376,"./base_plot":377,"./calc":378,"./defaults":379,"./layout_attributes":383,"./layout_defaults":384,"./plot":385,"./style":386,"./style_one":387}],383:[function(_dereq_,module,exports){
84046/**
84047* Copyright 2012-2020, Plotly, Inc.
84048* All rights reserved.
84049*
84050* This source code is licensed under the MIT license found in the
84051* LICENSE file in the root directory of this source tree.
84052*/
84053
84054'use strict';
84055
84056module.exports = {
84057 hiddenlabels: {
84058 valType: 'data_array',
84059
84060 editType: 'calc',
84061
84062 },
84063 piecolorway: {
84064 valType: 'colorlist',
84065
84066 editType: 'calc',
84067
84068 },
84069 extendpiecolors: {
84070 valType: 'boolean',
84071 dflt: true,
84072
84073 editType: 'calc',
84074
84075 }
84076};
84077
84078},{}],384:[function(_dereq_,module,exports){
84079/**
84080* Copyright 2012-2020, Plotly, Inc.
84081* All rights reserved.
84082*
84083* This source code is licensed under the MIT license found in the
84084* LICENSE file in the root directory of this source tree.
84085*/
84086
84087'use strict';
84088
84089var Lib = _dereq_('../../lib');
84090
84091var layoutAttributes = _dereq_('./layout_attributes');
84092
84093module.exports = function supplyLayoutDefaults(layoutIn, layoutOut) {
84094 function coerce(attr, dflt) {
84095 return Lib.coerce(layoutIn, layoutOut, layoutAttributes, attr, dflt);
84096 }
84097
84098 coerce('hiddenlabels');
84099 coerce('piecolorway', layoutOut.colorway);
84100 coerce('extendpiecolors');
84101};
84102
84103},{"../../lib":178,"./layout_attributes":383}],385:[function(_dereq_,module,exports){
84104/**
84105* Copyright 2012-2020, Plotly, Inc.
84106* All rights reserved.
84107*
84108* This source code is licensed under the MIT license found in the
84109* LICENSE file in the root directory of this source tree.
84110*/
84111
84112'use strict';
84113
84114var d3 = _dereq_('d3');
84115
84116var Plots = _dereq_('../../plots/plots');
84117var Fx = _dereq_('../../components/fx');
84118var Color = _dereq_('../../components/color');
84119var Drawing = _dereq_('../../components/drawing');
84120var Lib = _dereq_('../../lib');
84121var svgTextUtils = _dereq_('../../lib/svg_text_utils');
84122var uniformText = _dereq_('../bar/uniform_text');
84123var recordMinTextSize = uniformText.recordMinTextSize;
84124var clearMinTextSize = uniformText.clearMinTextSize;
84125var TEXTPAD = _dereq_('../bar/constants').TEXTPAD;
84126
84127var helpers = _dereq_('./helpers');
84128var eventData = _dereq_('./event_data');
84129var isValidTextValue = _dereq_('../../lib').isValidTextValue;
84130
84131function plot(gd, cdModule) {
84132 var fullLayout = gd._fullLayout;
84133 var gs = fullLayout._size;
84134
84135 clearMinTextSize('pie', fullLayout);
84136
84137 prerenderTitles(cdModule, gd);
84138 layoutAreas(cdModule, gs);
84139
84140 var plotGroups = Lib.makeTraceGroups(fullLayout._pielayer, cdModule, 'trace').each(function(cd) {
84141 var plotGroup = d3.select(this);
84142 var cd0 = cd[0];
84143 var trace = cd0.trace;
84144
84145 setCoords(cd);
84146
84147 // TODO: miter might look better but can sometimes cause problems
84148 // maybe miter with a small-ish stroke-miterlimit?
84149 plotGroup.attr('stroke-linejoin', 'round');
84150
84151 plotGroup.each(function() {
84152 var slices = d3.select(this).selectAll('g.slice').data(cd);
84153
84154 slices.enter().append('g')
84155 .classed('slice', true);
84156 slices.exit().remove();
84157
84158 var quadrants = [
84159 [[], []], // y<0: x<0, x>=0
84160 [[], []] // y>=0: x<0, x>=0
84161 ];
84162 var hasOutsideText = false;
84163
84164 slices.each(function(pt, i) {
84165 if(pt.hidden) {
84166 d3.select(this).selectAll('path,g').remove();
84167 return;
84168 }
84169
84170 // to have consistent event data compared to other traces
84171 pt.pointNumber = pt.i;
84172 pt.curveNumber = trace.index;
84173
84174 quadrants[pt.pxmid[1] < 0 ? 0 : 1][pt.pxmid[0] < 0 ? 0 : 1].push(pt);
84175
84176 var cx = cd0.cx;
84177 var cy = cd0.cy;
84178 var sliceTop = d3.select(this);
84179 var slicePath = sliceTop.selectAll('path.surface').data([pt]);
84180
84181 slicePath.enter().append('path')
84182 .classed('surface', true)
84183 .style({'pointer-events': 'all'});
84184
84185 sliceTop.call(attachFxHandlers, gd, cd);
84186
84187 if(trace.pull) {
84188 var pull = +helpers.castOption(trace.pull, pt.pts) || 0;
84189 if(pull > 0) {
84190 cx += pull * pt.pxmid[0];
84191 cy += pull * pt.pxmid[1];
84192 }
84193 }
84194
84195 pt.cxFinal = cx;
84196 pt.cyFinal = cy;
84197
84198 function arc(start, finish, cw, scale) {
84199 var dx = scale * (finish[0] - start[0]);
84200 var dy = scale * (finish[1] - start[1]);
84201
84202 return 'a' +
84203 (scale * cd0.r) + ',' + (scale * cd0.r) + ' 0 ' +
84204 pt.largeArc + (cw ? ' 1 ' : ' 0 ') + dx + ',' + dy;
84205 }
84206
84207 var hole = trace.hole;
84208 if(pt.v === cd0.vTotal) { // 100% fails bcs arc start and end are identical
84209 var outerCircle = 'M' + (cx + pt.px0[0]) + ',' + (cy + pt.px0[1]) +
84210 arc(pt.px0, pt.pxmid, true, 1) +
84211 arc(pt.pxmid, pt.px0, true, 1) + 'Z';
84212 if(hole) {
84213 slicePath.attr('d',
84214 'M' + (cx + hole * pt.px0[0]) + ',' + (cy + hole * pt.px0[1]) +
84215 arc(pt.px0, pt.pxmid, false, hole) +
84216 arc(pt.pxmid, pt.px0, false, hole) +
84217 'Z' + outerCircle);
84218 } else slicePath.attr('d', outerCircle);
84219 } else {
84220 var outerArc = arc(pt.px0, pt.px1, true, 1);
84221
84222 if(hole) {
84223 var rim = 1 - hole;
84224 slicePath.attr('d',
84225 'M' + (cx + hole * pt.px1[0]) + ',' + (cy + hole * pt.px1[1]) +
84226 arc(pt.px1, pt.px0, false, hole) +
84227 'l' + (rim * pt.px0[0]) + ',' + (rim * pt.px0[1]) +
84228 outerArc +
84229 'Z');
84230 } else {
84231 slicePath.attr('d',
84232 'M' + cx + ',' + cy +
84233 'l' + pt.px0[0] + ',' + pt.px0[1] +
84234 outerArc +
84235 'Z');
84236 }
84237 }
84238
84239 // add text
84240 formatSliceLabel(gd, pt, cd0);
84241 var textPosition = helpers.castOption(trace.textposition, pt.pts);
84242 var sliceTextGroup = sliceTop.selectAll('g.slicetext')
84243 .data(pt.text && (textPosition !== 'none') ? [0] : []);
84244
84245 sliceTextGroup.enter().append('g')
84246 .classed('slicetext', true);
84247 sliceTextGroup.exit().remove();
84248
84249 sliceTextGroup.each(function() {
84250 var sliceText = Lib.ensureSingle(d3.select(this), 'text', '', function(s) {
84251 // prohibit tex interpretation until we can handle
84252 // tex and regular text together
84253 s.attr('data-notex', 1);
84254 });
84255
84256 var font = Lib.ensureUniformFontSize(gd, textPosition === 'outside' ?
84257 determineOutsideTextFont(trace, pt, fullLayout.font) :
84258 determineInsideTextFont(trace, pt, fullLayout.font)
84259 );
84260
84261 sliceText.text(pt.text)
84262 .attr({
84263 'class': 'slicetext',
84264 transform: '',
84265 'text-anchor': 'middle'
84266 })
84267 .call(Drawing.font, font)
84268 .call(svgTextUtils.convertToTspans, gd);
84269
84270 // position the text relative to the slice
84271 var textBB = Drawing.bBox(sliceText.node());
84272 var transform;
84273
84274 if(textPosition === 'outside') {
84275 transform = transformOutsideText(textBB, pt);
84276 } else {
84277 transform = transformInsideText(textBB, pt, cd0);
84278 if(textPosition === 'auto' && transform.scale < 1) {
84279 var newFont = Lib.ensureUniformFontSize(gd, trace.outsidetextfont);
84280
84281 sliceText.call(Drawing.font, newFont);
84282 textBB = Drawing.bBox(sliceText.node());
84283
84284 transform = transformOutsideText(textBB, pt);
84285 }
84286 }
84287
84288 var textPosAngle = transform.textPosAngle;
84289 var textXY = textPosAngle === undefined ? pt.pxmid : getCoords(cd0.r, textPosAngle);
84290 transform.targetX = cx + textXY[0] * transform.rCenter + (transform.x || 0);
84291 transform.targetY = cy + textXY[1] * transform.rCenter + (transform.y || 0);
84292 computeTransform(transform, textBB);
84293
84294 // save some stuff to use later ensure no labels overlap
84295 if(transform.outside) {
84296 var targetY = transform.targetY;
84297 pt.yLabelMin = targetY - textBB.height / 2;
84298 pt.yLabelMid = targetY;
84299 pt.yLabelMax = targetY + textBB.height / 2;
84300 pt.labelExtraX = 0;
84301 pt.labelExtraY = 0;
84302 hasOutsideText = true;
84303 }
84304
84305 transform.fontSize = font.size;
84306 recordMinTextSize(trace.type, transform, fullLayout);
84307 cd[i].transform = transform;
84308
84309 sliceText.attr('transform', Lib.getTextTransform(transform));
84310 });
84311 });
84312
84313 // add the title
84314 var titleTextGroup = d3.select(this).selectAll('g.titletext')
84315 .data(trace.title.text ? [0] : []);
84316
84317 titleTextGroup.enter().append('g')
84318 .classed('titletext', true);
84319 titleTextGroup.exit().remove();
84320
84321 titleTextGroup.each(function() {
84322 var titleText = Lib.ensureSingle(d3.select(this), 'text', '', function(s) {
84323 // prohibit tex interpretation as above
84324 s.attr('data-notex', 1);
84325 });
84326
84327 var txt = trace.title.text;
84328 if(trace._meta) {
84329 txt = Lib.templateString(txt, trace._meta);
84330 }
84331
84332 titleText.text(txt)
84333 .attr({
84334 'class': 'titletext',
84335 transform: '',
84336 'text-anchor': 'middle',
84337 })
84338 .call(Drawing.font, trace.title.font)
84339 .call(svgTextUtils.convertToTspans, gd);
84340
84341 var transform;
84342
84343 if(trace.title.position === 'middle center') {
84344 transform = positionTitleInside(cd0);
84345 } else {
84346 transform = positionTitleOutside(cd0, gs);
84347 }
84348
84349 titleText.attr('transform',
84350 'translate(' + transform.x + ',' + transform.y + ')' +
84351 (transform.scale < 1 ? ('scale(' + transform.scale + ')') : '') +
84352 'translate(' + transform.tx + ',' + transform.ty + ')');
84353 });
84354
84355 // now make sure no labels overlap (at least within one pie)
84356 if(hasOutsideText) scootLabels(quadrants, trace);
84357
84358 plotTextLines(slices, trace);
84359
84360 if(hasOutsideText && trace.automargin) {
84361 // TODO if we ever want to improve perf,
84362 // we could reuse the textBB computed above together
84363 // with the sliceText transform info
84364 var traceBbox = Drawing.bBox(plotGroup.node());
84365
84366 var domain = trace.domain;
84367 var vpw = gs.w * (domain.x[1] - domain.x[0]);
84368 var vph = gs.h * (domain.y[1] - domain.y[0]);
84369 var xgap = (0.5 * vpw - cd0.r) / gs.w;
84370 var ygap = (0.5 * vph - cd0.r) / gs.h;
84371
84372 Plots.autoMargin(gd, 'pie.' + trace.uid + '.automargin', {
84373 xl: domain.x[0] - xgap,
84374 xr: domain.x[1] + xgap,
84375 yb: domain.y[0] - ygap,
84376 yt: domain.y[1] + ygap,
84377 l: Math.max(cd0.cx - cd0.r - traceBbox.left, 0),
84378 r: Math.max(traceBbox.right - (cd0.cx + cd0.r), 0),
84379 b: Math.max(traceBbox.bottom - (cd0.cy + cd0.r), 0),
84380 t: Math.max(cd0.cy - cd0.r - traceBbox.top, 0),
84381 pad: 5
84382 });
84383 }
84384 });
84385 });
84386
84387 // This is for a bug in Chrome (as of 2015-07-22, and does not affect FF)
84388 // if insidetextfont and outsidetextfont are different sizes, sometimes the size
84389 // of an "em" gets taken from the wrong element at first so lines are
84390 // spaced wrong. You just have to tell it to try again later and it gets fixed.
84391 // I have no idea why we haven't seen this in other contexts. Also, sometimes
84392 // it gets the initial draw correct but on redraw it gets confused.
84393 setTimeout(function() {
84394 plotGroups.selectAll('tspan').each(function() {
84395 var s = d3.select(this);
84396 if(s.attr('dy')) s.attr('dy', s.attr('dy'));
84397 });
84398 }, 0);
84399}
84400
84401// TODO add support for transition
84402function plotTextLines(slices, trace) {
84403 slices.each(function(pt) {
84404 var sliceTop = d3.select(this);
84405
84406 if(!pt.labelExtraX && !pt.labelExtraY) {
84407 sliceTop.select('path.textline').remove();
84408 return;
84409 }
84410
84411 // first move the text to its new location
84412 var sliceText = sliceTop.select('g.slicetext text');
84413
84414 pt.transform.targetX += pt.labelExtraX;
84415 pt.transform.targetY += pt.labelExtraY;
84416
84417 sliceText.attr('transform', Lib.getTextTransform(pt.transform));
84418
84419 // then add a line to the new location
84420 var lineStartX = pt.cxFinal + pt.pxmid[0];
84421 var lineStartY = pt.cyFinal + pt.pxmid[1];
84422 var textLinePath = 'M' + lineStartX + ',' + lineStartY;
84423 var finalX = (pt.yLabelMax - pt.yLabelMin) * (pt.pxmid[0] < 0 ? -1 : 1) / 4;
84424
84425 if(pt.labelExtraX) {
84426 var yFromX = pt.labelExtraX * pt.pxmid[1] / pt.pxmid[0];
84427 var yNet = pt.yLabelMid + pt.labelExtraY - (pt.cyFinal + pt.pxmid[1]);
84428
84429 if(Math.abs(yFromX) > Math.abs(yNet)) {
84430 textLinePath +=
84431 'l' + (yNet * pt.pxmid[0] / pt.pxmid[1]) + ',' + yNet +
84432 'H' + (lineStartX + pt.labelExtraX + finalX);
84433 } else {
84434 textLinePath += 'l' + pt.labelExtraX + ',' + yFromX +
84435 'v' + (yNet - yFromX) +
84436 'h' + finalX;
84437 }
84438 } else {
84439 textLinePath +=
84440 'V' + (pt.yLabelMid + pt.labelExtraY) +
84441 'h' + finalX;
84442 }
84443
84444 Lib.ensureSingle(sliceTop, 'path', 'textline')
84445 .call(Color.stroke, trace.outsidetextfont.color)
84446 .attr({
84447 'stroke-width': Math.min(2, trace.outsidetextfont.size / 8),
84448 d: textLinePath,
84449 fill: 'none'
84450 });
84451 });
84452}
84453
84454function attachFxHandlers(sliceTop, gd, cd) {
84455 var cd0 = cd[0];
84456 var trace = cd0.trace;
84457 var cx = cd0.cx;
84458 var cy = cd0.cy;
84459
84460 // hover state vars
84461 // have we drawn a hover label, so it should be cleared later
84462 if(!('_hasHoverLabel' in trace)) trace._hasHoverLabel = false;
84463 // have we emitted a hover event, so later an unhover event should be emitted
84464 // note that click events do not depend on this - you can still get them
84465 // with hovermode: false or if you were earlier dragging, then clicked
84466 // in the same slice that you moused up in
84467 if(!('_hasHoverEvent' in trace)) trace._hasHoverEvent = false;
84468
84469 sliceTop.on('mouseover', function(pt) {
84470 // in case fullLayout or fullData has changed without a replot
84471 var fullLayout2 = gd._fullLayout;
84472 var trace2 = gd._fullData[trace.index];
84473
84474 if(gd._dragging || fullLayout2.hovermode === false) return;
84475
84476 var hoverinfo = trace2.hoverinfo;
84477 if(Array.isArray(hoverinfo)) {
84478 // super hacky: we need to pull out the *first* hoverinfo from
84479 // pt.pts, then put it back into an array in a dummy trace
84480 // and call castHoverinfo on that.
84481 // TODO: do we want to have Fx.castHoverinfo somehow handle this?
84482 // it already takes an array for index, for 2D, so this seems tricky.
84483 hoverinfo = Fx.castHoverinfo({
84484 hoverinfo: [helpers.castOption(hoverinfo, pt.pts)],
84485 _module: trace._module
84486 }, fullLayout2, 0);
84487 }
84488
84489 if(hoverinfo === 'all') hoverinfo = 'label+text+value+percent+name';
84490
84491 // in case we dragged over the pie from another subplot,
84492 // or if hover is turned off
84493 if(trace2.hovertemplate || (hoverinfo !== 'none' && hoverinfo !== 'skip' && hoverinfo)) {
84494 var rInscribed = pt.rInscribed || 0;
84495 var hoverCenterX = cx + pt.pxmid[0] * (1 - rInscribed);
84496 var hoverCenterY = cy + pt.pxmid[1] * (1 - rInscribed);
84497 var separators = fullLayout2.separators;
84498 var text = [];
84499
84500 if(hoverinfo && hoverinfo.indexOf('label') !== -1) text.push(pt.label);
84501 pt.text = helpers.castOption(trace2.hovertext || trace2.text, pt.pts);
84502 if(hoverinfo && hoverinfo.indexOf('text') !== -1) {
84503 var tx = pt.text;
84504 if(Lib.isValidTextValue(tx)) text.push(tx);
84505 }
84506 pt.value = pt.v;
84507 pt.valueLabel = helpers.formatPieValue(pt.v, separators);
84508 if(hoverinfo && hoverinfo.indexOf('value') !== -1) text.push(pt.valueLabel);
84509 pt.percent = pt.v / cd0.vTotal;
84510 pt.percentLabel = helpers.formatPiePercent(pt.percent, separators);
84511 if(hoverinfo && hoverinfo.indexOf('percent') !== -1) text.push(pt.percentLabel);
84512
84513 var hoverLabel = trace2.hoverlabel;
84514 var hoverFont = hoverLabel.font;
84515
84516 Fx.loneHover({
84517 trace: trace,
84518 x0: hoverCenterX - rInscribed * cd0.r,
84519 x1: hoverCenterX + rInscribed * cd0.r,
84520 y: hoverCenterY,
84521 text: text.join('<br>'),
84522 name: (trace2.hovertemplate || hoverinfo.indexOf('name') !== -1) ? trace2.name : undefined,
84523 idealAlign: pt.pxmid[0] < 0 ? 'left' : 'right',
84524 color: helpers.castOption(hoverLabel.bgcolor, pt.pts) || pt.color,
84525 borderColor: helpers.castOption(hoverLabel.bordercolor, pt.pts),
84526 fontFamily: helpers.castOption(hoverFont.family, pt.pts),
84527 fontSize: helpers.castOption(hoverFont.size, pt.pts),
84528 fontColor: helpers.castOption(hoverFont.color, pt.pts),
84529 nameLength: helpers.castOption(hoverLabel.namelength, pt.pts),
84530 textAlign: helpers.castOption(hoverLabel.align, pt.pts),
84531 hovertemplate: helpers.castOption(trace2.hovertemplate, pt.pts),
84532 hovertemplateLabels: pt,
84533 eventData: [eventData(pt, trace2)]
84534 }, {
84535 container: fullLayout2._hoverlayer.node(),
84536 outerContainer: fullLayout2._paper.node(),
84537 gd: gd
84538 });
84539
84540 trace._hasHoverLabel = true;
84541 }
84542
84543 trace._hasHoverEvent = true;
84544 gd.emit('plotly_hover', {
84545 points: [eventData(pt, trace2)],
84546 event: d3.event
84547 });
84548 });
84549
84550 sliceTop.on('mouseout', function(evt) {
84551 var fullLayout2 = gd._fullLayout;
84552 var trace2 = gd._fullData[trace.index];
84553 var pt = d3.select(this).datum();
84554
84555 if(trace._hasHoverEvent) {
84556 evt.originalEvent = d3.event;
84557 gd.emit('plotly_unhover', {
84558 points: [eventData(pt, trace2)],
84559 event: d3.event
84560 });
84561 trace._hasHoverEvent = false;
84562 }
84563
84564 if(trace._hasHoverLabel) {
84565 Fx.loneUnhover(fullLayout2._hoverlayer.node());
84566 trace._hasHoverLabel = false;
84567 }
84568 });
84569
84570 sliceTop.on('click', function(pt) {
84571 // TODO: this does not support right-click. If we want to support it, we
84572 // would likely need to change pie to use dragElement instead of straight
84573 // mapbox event binding. Or perhaps better, make a simple wrapper with the
84574 // right mousedown, mousemove, and mouseup handlers just for a left/right click
84575 // mapbox would use this too.
84576 var fullLayout2 = gd._fullLayout;
84577 var trace2 = gd._fullData[trace.index];
84578
84579 if(gd._dragging || fullLayout2.hovermode === false) return;
84580
84581 gd._hoverdata = [eventData(pt, trace2)];
84582 Fx.click(gd, d3.event);
84583 });
84584}
84585
84586function determineOutsideTextFont(trace, pt, layoutFont) {
84587 var color =
84588 helpers.castOption(trace.outsidetextfont.color, pt.pts) ||
84589 helpers.castOption(trace.textfont.color, pt.pts) ||
84590 layoutFont.color;
84591
84592 var family =
84593 helpers.castOption(trace.outsidetextfont.family, pt.pts) ||
84594 helpers.castOption(trace.textfont.family, pt.pts) ||
84595 layoutFont.family;
84596
84597 var size =
84598 helpers.castOption(trace.outsidetextfont.size, pt.pts) ||
84599 helpers.castOption(trace.textfont.size, pt.pts) ||
84600 layoutFont.size;
84601
84602 return {
84603 color: color,
84604 family: family,
84605 size: size
84606 };
84607}
84608
84609function determineInsideTextFont(trace, pt, layoutFont) {
84610 var customColor = helpers.castOption(trace.insidetextfont.color, pt.pts);
84611 if(!customColor && trace._input.textfont) {
84612 // Why not simply using trace.textfont? Because if not set, it
84613 // defaults to layout.font which has a default color. But if
84614 // textfont.color and insidetextfont.color don't supply a value,
84615 // a contrasting color shall be used.
84616 customColor = helpers.castOption(trace._input.textfont.color, pt.pts);
84617 }
84618
84619 var family =
84620 helpers.castOption(trace.insidetextfont.family, pt.pts) ||
84621 helpers.castOption(trace.textfont.family, pt.pts) ||
84622 layoutFont.family;
84623
84624 var size =
84625 helpers.castOption(trace.insidetextfont.size, pt.pts) ||
84626 helpers.castOption(trace.textfont.size, pt.pts) ||
84627 layoutFont.size;
84628
84629 return {
84630 color: customColor || Color.contrast(pt.color),
84631 family: family,
84632 size: size
84633 };
84634}
84635
84636function prerenderTitles(cdModule, gd) {
84637 var cd0, trace;
84638
84639 // Determine the width and height of the title for each pie.
84640 for(var i = 0; i < cdModule.length; i++) {
84641 cd0 = cdModule[i][0];
84642 trace = cd0.trace;
84643
84644 if(trace.title.text) {
84645 var txt = trace.title.text;
84646 if(trace._meta) {
84647 txt = Lib.templateString(txt, trace._meta);
84648 }
84649
84650 var dummyTitle = Drawing.tester.append('text')
84651 .attr('data-notex', 1)
84652 .text(txt)
84653 .call(Drawing.font, trace.title.font)
84654 .call(svgTextUtils.convertToTspans, gd);
84655 var bBox = Drawing.bBox(dummyTitle.node(), true);
84656 cd0.titleBox = {
84657 width: bBox.width,
84658 height: bBox.height,
84659 };
84660 dummyTitle.remove();
84661 }
84662 }
84663}
84664
84665function transformInsideText(textBB, pt, cd0) {
84666 var r = cd0.r || pt.rpx1;
84667 var rInscribed = pt.rInscribed;
84668
84669 var isEmpty = pt.startangle === pt.stopangle;
84670 if(isEmpty) {
84671 return {
84672 rCenter: 1 - rInscribed,
84673 scale: 0,
84674 rotate: 0,
84675 textPosAngle: 0
84676 };
84677 }
84678
84679 var ring = pt.ring;
84680 var isCircle = (ring === 1) && (Math.abs(pt.startangle - pt.stopangle) === Math.PI * 2);
84681
84682 var halfAngle = pt.halfangle;
84683 var midAngle = pt.midangle;
84684
84685 var orientation = cd0.trace.insidetextorientation;
84686 var isHorizontal = orientation === 'horizontal';
84687 var isTangential = orientation === 'tangential';
84688 var isRadial = orientation === 'radial';
84689 var isAuto = orientation === 'auto';
84690
84691 var allTransforms = [];
84692 var newT;
84693
84694 if(!isAuto) {
84695 // max size if text is placed (horizontally) at the top or bottom of the arc
84696
84697 var considerCrossing = function(angle, key) {
84698 if(isCrossing(pt, angle)) {
84699 var dStart = Math.abs(angle - pt.startangle);
84700 var dStop = Math.abs(angle - pt.stopangle);
84701
84702 var closestEdge = dStart < dStop ? dStart : dStop;
84703
84704 if(key === 'tan') {
84705 newT = calcTanTransform(textBB, r, ring, closestEdge, 0);
84706 } else { // case of 'rad'
84707 newT = calcRadTransform(textBB, r, ring, closestEdge, Math.PI / 2);
84708 }
84709 newT.textPosAngle = angle;
84710
84711 allTransforms.push(newT);
84712 }
84713 };
84714
84715 // to cover all cases with trace.rotation added
84716 var i;
84717 if(isHorizontal || isTangential) {
84718 // top
84719 for(i = 4; i >= -4; i -= 2) considerCrossing(Math.PI * i, 'tan');
84720 // bottom
84721 for(i = 4; i >= -4; i -= 2) considerCrossing(Math.PI * (i + 1), 'tan');
84722 }
84723 if(isHorizontal || isRadial) {
84724 // left
84725 for(i = 4; i >= -4; i -= 2) considerCrossing(Math.PI * (i + 1.5), 'rad');
84726 // right
84727 for(i = 4; i >= -4; i -= 2) considerCrossing(Math.PI * (i + 0.5), 'rad');
84728 }
84729 }
84730
84731 if(isCircle || isAuto || isHorizontal) {
84732 // max size text can be inserted inside without rotating it
84733 // this inscribes the text rectangle in a circle, which is then inscribed
84734 // in the slice, so it will be an underestimate, which some day we may want
84735 // to improve so this case can get more use
84736 var textDiameter = Math.sqrt(textBB.width * textBB.width + textBB.height * textBB.height);
84737
84738 newT = {
84739 scale: rInscribed * r * 2 / textDiameter,
84740
84741 // and the center position and rotation in this case
84742 rCenter: 1 - rInscribed,
84743 rotate: 0
84744 };
84745
84746 newT.textPosAngle = (pt.startangle + pt.stopangle) / 2;
84747 if(newT.scale >= 1) return newT;
84748
84749 allTransforms.push(newT);
84750 }
84751
84752 if(isAuto || isRadial) {
84753 newT = calcRadTransform(textBB, r, ring, halfAngle, midAngle);
84754 newT.textPosAngle = (pt.startangle + pt.stopangle) / 2;
84755 allTransforms.push(newT);
84756 }
84757
84758 if(isAuto || isTangential) {
84759 newT = calcTanTransform(textBB, r, ring, halfAngle, midAngle);
84760 newT.textPosAngle = (pt.startangle + pt.stopangle) / 2;
84761 allTransforms.push(newT);
84762 }
84763
84764 var id = 0;
84765 var maxScale = 0;
84766 for(var k = 0; k < allTransforms.length; k++) {
84767 var s = allTransforms[k].scale;
84768 if(maxScale < s) {
84769 maxScale = s;
84770 id = k;
84771 }
84772
84773 if(!isAuto && maxScale >= 1) {
84774 // respect test order for non-auto options
84775 break;
84776 }
84777 }
84778 return allTransforms[id];
84779}
84780
84781function isCrossing(pt, angle) {
84782 var start = pt.startangle;
84783 var stop = pt.stopangle;
84784 return (
84785 (start > angle && angle > stop) ||
84786 (start < angle && angle < stop)
84787 );
84788}
84789
84790function calcRadTransform(textBB, r, ring, halfAngle, midAngle) {
84791 r = Math.max(0, r - 2 * TEXTPAD);
84792
84793 // max size if text is rotated radially
84794 var a = textBB.width / textBB.height;
84795 var s = calcMaxHalfSize(a, halfAngle, r, ring);
84796 return {
84797 scale: s * 2 / textBB.height,
84798 rCenter: calcRCenter(a, s / r),
84799 rotate: calcRotate(midAngle)
84800 };
84801}
84802
84803function calcTanTransform(textBB, r, ring, halfAngle, midAngle) {
84804 r = Math.max(0, r - 2 * TEXTPAD);
84805
84806 // max size if text is rotated tangentially
84807 var a = textBB.height / textBB.width;
84808 var s = calcMaxHalfSize(a, halfAngle, r, ring);
84809 return {
84810 scale: s * 2 / textBB.width,
84811 rCenter: calcRCenter(a, s / r),
84812 rotate: calcRotate(midAngle + Math.PI / 2)
84813 };
84814}
84815
84816function calcRCenter(a, b) {
84817 return Math.cos(b) - a * b;
84818}
84819
84820function calcRotate(t) {
84821 return (180 / Math.PI * t + 720) % 180 - 90;
84822}
84823
84824function calcMaxHalfSize(a, halfAngle, r, ring) {
84825 var q = a + 1 / (2 * Math.tan(halfAngle));
84826 return r * Math.min(
84827 1 / (Math.sqrt(q * q + 0.5) + q),
84828 ring / (Math.sqrt(a * a + ring / 2) + a)
84829 );
84830}
84831
84832function getInscribedRadiusFraction(pt, cd0) {
84833 if(pt.v === cd0.vTotal && !cd0.trace.hole) return 1;// special case of 100% with no hole
84834
84835 return Math.min(1 / (1 + 1 / Math.sin(pt.halfangle)), pt.ring / 2);
84836}
84837
84838function transformOutsideText(textBB, pt) {
84839 var x = pt.pxmid[0];
84840 var y = pt.pxmid[1];
84841 var dx = textBB.width / 2;
84842 var dy = textBB.height / 2;
84843
84844 if(x < 0) dx *= -1;
84845 if(y < 0) dy *= -1;
84846
84847 return {
84848 scale: 1,
84849 rCenter: 1,
84850 rotate: 0,
84851 x: dx + Math.abs(dy) * (dx > 0 ? 1 : -1) / 2,
84852 y: dy / (1 + x * x / (y * y)),
84853 outside: true
84854 };
84855}
84856
84857function positionTitleInside(cd0) {
84858 var textDiameter =
84859 Math.sqrt(cd0.titleBox.width * cd0.titleBox.width + cd0.titleBox.height * cd0.titleBox.height);
84860 return {
84861 x: cd0.cx,
84862 y: cd0.cy,
84863 scale: cd0.trace.hole * cd0.r * 2 / textDiameter,
84864 tx: 0,
84865 ty: - cd0.titleBox.height / 2 + cd0.trace.title.font.size
84866 };
84867}
84868
84869function positionTitleOutside(cd0, plotSize) {
84870 var scaleX = 1;
84871 var scaleY = 1;
84872 var maxPull;
84873
84874 var trace = cd0.trace;
84875 // position of the baseline point of the text box in the plot, before scaling.
84876 // we anchored the text in the middle, so the baseline is on the bottom middle
84877 // of the first line of text.
84878 var topMiddle = {
84879 x: cd0.cx,
84880 y: cd0.cy
84881 };
84882 // relative translation of the text box after scaling
84883 var translate = {
84884 tx: 0,
84885 ty: 0
84886 };
84887
84888 // we reason below as if the baseline is the top middle point of the text box.
84889 // so we must add the font size to approximate the y-coord. of the top.
84890 // note that this correction must happen after scaling.
84891 translate.ty += trace.title.font.size;
84892 maxPull = getMaxPull(trace);
84893
84894 if(trace.title.position.indexOf('top') !== -1) {
84895 topMiddle.y -= (1 + maxPull) * cd0.r;
84896 translate.ty -= cd0.titleBox.height;
84897 } else if(trace.title.position.indexOf('bottom') !== -1) {
84898 topMiddle.y += (1 + maxPull) * cd0.r;
84899 }
84900
84901 var rx = applyAspectRatio(cd0.r, cd0.trace.aspectratio);
84902
84903 var maxWidth = plotSize.w * (trace.domain.x[1] - trace.domain.x[0]) / 2;
84904 if(trace.title.position.indexOf('left') !== -1) {
84905 // we start the text at the left edge of the pie
84906 maxWidth = maxWidth + rx;
84907 topMiddle.x -= (1 + maxPull) * rx;
84908 translate.tx += cd0.titleBox.width / 2;
84909 } else if(trace.title.position.indexOf('center') !== -1) {
84910 maxWidth *= 2;
84911 } else if(trace.title.position.indexOf('right') !== -1) {
84912 maxWidth = maxWidth + rx;
84913 topMiddle.x += (1 + maxPull) * rx;
84914 translate.tx -= cd0.titleBox.width / 2;
84915 }
84916 scaleX = maxWidth / cd0.titleBox.width;
84917 scaleY = getTitleSpace(cd0, plotSize) / cd0.titleBox.height;
84918 return {
84919 x: topMiddle.x,
84920 y: topMiddle.y,
84921 scale: Math.min(scaleX, scaleY),
84922 tx: translate.tx,
84923 ty: translate.ty
84924 };
84925}
84926
84927function applyAspectRatio(x, aspectratio) {
84928 return x / ((aspectratio === undefined) ? 1 : aspectratio);
84929}
84930
84931function getTitleSpace(cd0, plotSize) {
84932 var trace = cd0.trace;
84933 var pieBoxHeight = plotSize.h * (trace.domain.y[1] - trace.domain.y[0]);
84934 // use at most half of the plot for the title
84935 return Math.min(cd0.titleBox.height, pieBoxHeight / 2);
84936}
84937
84938function getMaxPull(trace) {
84939 var maxPull = trace.pull;
84940 if(!maxPull) return 0;
84941
84942 var j;
84943 if(Array.isArray(maxPull)) {
84944 maxPull = 0;
84945 for(j = 0; j < trace.pull.length; j++) {
84946 if(trace.pull[j] > maxPull) maxPull = trace.pull[j];
84947 }
84948 }
84949 return maxPull;
84950}
84951
84952function scootLabels(quadrants, trace) {
84953 var xHalf, yHalf, equatorFirst, farthestX, farthestY,
84954 xDiffSign, yDiffSign, thisQuad, oppositeQuad,
84955 wholeSide, i, thisQuadOutside, firstOppositeOutsidePt;
84956
84957 function topFirst(a, b) { return a.pxmid[1] - b.pxmid[1]; }
84958 function bottomFirst(a, b) { return b.pxmid[1] - a.pxmid[1]; }
84959
84960 function scootOneLabel(thisPt, prevPt) {
84961 if(!prevPt) prevPt = {};
84962
84963 var prevOuterY = prevPt.labelExtraY + (yHalf ? prevPt.yLabelMax : prevPt.yLabelMin);
84964 var thisInnerY = yHalf ? thisPt.yLabelMin : thisPt.yLabelMax;
84965 var thisOuterY = yHalf ? thisPt.yLabelMax : thisPt.yLabelMin;
84966 var thisSliceOuterY = thisPt.cyFinal + farthestY(thisPt.px0[1], thisPt.px1[1]);
84967 var newExtraY = prevOuterY - thisInnerY;
84968
84969 var xBuffer, i, otherPt, otherOuterY, otherOuterX, newExtraX;
84970
84971 // make sure this label doesn't overlap other labels
84972 // this *only* has us move these labels vertically
84973 if(newExtraY * yDiffSign > 0) thisPt.labelExtraY = newExtraY;
84974
84975 // make sure this label doesn't overlap any slices
84976 if(!Array.isArray(trace.pull)) return; // this can only happen with array pulls
84977
84978 for(i = 0; i < wholeSide.length; i++) {
84979 otherPt = wholeSide[i];
84980
84981 // overlap can only happen if the other point is pulled more than this one
84982 if(otherPt === thisPt || (
84983 (helpers.castOption(trace.pull, thisPt.pts) || 0) >=
84984 (helpers.castOption(trace.pull, otherPt.pts) || 0))
84985 ) {
84986 continue;
84987 }
84988
84989 if((thisPt.pxmid[1] - otherPt.pxmid[1]) * yDiffSign > 0) {
84990 // closer to the equator - by construction all of these happen first
84991 // move the text vertically to get away from these slices
84992 otherOuterY = otherPt.cyFinal + farthestY(otherPt.px0[1], otherPt.px1[1]);
84993 newExtraY = otherOuterY - thisInnerY - thisPt.labelExtraY;
84994
84995 if(newExtraY * yDiffSign > 0) thisPt.labelExtraY += newExtraY;
84996 } else if((thisOuterY + thisPt.labelExtraY - thisSliceOuterY) * yDiffSign > 0) {
84997 // farther from the equator - happens after we've done all the
84998 // vertical moving we're going to do
84999 // move horizontally to get away from these more polar slices
85000
85001 // if we're moving horz. based on a slice that's several slices away from this one
85002 // then we need some extra space for the lines to labels between them
85003 xBuffer = 3 * xDiffSign * Math.abs(i - wholeSide.indexOf(thisPt));
85004
85005 otherOuterX = otherPt.cxFinal + farthestX(otherPt.px0[0], otherPt.px1[0]);
85006 newExtraX = otherOuterX + xBuffer - (thisPt.cxFinal + thisPt.pxmid[0]) - thisPt.labelExtraX;
85007
85008 if(newExtraX * xDiffSign > 0) thisPt.labelExtraX += newExtraX;
85009 }
85010 }
85011 }
85012
85013 for(yHalf = 0; yHalf < 2; yHalf++) {
85014 equatorFirst = yHalf ? topFirst : bottomFirst;
85015 farthestY = yHalf ? Math.max : Math.min;
85016 yDiffSign = yHalf ? 1 : -1;
85017
85018 for(xHalf = 0; xHalf < 2; xHalf++) {
85019 farthestX = xHalf ? Math.max : Math.min;
85020 xDiffSign = xHalf ? 1 : -1;
85021
85022 // first sort the array
85023 // note this is a copy of cd, so cd itself doesn't get sorted
85024 // but we can still modify points in place.
85025 thisQuad = quadrants[yHalf][xHalf];
85026 thisQuad.sort(equatorFirst);
85027
85028 oppositeQuad = quadrants[1 - yHalf][xHalf];
85029 wholeSide = oppositeQuad.concat(thisQuad);
85030
85031 thisQuadOutside = [];
85032 for(i = 0; i < thisQuad.length; i++) {
85033 if(thisQuad[i].yLabelMid !== undefined) thisQuadOutside.push(thisQuad[i]);
85034 }
85035
85036 firstOppositeOutsidePt = false;
85037 for(i = 0; yHalf && i < oppositeQuad.length; i++) {
85038 if(oppositeQuad[i].yLabelMid !== undefined) {
85039 firstOppositeOutsidePt = oppositeQuad[i];
85040 break;
85041 }
85042 }
85043
85044 // each needs to avoid the previous
85045 for(i = 0; i < thisQuadOutside.length; i++) {
85046 var prevPt = i && thisQuadOutside[i - 1];
85047 // bottom half needs to avoid the first label of the top half
85048 // top half we still need to call scootOneLabel on the first slice
85049 // so we can avoid other slices, but we don't pass a prevPt
85050 if(firstOppositeOutsidePt && !i) prevPt = firstOppositeOutsidePt;
85051 scootOneLabel(thisQuadOutside[i], prevPt);
85052 }
85053 }
85054 }
85055}
85056
85057function layoutAreas(cdModule, plotSize) {
85058 var scaleGroups = [];
85059
85060 // figure out the center and maximum radius
85061 for(var i = 0; i < cdModule.length; i++) {
85062 var cd0 = cdModule[i][0];
85063 var trace = cd0.trace;
85064
85065 var domain = trace.domain;
85066 var width = plotSize.w * (domain.x[1] - domain.x[0]);
85067 var height = plotSize.h * (domain.y[1] - domain.y[0]);
85068 // leave some space for the title, if it will be displayed outside
85069 if(trace.title.text && trace.title.position !== 'middle center') {
85070 height -= getTitleSpace(cd0, plotSize);
85071 }
85072
85073 var rx = width / 2;
85074 var ry = height / 2;
85075 if(trace.type === 'funnelarea' && !trace.scalegroup) {
85076 ry /= trace.aspectratio;
85077 }
85078
85079 cd0.r = Math.min(rx, ry) / (1 + getMaxPull(trace));
85080
85081 cd0.cx = plotSize.l + plotSize.w * (trace.domain.x[1] + trace.domain.x[0]) / 2;
85082 cd0.cy = plotSize.t + plotSize.h * (1 - trace.domain.y[0]) - height / 2;
85083 if(trace.title.text && trace.title.position.indexOf('bottom') !== -1) {
85084 cd0.cy -= getTitleSpace(cd0, plotSize);
85085 }
85086
85087 if(trace.scalegroup && scaleGroups.indexOf(trace.scalegroup) === -1) {
85088 scaleGroups.push(trace.scalegroup);
85089 }
85090 }
85091
85092 groupScale(cdModule, scaleGroups);
85093}
85094
85095function groupScale(cdModule, scaleGroups) {
85096 var cd0, i, trace;
85097
85098 // scale those that are grouped
85099 for(var k = 0; k < scaleGroups.length; k++) {
85100 var min = Infinity;
85101 var g = scaleGroups[k];
85102
85103 for(i = 0; i < cdModule.length; i++) {
85104 cd0 = cdModule[i][0];
85105 trace = cd0.trace;
85106
85107 if(trace.scalegroup === g) {
85108 var area;
85109 if(trace.type === 'pie') {
85110 area = cd0.r * cd0.r;
85111 } else if(trace.type === 'funnelarea') {
85112 var rx, ry;
85113
85114 if(trace.aspectratio > 1) {
85115 rx = cd0.r;
85116 ry = rx / trace.aspectratio;
85117 } else {
85118 ry = cd0.r;
85119 rx = ry * trace.aspectratio;
85120 }
85121
85122 rx *= (1 + trace.baseratio) / 2;
85123
85124 area = rx * ry;
85125 }
85126
85127 min = Math.min(min, area / cd0.vTotal);
85128 }
85129 }
85130
85131 for(i = 0; i < cdModule.length; i++) {
85132 cd0 = cdModule[i][0];
85133 trace = cd0.trace;
85134 if(trace.scalegroup === g) {
85135 var v = min * cd0.vTotal;
85136 if(trace.type === 'funnelarea') {
85137 v /= (1 + trace.baseratio) / 2;
85138 v /= trace.aspectratio;
85139 }
85140
85141 cd0.r = Math.sqrt(v);
85142 }
85143 }
85144 }
85145}
85146
85147function setCoords(cd) {
85148 var cd0 = cd[0];
85149 var r = cd0.r;
85150 var trace = cd0.trace;
85151 var currentAngle = trace.rotation * Math.PI / 180;
85152 var angleFactor = 2 * Math.PI / cd0.vTotal;
85153 var firstPt = 'px0';
85154 var lastPt = 'px1';
85155
85156 var i, cdi, currentCoords;
85157
85158 if(trace.direction === 'counterclockwise') {
85159 for(i = 0; i < cd.length; i++) {
85160 if(!cd[i].hidden) break; // find the first non-hidden slice
85161 }
85162 if(i === cd.length) return; // all slices hidden
85163
85164 currentAngle += angleFactor * cd[i].v;
85165 angleFactor *= -1;
85166 firstPt = 'px1';
85167 lastPt = 'px0';
85168 }
85169
85170 currentCoords = getCoords(r, currentAngle);
85171
85172 for(i = 0; i < cd.length; i++) {
85173 cdi = cd[i];
85174 if(cdi.hidden) continue;
85175
85176 cdi[firstPt] = currentCoords;
85177
85178 cdi.startangle = currentAngle;
85179 currentAngle += angleFactor * cdi.v / 2;
85180 cdi.pxmid = getCoords(r, currentAngle);
85181 cdi.midangle = currentAngle;
85182 currentAngle += angleFactor * cdi.v / 2;
85183 currentCoords = getCoords(r, currentAngle);
85184 cdi.stopangle = currentAngle;
85185
85186 cdi[lastPt] = currentCoords;
85187
85188 cdi.largeArc = (cdi.v > cd0.vTotal / 2) ? 1 : 0;
85189
85190 cdi.halfangle = Math.PI * Math.min(cdi.v / cd0.vTotal, 0.5);
85191 cdi.ring = 1 - trace.hole;
85192 cdi.rInscribed = getInscribedRadiusFraction(cdi, cd0);
85193 }
85194}
85195
85196function getCoords(r, angle) {
85197 return [r * Math.sin(angle), -r * Math.cos(angle)];
85198}
85199
85200function formatSliceLabel(gd, pt, cd0) {
85201 var fullLayout = gd._fullLayout;
85202 var trace = cd0.trace;
85203 // look for textemplate
85204 var texttemplate = trace.texttemplate;
85205
85206 // now insert text
85207 var textinfo = trace.textinfo;
85208 if(!texttemplate && textinfo && textinfo !== 'none') {
85209 var parts = textinfo.split('+');
85210 var hasFlag = function(flag) { return parts.indexOf(flag) !== -1; };
85211 var hasLabel = hasFlag('label');
85212 var hasText = hasFlag('text');
85213 var hasValue = hasFlag('value');
85214 var hasPercent = hasFlag('percent');
85215
85216 var separators = fullLayout.separators;
85217 var text;
85218
85219 text = hasLabel ? [pt.label] : [];
85220 if(hasText) {
85221 var tx = helpers.getFirstFilled(trace.text, pt.pts);
85222 if(isValidTextValue(tx)) text.push(tx);
85223 }
85224 if(hasValue) text.push(helpers.formatPieValue(pt.v, separators));
85225 if(hasPercent) text.push(helpers.formatPiePercent(pt.v / cd0.vTotal, separators));
85226 pt.text = text.join('<br>');
85227 }
85228
85229 function makeTemplateVariables(pt) {
85230 return {
85231 label: pt.label,
85232 value: pt.v,
85233 valueLabel: helpers.formatPieValue(pt.v, fullLayout.separators),
85234 percent: pt.v / cd0.vTotal,
85235 percentLabel: helpers.formatPiePercent(pt.v / cd0.vTotal, fullLayout.separators),
85236 color: pt.color,
85237 text: pt.text,
85238 customdata: Lib.castOption(trace, pt.i, 'customdata')
85239 };
85240 }
85241
85242 if(texttemplate) {
85243 var txt = Lib.castOption(trace, pt.i, 'texttemplate');
85244 if(!txt) {
85245 pt.text = '';
85246 } else {
85247 var obj = makeTemplateVariables(pt);
85248 var ptTx = helpers.getFirstFilled(trace.text, pt.pts);
85249 if(isValidTextValue(ptTx) || ptTx === '') obj.text = ptTx;
85250 pt.text = Lib.texttemplateString(txt, obj, gd._fullLayout._d3locale, obj, trace._meta || {});
85251 }
85252 }
85253}
85254
85255function computeTransform(
85256 transform, // inout
85257 textBB // in
85258) {
85259 var a = transform.rotate * Math.PI / 180;
85260 var cosA = Math.cos(a);
85261 var sinA = Math.sin(a);
85262 var midX = (textBB.left + textBB.right) / 2;
85263 var midY = (textBB.top + textBB.bottom) / 2;
85264 transform.textX = midX * cosA - midY * sinA;
85265 transform.textY = midX * sinA + midY * cosA;
85266 transform.noCenter = true;
85267}
85268
85269module.exports = {
85270 plot: plot,
85271 formatSliceLabel: formatSliceLabel,
85272 transformInsideText: transformInsideText,
85273 determineInsideTextFont: determineInsideTextFont,
85274 positionTitleOutside: positionTitleOutside,
85275 prerenderTitles: prerenderTitles,
85276 layoutAreas: layoutAreas,
85277 attachFxHandlers: attachFxHandlers,
85278 computeTransform: computeTransform
85279};
85280
85281},{"../../components/color":52,"../../components/drawing":74,"../../components/fx":92,"../../lib":178,"../../lib/svg_text_utils":199,"../../plots/plots":256,"../bar/constants":281,"../bar/uniform_text":295,"./event_data":380,"./helpers":381,"d3":16}],386:[function(_dereq_,module,exports){
85282/**
85283* Copyright 2012-2020, Plotly, Inc.
85284* All rights reserved.
85285*
85286* This source code is licensed under the MIT license found in the
85287* LICENSE file in the root directory of this source tree.
85288*/
85289
85290'use strict';
85291
85292var d3 = _dereq_('d3');
85293
85294var styleOne = _dereq_('./style_one');
85295var resizeText = _dereq_('../bar/uniform_text').resizeText;
85296
85297module.exports = function style(gd) {
85298 var s = gd._fullLayout._pielayer.selectAll('.trace');
85299 resizeText(gd, s, 'pie');
85300
85301 s.each(function(cd) {
85302 var cd0 = cd[0];
85303 var trace = cd0.trace;
85304 var traceSelection = d3.select(this);
85305
85306 traceSelection.style({opacity: trace.opacity});
85307
85308 traceSelection.selectAll('path.surface').each(function(pt) {
85309 d3.select(this).call(styleOne, pt, trace);
85310 });
85311 });
85312};
85313
85314},{"../bar/uniform_text":295,"./style_one":387,"d3":16}],387:[function(_dereq_,module,exports){
85315/**
85316* Copyright 2012-2020, Plotly, Inc.
85317* All rights reserved.
85318*
85319* This source code is licensed under the MIT license found in the
85320* LICENSE file in the root directory of this source tree.
85321*/
85322
85323'use strict';
85324
85325var Color = _dereq_('../../components/color');
85326var castOption = _dereq_('./helpers').castOption;
85327
85328module.exports = function styleOne(s, pt, trace) {
85329 var line = trace.marker.line;
85330 var lineColor = castOption(line.color, pt.pts) || Color.defaultLine;
85331 var lineWidth = castOption(line.width, pt.pts) || 0;
85332
85333 s.style('stroke-width', lineWidth)
85334 .call(Color.fill, pt.color)
85335 .call(Color.stroke, lineColor);
85336};
85337
85338},{"../../components/color":52,"./helpers":381}],388:[function(_dereq_,module,exports){
85339/**
85340* Copyright 2012-2020, Plotly, Inc.
85341* All rights reserved.
85342*
85343* This source code is licensed under the MIT license found in the
85344* LICENSE file in the root directory of this source tree.
85345*/
85346
85347
85348'use strict';
85349
85350var Lib = _dereq_('../../lib');
85351
85352
85353// arrayOk attributes, merge them into calcdata array
85354module.exports = function arraysToCalcdata(cd, trace) {
85355 // so each point knows which index it originally came from
85356 for(var i = 0; i < cd.length; i++) cd[i].i = i;
85357
85358 Lib.mergeArray(trace.text, cd, 'tx');
85359 Lib.mergeArray(trace.texttemplate, cd, 'txt');
85360 Lib.mergeArray(trace.hovertext, cd, 'htx');
85361 Lib.mergeArray(trace.customdata, cd, 'data');
85362 Lib.mergeArray(trace.textposition, cd, 'tp');
85363 if(trace.textfont) {
85364 Lib.mergeArrayCastPositive(trace.textfont.size, cd, 'ts');
85365 Lib.mergeArray(trace.textfont.color, cd, 'tc');
85366 Lib.mergeArray(trace.textfont.family, cd, 'tf');
85367 }
85368
85369 var marker = trace.marker;
85370 if(marker) {
85371 Lib.mergeArrayCastPositive(marker.size, cd, 'ms');
85372 Lib.mergeArrayCastPositive(marker.opacity, cd, 'mo');
85373 Lib.mergeArray(marker.symbol, cd, 'mx');
85374 Lib.mergeArray(marker.color, cd, 'mc');
85375
85376 var markerLine = marker.line;
85377 if(marker.line) {
85378 Lib.mergeArray(markerLine.color, cd, 'mlc');
85379 Lib.mergeArrayCastPositive(markerLine.width, cd, 'mlw');
85380 }
85381
85382 var markerGradient = marker.gradient;
85383 if(markerGradient && markerGradient.type !== 'none') {
85384 Lib.mergeArray(markerGradient.type, cd, 'mgt');
85385 Lib.mergeArray(markerGradient.color, cd, 'mgc');
85386 }
85387 }
85388};
85389
85390},{"../../lib":178}],389:[function(_dereq_,module,exports){
85391/**
85392* Copyright 2012-2020, Plotly, Inc.
85393* All rights reserved.
85394*
85395* This source code is licensed under the MIT license found in the
85396* LICENSE file in the root directory of this source tree.
85397*/
85398
85399'use strict';
85400
85401var texttemplateAttrs = _dereq_('../../plots/template_attributes').texttemplateAttrs;
85402var hovertemplateAttrs = _dereq_('../../plots/template_attributes').hovertemplateAttrs;
85403var colorScaleAttrs = _dereq_('../../components/colorscale/attributes');
85404var fontAttrs = _dereq_('../../plots/font_attributes');
85405var dash = _dereq_('../../components/drawing/attributes').dash;
85406
85407var Drawing = _dereq_('../../components/drawing');
85408var constants = _dereq_('./constants');
85409var extendFlat = _dereq_('../../lib/extend').extendFlat;
85410
85411module.exports = {
85412 x: {
85413 valType: 'data_array',
85414 editType: 'calc+clearAxisTypes',
85415 anim: true,
85416
85417 },
85418 x0: {
85419 valType: 'any',
85420 dflt: 0,
85421
85422 editType: 'calc+clearAxisTypes',
85423 anim: true,
85424
85425 },
85426 dx: {
85427 valType: 'number',
85428 dflt: 1,
85429
85430 editType: 'calc',
85431 anim: true,
85432
85433 },
85434 y: {
85435 valType: 'data_array',
85436 editType: 'calc+clearAxisTypes',
85437 anim: true,
85438
85439 },
85440 y0: {
85441 valType: 'any',
85442 dflt: 0,
85443
85444 editType: 'calc+clearAxisTypes',
85445 anim: true,
85446
85447 },
85448 dy: {
85449 valType: 'number',
85450 dflt: 1,
85451
85452 editType: 'calc',
85453 anim: true,
85454
85455 },
85456
85457 stackgroup: {
85458 valType: 'string',
85459
85460 dflt: '',
85461 editType: 'calc',
85462
85463 },
85464 orientation: {
85465 valType: 'enumerated',
85466
85467 values: ['v', 'h'],
85468 editType: 'calc',
85469
85470 },
85471 groupnorm: {
85472 valType: 'enumerated',
85473 values: ['', 'fraction', 'percent'],
85474 dflt: '',
85475
85476 editType: 'calc',
85477
85478 },
85479 stackgaps: {
85480 valType: 'enumerated',
85481 values: ['infer zero', 'interpolate'],
85482 dflt: 'infer zero',
85483
85484 editType: 'calc',
85485
85486 },
85487
85488 text: {
85489 valType: 'string',
85490
85491 dflt: '',
85492 arrayOk: true,
85493 editType: 'calc',
85494
85495 },
85496
85497 texttemplate: texttemplateAttrs({}, {
85498
85499 }),
85500 hovertext: {
85501 valType: 'string',
85502
85503 dflt: '',
85504 arrayOk: true,
85505 editType: 'style',
85506
85507 },
85508 mode: {
85509 valType: 'flaglist',
85510 flags: ['lines', 'markers', 'text'],
85511 extras: ['none'],
85512
85513 editType: 'calc',
85514
85515 },
85516 hoveron: {
85517 valType: 'flaglist',
85518 flags: ['points', 'fills'],
85519
85520 editType: 'style',
85521
85522 },
85523 hovertemplate: hovertemplateAttrs({}, {
85524 keys: constants.eventDataKeys
85525 }),
85526 line: {
85527 color: {
85528 valType: 'color',
85529
85530 editType: 'style',
85531 anim: true,
85532
85533 },
85534 width: {
85535 valType: 'number',
85536 min: 0,
85537 dflt: 2,
85538
85539 editType: 'style',
85540 anim: true,
85541
85542 },
85543 shape: {
85544 valType: 'enumerated',
85545 values: ['linear', 'spline', 'hv', 'vh', 'hvh', 'vhv'],
85546 dflt: 'linear',
85547
85548 editType: 'plot',
85549
85550 },
85551 smoothing: {
85552 valType: 'number',
85553 min: 0,
85554 max: 1.3,
85555 dflt: 1,
85556
85557 editType: 'plot',
85558
85559 },
85560 dash: extendFlat({}, dash, {editType: 'style'}),
85561 simplify: {
85562 valType: 'boolean',
85563 dflt: true,
85564
85565 editType: 'plot',
85566
85567 },
85568 editType: 'plot'
85569 },
85570
85571 connectgaps: {
85572 valType: 'boolean',
85573 dflt: false,
85574
85575 editType: 'calc',
85576
85577 },
85578 cliponaxis: {
85579 valType: 'boolean',
85580 dflt: true,
85581
85582 editType: 'plot',
85583
85584 },
85585
85586 fill: {
85587 valType: 'enumerated',
85588 values: ['none', 'tozeroy', 'tozerox', 'tonexty', 'tonextx', 'toself', 'tonext'],
85589
85590 editType: 'calc',
85591
85592 },
85593 fillcolor: {
85594 valType: 'color',
85595
85596 editType: 'style',
85597 anim: true,
85598
85599 },
85600 marker: extendFlat({
85601 symbol: {
85602 valType: 'enumerated',
85603 values: Drawing.symbolList,
85604 dflt: 'circle',
85605 arrayOk: true,
85606
85607 editType: 'style',
85608
85609 },
85610 opacity: {
85611 valType: 'number',
85612 min: 0,
85613 max: 1,
85614 arrayOk: true,
85615
85616 editType: 'style',
85617 anim: true,
85618
85619 },
85620 size: {
85621 valType: 'number',
85622 min: 0,
85623 dflt: 6,
85624 arrayOk: true,
85625
85626 editType: 'calc',
85627 anim: true,
85628
85629 },
85630 maxdisplayed: {
85631 valType: 'number',
85632 min: 0,
85633 dflt: 0,
85634
85635 editType: 'plot',
85636
85637 },
85638 sizeref: {
85639 valType: 'number',
85640 dflt: 1,
85641
85642 editType: 'calc',
85643
85644 },
85645 sizemin: {
85646 valType: 'number',
85647 min: 0,
85648 dflt: 0,
85649
85650 editType: 'calc',
85651
85652 },
85653 sizemode: {
85654 valType: 'enumerated',
85655 values: ['diameter', 'area'],
85656 dflt: 'diameter',
85657
85658 editType: 'calc',
85659
85660 },
85661
85662 line: extendFlat({
85663 width: {
85664 valType: 'number',
85665 min: 0,
85666 arrayOk: true,
85667
85668 editType: 'style',
85669 anim: true,
85670
85671 },
85672 editType: 'calc'
85673 },
85674 colorScaleAttrs('marker.line', {anim: true})
85675 ),
85676 gradient: {
85677 type: {
85678 valType: 'enumerated',
85679 values: ['radial', 'horizontal', 'vertical', 'none'],
85680 arrayOk: true,
85681 dflt: 'none',
85682
85683 editType: 'calc',
85684
85685 },
85686 color: {
85687 valType: 'color',
85688 arrayOk: true,
85689
85690 editType: 'calc',
85691
85692 },
85693 editType: 'calc'
85694 },
85695 editType: 'calc'
85696 },
85697 colorScaleAttrs('marker', {anim: true})
85698 ),
85699 selected: {
85700 marker: {
85701 opacity: {
85702 valType: 'number',
85703 min: 0,
85704 max: 1,
85705
85706 editType: 'style',
85707
85708 },
85709 color: {
85710 valType: 'color',
85711
85712 editType: 'style',
85713
85714 },
85715 size: {
85716 valType: 'number',
85717 min: 0,
85718
85719 editType: 'style',
85720
85721 },
85722 editType: 'style'
85723 },
85724 textfont: {
85725 color: {
85726 valType: 'color',
85727
85728 editType: 'style',
85729
85730 },
85731 editType: 'style'
85732 },
85733 editType: 'style'
85734 },
85735 unselected: {
85736 marker: {
85737 opacity: {
85738 valType: 'number',
85739 min: 0,
85740 max: 1,
85741
85742 editType: 'style',
85743
85744 },
85745 color: {
85746 valType: 'color',
85747
85748 editType: 'style',
85749
85750 },
85751 size: {
85752 valType: 'number',
85753 min: 0,
85754
85755 editType: 'style',
85756
85757 },
85758 editType: 'style'
85759 },
85760 textfont: {
85761 color: {
85762 valType: 'color',
85763
85764 editType: 'style',
85765
85766 },
85767 editType: 'style'
85768 },
85769 editType: 'style'
85770 },
85771
85772 textposition: {
85773 valType: 'enumerated',
85774 values: [
85775 'top left', 'top center', 'top right',
85776 'middle left', 'middle center', 'middle right',
85777 'bottom left', 'bottom center', 'bottom right'
85778 ],
85779 dflt: 'middle center',
85780 arrayOk: true,
85781
85782 editType: 'calc',
85783
85784 },
85785 textfont: fontAttrs({
85786 editType: 'calc',
85787 colorEditType: 'style',
85788 arrayOk: true,
85789
85790 }),
85791
85792 r: {
85793 valType: 'data_array',
85794 editType: 'calc',
85795
85796 },
85797 t: {
85798 valType: 'data_array',
85799 editType: 'calc',
85800
85801 }
85802};
85803
85804},{"../../components/colorscale/attributes":59,"../../components/drawing":74,"../../components/drawing/attributes":73,"../../lib/extend":173,"../../plots/font_attributes":250,"../../plots/template_attributes":264,"./constants":393}],390:[function(_dereq_,module,exports){
85805/**
85806* Copyright 2012-2020, Plotly, Inc.
85807* All rights reserved.
85808*
85809* This source code is licensed under the MIT license found in the
85810* LICENSE file in the root directory of this source tree.
85811*/
85812
85813'use strict';
85814
85815var isNumeric = _dereq_('fast-isnumeric');
85816var Lib = _dereq_('../../lib');
85817
85818var Axes = _dereq_('../../plots/cartesian/axes');
85819var BADNUM = _dereq_('../../constants/numerical').BADNUM;
85820
85821var subTypes = _dereq_('./subtypes');
85822var calcColorscale = _dereq_('./colorscale_calc');
85823var arraysToCalcdata = _dereq_('./arrays_to_calcdata');
85824var calcSelection = _dereq_('./calc_selection');
85825
85826function calc(gd, trace) {
85827 var fullLayout = gd._fullLayout;
85828 var xa = Axes.getFromId(gd, trace.xaxis || 'x');
85829 var ya = Axes.getFromId(gd, trace.yaxis || 'y');
85830 var x = xa.makeCalcdata(trace, 'x');
85831 var y = ya.makeCalcdata(trace, 'y');
85832 var serieslen = trace._length;
85833 var cd = new Array(serieslen);
85834 var ids = trace.ids;
85835 var stackGroupOpts = getStackOpts(trace, fullLayout, xa, ya);
85836 var interpolateGaps = false;
85837 var isV, i, j, k, interpolate, vali;
85838
85839 setFirstScatter(fullLayout, trace);
85840
85841 var xAttr = 'x';
85842 var yAttr = 'y';
85843 var posAttr;
85844 if(stackGroupOpts) {
85845 Lib.pushUnique(stackGroupOpts.traceIndices, trace._expandedIndex);
85846 isV = stackGroupOpts.orientation === 'v';
85847
85848 // size, like we use for bar
85849 if(isV) {
85850 yAttr = 's';
85851 posAttr = 'x';
85852 } else {
85853 xAttr = 's';
85854 posAttr = 'y';
85855 }
85856 interpolate = stackGroupOpts.stackgaps === 'interpolate';
85857 } else {
85858 var ppad = calcMarkerSize(trace, serieslen);
85859 calcAxisExpansion(gd, trace, xa, ya, x, y, ppad);
85860 }
85861
85862 for(i = 0; i < serieslen; i++) {
85863 var cdi = cd[i] = {};
85864 var xValid = isNumeric(x[i]);
85865 var yValid = isNumeric(y[i]);
85866 if(xValid && yValid) {
85867 cdi[xAttr] = x[i];
85868 cdi[yAttr] = y[i];
85869 } else if(stackGroupOpts && (isV ? xValid : yValid)) {
85870 // if we're stacking we need to hold on to all valid positions
85871 // even with invalid sizes
85872
85873 cdi[posAttr] = isV ? x[i] : y[i];
85874 cdi.gap = true;
85875 if(interpolate) {
85876 cdi.s = BADNUM;
85877 interpolateGaps = true;
85878 } else {
85879 cdi.s = 0;
85880 }
85881 } else {
85882 cdi[xAttr] = cdi[yAttr] = BADNUM;
85883 }
85884
85885 if(ids) {
85886 cdi.id = String(ids[i]);
85887 }
85888 }
85889
85890 arraysToCalcdata(cd, trace);
85891 calcColorscale(gd, trace);
85892 calcSelection(cd, trace);
85893
85894 if(stackGroupOpts) {
85895 // remove bad positions and sort
85896 // note that original indices get added to cd in arraysToCalcdata
85897 i = 0;
85898 while(i < cd.length) {
85899 if(cd[i][posAttr] === BADNUM) {
85900 cd.splice(i, 1);
85901 } else i++;
85902 }
85903
85904 Lib.sort(cd, function(a, b) {
85905 return (a[posAttr] - b[posAttr]) || (a.i - b.i);
85906 });
85907
85908 if(interpolateGaps) {
85909 // first fill the beginning with constant from the first point
85910 i = 0;
85911 while(i < cd.length - 1 && cd[i].gap) {
85912 i++;
85913 }
85914 vali = cd[i].s;
85915 if(!vali) vali = cd[i].s = 0; // in case of no data AT ALL in this trace - use 0
85916 for(j = 0; j < i; j++) {
85917 cd[j].s = vali;
85918 }
85919 // then fill the end with constant from the last point
85920 k = cd.length - 1;
85921 while(k > i && cd[k].gap) {
85922 k--;
85923 }
85924 vali = cd[k].s;
85925 for(j = cd.length - 1; j > k; j--) {
85926 cd[j].s = vali;
85927 }
85928 // now interpolate internal gaps linearly
85929 while(i < k) {
85930 i++;
85931 if(cd[i].gap) {
85932 j = i + 1;
85933 while(cd[j].gap) {
85934 j++;
85935 }
85936 var pos0 = cd[i - 1][posAttr];
85937 var size0 = cd[i - 1].s;
85938 var m = (cd[j].s - size0) / (cd[j][posAttr] - pos0);
85939 while(i < j) {
85940 cd[i].s = size0 + (cd[i][posAttr] - pos0) * m;
85941 i++;
85942 }
85943 }
85944 }
85945 }
85946 }
85947
85948 return cd;
85949}
85950
85951function calcAxisExpansion(gd, trace, xa, ya, x, y, ppad) {
85952 var serieslen = trace._length;
85953 var fullLayout = gd._fullLayout;
85954 var xId = xa._id;
85955 var yId = ya._id;
85956 var firstScatter = fullLayout._firstScatter[firstScatterGroup(trace)] === trace.uid;
85957 var stackOrientation = (getStackOpts(trace, fullLayout, xa, ya) || {}).orientation;
85958 var fill = trace.fill;
85959
85960 // cancel minimum tick spacings (only applies to bars and boxes)
85961 xa._minDtick = 0;
85962 ya._minDtick = 0;
85963
85964 // check whether bounds should be tight, padded, extended to zero...
85965 // most cases both should be padded on both ends, so start with that.
85966 var xOptions = {padded: true};
85967 var yOptions = {padded: true};
85968
85969 if(ppad) {
85970 xOptions.ppad = yOptions.ppad = ppad;
85971 }
85972
85973 // TODO: text size
85974
85975 var openEnded = serieslen < 2 || (x[0] !== x[serieslen - 1]) || (y[0] !== y[serieslen - 1]);
85976
85977 if(openEnded && (
85978 (fill === 'tozerox') ||
85979 ((fill === 'tonextx') && (firstScatter || stackOrientation === 'h'))
85980 )) {
85981 // include zero (tight) and extremes (padded) if fill to zero
85982 // (unless the shape is closed, then it's just filling the shape regardless)
85983
85984 xOptions.tozero = true;
85985 } else if(!(trace.error_y || {}).visible && (
85986 // if no error bars, markers or text, or fill to y=0 remove x padding
85987
85988 (fill === 'tonexty' || fill === 'tozeroy') ||
85989 (!subTypes.hasMarkers(trace) && !subTypes.hasText(trace))
85990 )) {
85991 xOptions.padded = false;
85992 xOptions.ppad = 0;
85993 }
85994
85995 if(openEnded && (
85996 (fill === 'tozeroy') ||
85997 ((fill === 'tonexty') && (firstScatter || stackOrientation === 'v'))
85998 )) {
85999 // now check for y - rather different logic, though still mostly padded both ends
86000 // include zero (tight) and extremes (padded) if fill to zero
86001 // (unless the shape is closed, then it's just filling the shape regardless)
86002
86003 yOptions.tozero = true;
86004 } else if(fill === 'tonextx' || fill === 'tozerox') {
86005 // tight y: any x fill
86006
86007 yOptions.padded = false;
86008 }
86009
86010 // N.B. asymmetric splom traces call this with blank {} xa or ya
86011 if(xId) trace._extremes[xId] = Axes.findExtremes(xa, x, xOptions);
86012 if(yId) trace._extremes[yId] = Axes.findExtremes(ya, y, yOptions);
86013}
86014
86015function calcMarkerSize(trace, serieslen) {
86016 if(!subTypes.hasMarkers(trace)) return;
86017
86018 // Treat size like x or y arrays --- Run d2c
86019 // this needs to go before ppad computation
86020 var marker = trace.marker;
86021 var sizeref = 1.6 * (trace.marker.sizeref || 1);
86022 var markerTrans;
86023
86024 if(trace.marker.sizemode === 'area') {
86025 markerTrans = function(v) {
86026 return Math.max(Math.sqrt((v || 0) / sizeref), 3);
86027 };
86028 } else {
86029 markerTrans = function(v) {
86030 return Math.max((v || 0) / sizeref, 3);
86031 };
86032 }
86033
86034 if(Lib.isArrayOrTypedArray(marker.size)) {
86035 // I tried auto-type but category and dates dont make much sense.
86036 var ax = {type: 'linear'};
86037 Axes.setConvert(ax);
86038
86039 var s = ax.makeCalcdata(trace.marker, 'size');
86040
86041 var sizeOut = new Array(serieslen);
86042 for(var i = 0; i < serieslen; i++) {
86043 sizeOut[i] = markerTrans(s[i]);
86044 }
86045 return sizeOut;
86046 } else {
86047 return markerTrans(marker.size);
86048 }
86049}
86050
86051/**
86052 * mark the first scatter trace for each subplot
86053 * note that scatter and scattergl each get their own first trace
86054 * note also that I'm doing this during calc rather than supplyDefaults
86055 * so I don't need to worry about transforms, but if we ever do
86056 * per-trace calc this will get confused.
86057 */
86058function setFirstScatter(fullLayout, trace) {
86059 var group = firstScatterGroup(trace);
86060 var firstScatter = fullLayout._firstScatter;
86061 if(!firstScatter[group]) firstScatter[group] = trace.uid;
86062}
86063
86064function firstScatterGroup(trace) {
86065 var stackGroup = trace.stackgroup;
86066 return trace.xaxis + trace.yaxis + trace.type +
86067 (stackGroup ? '-' + stackGroup : '');
86068}
86069
86070function getStackOpts(trace, fullLayout, xa, ya) {
86071 var stackGroup = trace.stackgroup;
86072 if(!stackGroup) return;
86073 var stackOpts = fullLayout._scatterStackOpts[xa._id + ya._id][stackGroup];
86074 var stackAx = stackOpts.orientation === 'v' ? ya : xa;
86075 // Allow stacking only on numeric axes
86076 // calc is a little late to be figuring this out, but during supplyDefaults
86077 // we don't know the axis type yet
86078 if(stackAx.type === 'linear' || stackAx.type === 'log') return stackOpts;
86079}
86080
86081module.exports = {
86082 calc: calc,
86083 calcMarkerSize: calcMarkerSize,
86084 calcAxisExpansion: calcAxisExpansion,
86085 setFirstScatter: setFirstScatter,
86086 getStackOpts: getStackOpts
86087};
86088
86089},{"../../constants/numerical":158,"../../lib":178,"../../plots/cartesian/axes":222,"./arrays_to_calcdata":388,"./calc_selection":391,"./colorscale_calc":392,"./subtypes":413,"fast-isnumeric":18}],391:[function(_dereq_,module,exports){
86090/**
86091* Copyright 2012-2020, Plotly, Inc.
86092* All rights reserved.
86093*
86094* This source code is licensed under the MIT license found in the
86095* LICENSE file in the root directory of this source tree.
86096*/
86097
86098'use strict';
86099
86100var Lib = _dereq_('../../lib');
86101
86102module.exports = function calcSelection(cd, trace) {
86103 if(Lib.isArrayOrTypedArray(trace.selectedpoints)) {
86104 Lib.tagSelected(cd, trace);
86105 }
86106};
86107
86108},{"../../lib":178}],392:[function(_dereq_,module,exports){
86109/**
86110* Copyright 2012-2020, Plotly, Inc.
86111* All rights reserved.
86112*
86113* This source code is licensed under the MIT license found in the
86114* LICENSE file in the root directory of this source tree.
86115*/
86116
86117'use strict';
86118
86119var hasColorscale = _dereq_('../../components/colorscale/helpers').hasColorscale;
86120var calcColorscale = _dereq_('../../components/colorscale/calc');
86121
86122var subTypes = _dereq_('./subtypes');
86123
86124module.exports = function calcMarkerColorscale(gd, trace) {
86125 if(subTypes.hasLines(trace) && hasColorscale(trace, 'line')) {
86126 calcColorscale(gd, trace, {
86127 vals: trace.line.color,
86128 containerStr: 'line',
86129 cLetter: 'c'
86130 });
86131 }
86132
86133 if(subTypes.hasMarkers(trace)) {
86134 if(hasColorscale(trace, 'marker')) {
86135 calcColorscale(gd, trace, {
86136 vals: trace.marker.color,
86137 containerStr: 'marker',
86138 cLetter: 'c'
86139 });
86140 }
86141 if(hasColorscale(trace, 'marker.line')) {
86142 calcColorscale(gd, trace, {
86143 vals: trace.marker.line.color,
86144 containerStr: 'marker.line',
86145 cLetter: 'c'
86146 });
86147 }
86148 }
86149};
86150
86151},{"../../components/colorscale/calc":60,"../../components/colorscale/helpers":63,"./subtypes":413}],393:[function(_dereq_,module,exports){
86152/**
86153* Copyright 2012-2020, Plotly, Inc.
86154* All rights reserved.
86155*
86156* This source code is licensed under the MIT license found in the
86157* LICENSE file in the root directory of this source tree.
86158*/
86159
86160
86161'use strict';
86162
86163module.exports = {
86164 PTS_LINESONLY: 20,
86165
86166 // fixed parameters of clustering and clipping algorithms
86167
86168 // fraction of clustering tolerance "so close we don't even consider it a new point"
86169 minTolerance: 0.2,
86170 // how fast does clustering tolerance increase as you get away from the visible region
86171 toleranceGrowth: 10,
86172
86173 // number of viewport sizes away from the visible region
86174 // at which we clip all lines to the perimeter
86175 maxScreensAway: 20,
86176
86177 eventDataKeys: []
86178};
86179
86180},{}],394:[function(_dereq_,module,exports){
86181/**
86182* Copyright 2012-2020, Plotly, Inc.
86183* All rights reserved.
86184*
86185* This source code is licensed under the MIT license found in the
86186* LICENSE file in the root directory of this source tree.
86187*/
86188
86189
86190'use strict';
86191
86192var calc = _dereq_('./calc');
86193
86194/*
86195 * Scatter stacking & normalization calculations
86196 * runs per subplot, and can handle multiple stacking groups
86197 */
86198
86199module.exports = function crossTraceCalc(gd, plotinfo) {
86200 var xa = plotinfo.xaxis;
86201 var ya = plotinfo.yaxis;
86202 var subplot = xa._id + ya._id;
86203
86204 var subplotStackOpts = gd._fullLayout._scatterStackOpts[subplot];
86205 if(!subplotStackOpts) return;
86206
86207 var calcTraces = gd.calcdata;
86208
86209 var i, j, k, i2, cd, cd0, posj, sumj, norm;
86210 var groupOpts, interpolate, groupnorm, posAttr, valAttr;
86211 var hasAnyBlanks;
86212
86213 for(var stackGroup in subplotStackOpts) {
86214 groupOpts = subplotStackOpts[stackGroup];
86215 var indices = groupOpts.traceIndices;
86216
86217 // can get here with no indices if the stack axis is non-numeric
86218 if(!indices.length) continue;
86219
86220 interpolate = groupOpts.stackgaps === 'interpolate';
86221 groupnorm = groupOpts.groupnorm;
86222 if(groupOpts.orientation === 'v') {
86223 posAttr = 'x';
86224 valAttr = 'y';
86225 } else {
86226 posAttr = 'y';
86227 valAttr = 'x';
86228 }
86229 hasAnyBlanks = new Array(indices.length);
86230 for(i = 0; i < hasAnyBlanks.length; i++) {
86231 hasAnyBlanks[i] = false;
86232 }
86233
86234 // Collect the complete set of all positions across ALL traces.
86235 // Start with the first trace, then interleave items from later traces
86236 // as needed.
86237 // Fill in mising items as we go.
86238 cd0 = calcTraces[indices[0]];
86239 var allPositions = new Array(cd0.length);
86240 for(i = 0; i < cd0.length; i++) {
86241 allPositions[i] = cd0[i][posAttr];
86242 }
86243
86244 for(i = 1; i < indices.length; i++) {
86245 cd = calcTraces[indices[i]];
86246
86247 for(j = k = 0; j < cd.length; j++) {
86248 posj = cd[j][posAttr];
86249 for(; posj > allPositions[k] && k < allPositions.length; k++) {
86250 // the current trace is missing a position from some previous trace(s)
86251 insertBlank(cd, j, allPositions[k], i, hasAnyBlanks, interpolate, posAttr);
86252 j++;
86253 }
86254 if(posj !== allPositions[k]) {
86255 // previous trace(s) are missing a position from the current trace
86256 for(i2 = 0; i2 < i; i2++) {
86257 insertBlank(calcTraces[indices[i2]], k, posj, i2, hasAnyBlanks, interpolate, posAttr);
86258 }
86259 allPositions.splice(k, 0, posj);
86260 }
86261 k++;
86262 }
86263 for(; k < allPositions.length; k++) {
86264 insertBlank(cd, j, allPositions[k], i, hasAnyBlanks, interpolate, posAttr);
86265 j++;
86266 }
86267 }
86268
86269 var serieslen = allPositions.length;
86270
86271 // stack (and normalize)!
86272 for(j = 0; j < cd0.length; j++) {
86273 sumj = cd0[j][valAttr] = cd0[j].s;
86274 for(i = 1; i < indices.length; i++) {
86275 cd = calcTraces[indices[i]];
86276 cd[0].trace._rawLength = cd[0].trace._length;
86277 cd[0].trace._length = serieslen;
86278 sumj += cd[j].s;
86279 cd[j][valAttr] = sumj;
86280 }
86281
86282 if(groupnorm) {
86283 norm = ((groupnorm === 'fraction') ? sumj : (sumj / 100)) || 1;
86284 for(i = 0; i < indices.length; i++) {
86285 var cdj = calcTraces[indices[i]][j];
86286 cdj[valAttr] /= norm;
86287 cdj.sNorm = cdj.s / norm;
86288 }
86289 }
86290 }
86291
86292 // autorange
86293 for(i = 0; i < indices.length; i++) {
86294 cd = calcTraces[indices[i]];
86295 var trace = cd[0].trace;
86296 var ppad = calc.calcMarkerSize(trace, trace._rawLength);
86297 var arrayPad = Array.isArray(ppad);
86298 if((ppad && hasAnyBlanks[i]) || arrayPad) {
86299 var ppadRaw = ppad;
86300 ppad = new Array(serieslen);
86301 for(j = 0; j < serieslen; j++) {
86302 ppad[j] = cd[j].gap ? 0 : (arrayPad ? ppadRaw[cd[j].i] : ppadRaw);
86303 }
86304 }
86305 var x = new Array(serieslen);
86306 var y = new Array(serieslen);
86307 for(j = 0; j < serieslen; j++) {
86308 x[j] = cd[j].x;
86309 y[j] = cd[j].y;
86310 }
86311 calc.calcAxisExpansion(gd, trace, xa, ya, x, y, ppad);
86312
86313 // while we're here (in a loop over all traces in the stack)
86314 // record the orientation, so hover can find it easily
86315 cd[0].t.orientation = groupOpts.orientation;
86316 }
86317 }
86318};
86319
86320function insertBlank(calcTrace, index, position, traceIndex, hasAnyBlanks, interpolate, posAttr) {
86321 hasAnyBlanks[traceIndex] = true;
86322 var newEntry = {
86323 i: null,
86324 gap: true,
86325 s: 0
86326 };
86327 newEntry[posAttr] = position;
86328 calcTrace.splice(index, 0, newEntry);
86329 // Even if we're not interpolating, if one trace has multiple
86330 // values at the same position and this trace only has one value there,
86331 // we just duplicate that one value rather than insert a zero.
86332 // We also make it look like a real point - because it's ambiguous which
86333 // one really is the real one!
86334 if(index && position === calcTrace[index - 1][posAttr]) {
86335 var prevEntry = calcTrace[index - 1];
86336 newEntry.s = prevEntry.s;
86337 // TODO is it going to cause any problems to have multiple
86338 // calcdata points with the same index?
86339 newEntry.i = prevEntry.i;
86340 newEntry.gap = prevEntry.gap;
86341 } else if(interpolate) {
86342 newEntry.s = getInterp(calcTrace, index, position, posAttr);
86343 }
86344 if(!index) {
86345 // t and trace need to stay on the first cd entry
86346 calcTrace[0].t = calcTrace[1].t;
86347 calcTrace[0].trace = calcTrace[1].trace;
86348 delete calcTrace[1].t;
86349 delete calcTrace[1].trace;
86350 }
86351}
86352
86353function getInterp(calcTrace, index, position, posAttr) {
86354 var pt0 = calcTrace[index - 1];
86355 var pt1 = calcTrace[index + 1];
86356 if(!pt1) return pt0.s;
86357 if(!pt0) return pt1.s;
86358 return pt0.s + (pt1.s - pt0.s) * (position - pt0[posAttr]) / (pt1[posAttr] - pt0[posAttr]);
86359}
86360
86361},{"./calc":390}],395:[function(_dereq_,module,exports){
86362/**
86363* Copyright 2012-2020, Plotly, Inc.
86364* All rights reserved.
86365*
86366* This source code is licensed under the MIT license found in the
86367* LICENSE file in the root directory of this source tree.
86368*/
86369
86370
86371'use strict';
86372
86373
86374// remove opacity for any trace that has a fill or is filled to
86375module.exports = function crossTraceDefaults(fullData) {
86376 for(var i = 0; i < fullData.length; i++) {
86377 var tracei = fullData[i];
86378 if(tracei.type !== 'scatter') continue;
86379
86380 var filli = tracei.fill;
86381 if(filli === 'none' || filli === 'toself') continue;
86382
86383 tracei.opacity = undefined;
86384
86385 if(filli === 'tonexty' || filli === 'tonextx') {
86386 for(var j = i - 1; j >= 0; j--) {
86387 var tracej = fullData[j];
86388
86389 if((tracej.type === 'scatter') &&
86390 (tracej.xaxis === tracei.xaxis) &&
86391 (tracej.yaxis === tracei.yaxis)) {
86392 tracej.opacity = undefined;
86393 break;
86394 }
86395 }
86396 }
86397 }
86398};
86399
86400},{}],396:[function(_dereq_,module,exports){
86401/**
86402* Copyright 2012-2020, Plotly, Inc.
86403* All rights reserved.
86404*
86405* This source code is licensed under the MIT license found in the
86406* LICENSE file in the root directory of this source tree.
86407*/
86408
86409'use strict';
86410
86411var Lib = _dereq_('../../lib');
86412var Registry = _dereq_('../../registry');
86413
86414var attributes = _dereq_('./attributes');
86415var constants = _dereq_('./constants');
86416var subTypes = _dereq_('./subtypes');
86417var handleXYDefaults = _dereq_('./xy_defaults');
86418var handleStackDefaults = _dereq_('./stack_defaults');
86419var handleMarkerDefaults = _dereq_('./marker_defaults');
86420var handleLineDefaults = _dereq_('./line_defaults');
86421var handleLineShapeDefaults = _dereq_('./line_shape_defaults');
86422var handleTextDefaults = _dereq_('./text_defaults');
86423var handleFillColorDefaults = _dereq_('./fillcolor_defaults');
86424
86425module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
86426 function coerce(attr, dflt) {
86427 return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
86428 }
86429
86430 var len = handleXYDefaults(traceIn, traceOut, layout, coerce);
86431 if(!len) traceOut.visible = false;
86432
86433 if(!traceOut.visible) return;
86434
86435 var stackGroupOpts = handleStackDefaults(traceIn, traceOut, layout, coerce);
86436
86437 var defaultMode = !stackGroupOpts && (len < constants.PTS_LINESONLY) ?
86438 'lines+markers' : 'lines';
86439 coerce('text');
86440 coerce('hovertext');
86441 coerce('mode', defaultMode);
86442
86443 if(subTypes.hasLines(traceOut)) {
86444 handleLineDefaults(traceIn, traceOut, defaultColor, layout, coerce);
86445 handleLineShapeDefaults(traceIn, traceOut, coerce);
86446 coerce('connectgaps');
86447 coerce('line.simplify');
86448 }
86449
86450 if(subTypes.hasMarkers(traceOut)) {
86451 handleMarkerDefaults(traceIn, traceOut, defaultColor, layout, coerce, {gradient: true});
86452 }
86453
86454 if(subTypes.hasText(traceOut)) {
86455 coerce('texttemplate');
86456 handleTextDefaults(traceIn, traceOut, layout, coerce);
86457 }
86458
86459 var dfltHoverOn = [];
86460
86461 if(subTypes.hasMarkers(traceOut) || subTypes.hasText(traceOut)) {
86462 coerce('cliponaxis');
86463 coerce('marker.maxdisplayed');
86464 dfltHoverOn.push('points');
86465 }
86466
86467 // It's possible for this default to be changed by a later trace.
86468 // We handle that case in some hacky code inside handleStackDefaults.
86469 coerce('fill', stackGroupOpts ? stackGroupOpts.fillDflt : 'none');
86470 if(traceOut.fill !== 'none') {
86471 handleFillColorDefaults(traceIn, traceOut, defaultColor, coerce);
86472 if(!subTypes.hasLines(traceOut)) handleLineShapeDefaults(traceIn, traceOut, coerce);
86473 }
86474
86475 var lineColor = (traceOut.line || {}).color;
86476 var markerColor = (traceOut.marker || {}).color;
86477
86478 if(traceOut.fill === 'tonext' || traceOut.fill === 'toself') {
86479 dfltHoverOn.push('fills');
86480 }
86481 coerce('hoveron', dfltHoverOn.join('+') || 'points');
86482 if(traceOut.hoveron !== 'fills') coerce('hovertemplate');
86483 var errorBarsSupplyDefaults = Registry.getComponentMethod('errorbars', 'supplyDefaults');
86484 errorBarsSupplyDefaults(traceIn, traceOut, lineColor || markerColor || defaultColor, {axis: 'y'});
86485 errorBarsSupplyDefaults(traceIn, traceOut, lineColor || markerColor || defaultColor, {axis: 'x', inherit: 'y'});
86486
86487 Lib.coerceSelectionMarkerOpacity(traceOut, coerce);
86488};
86489
86490},{"../../lib":178,"../../registry":269,"./attributes":389,"./constants":393,"./fillcolor_defaults":397,"./line_defaults":402,"./line_shape_defaults":404,"./marker_defaults":408,"./stack_defaults":411,"./subtypes":413,"./text_defaults":414,"./xy_defaults":415}],397:[function(_dereq_,module,exports){
86491/**
86492* Copyright 2012-2020, Plotly, Inc.
86493* All rights reserved.
86494*
86495* This source code is licensed under the MIT license found in the
86496* LICENSE file in the root directory of this source tree.
86497*/
86498
86499
86500'use strict';
86501
86502var Color = _dereq_('../../components/color');
86503var isArrayOrTypedArray = _dereq_('../../lib').isArrayOrTypedArray;
86504
86505module.exports = function fillColorDefaults(traceIn, traceOut, defaultColor, coerce) {
86506 var inheritColorFromMarker = false;
86507
86508 if(traceOut.marker) {
86509 // don't try to inherit a color array
86510 var markerColor = traceOut.marker.color;
86511 var markerLineColor = (traceOut.marker.line || {}).color;
86512
86513 if(markerColor && !isArrayOrTypedArray(markerColor)) {
86514 inheritColorFromMarker = markerColor;
86515 } else if(markerLineColor && !isArrayOrTypedArray(markerLineColor)) {
86516 inheritColorFromMarker = markerLineColor;
86517 }
86518 }
86519
86520 coerce('fillcolor', Color.addOpacity(
86521 (traceOut.line || {}).color ||
86522 inheritColorFromMarker ||
86523 defaultColor, 0.5
86524 ));
86525};
86526
86527},{"../../components/color":52,"../../lib":178}],398:[function(_dereq_,module,exports){
86528/**
86529* Copyright 2012-2020, Plotly, Inc.
86530* All rights reserved.
86531*
86532* This source code is licensed under the MIT license found in the
86533* LICENSE file in the root directory of this source tree.
86534*/
86535
86536'use strict';
86537
86538var Axes = _dereq_('../../plots/cartesian/axes');
86539
86540module.exports = function formatLabels(cdi, trace, fullLayout) {
86541 var labels = {};
86542
86543 var mockGd = {_fullLayout: fullLayout};
86544 var xa = Axes.getFromTrace(mockGd, trace, 'x');
86545 var ya = Axes.getFromTrace(mockGd, trace, 'y');
86546
86547 labels.xLabel = Axes.tickText(xa, cdi.x, true).text;
86548 labels.yLabel = Axes.tickText(ya, cdi.y, true).text;
86549
86550 return labels;
86551};
86552
86553},{"../../plots/cartesian/axes":222}],399:[function(_dereq_,module,exports){
86554/**
86555* Copyright 2012-2020, Plotly, Inc.
86556* All rights reserved.
86557*
86558* This source code is licensed under the MIT license found in the
86559* LICENSE file in the root directory of this source tree.
86560*/
86561
86562
86563'use strict';
86564
86565var Color = _dereq_('../../components/color');
86566var subtypes = _dereq_('./subtypes');
86567
86568
86569module.exports = function getTraceColor(trace, di) {
86570 var lc, tc;
86571
86572 // TODO: text modes
86573
86574 if(trace.mode === 'lines') {
86575 lc = trace.line.color;
86576 return (lc && Color.opacity(lc)) ?
86577 lc : trace.fillcolor;
86578 } else if(trace.mode === 'none') {
86579 return trace.fill ? trace.fillcolor : '';
86580 } else {
86581 var mc = di.mcc || (trace.marker || {}).color;
86582 var mlc = di.mlcc || ((trace.marker || {}).line || {}).color;
86583
86584 tc = (mc && Color.opacity(mc)) ? mc :
86585 (mlc && Color.opacity(mlc) &&
86586 (di.mlw || ((trace.marker || {}).line || {}).width)) ? mlc : '';
86587
86588 if(tc) {
86589 // make sure the points aren't TOO transparent
86590 if(Color.opacity(tc) < 0.3) {
86591 return Color.addOpacity(tc, 0.3);
86592 } else return tc;
86593 } else {
86594 lc = (trace.line || {}).color;
86595 return (lc && Color.opacity(lc) &&
86596 subtypes.hasLines(trace) && trace.line.width) ?
86597 lc : trace.fillcolor;
86598 }
86599 }
86600};
86601
86602},{"../../components/color":52,"./subtypes":413}],400:[function(_dereq_,module,exports){
86603/**
86604* Copyright 2012-2020, Plotly, Inc.
86605* All rights reserved.
86606*
86607* This source code is licensed under the MIT license found in the
86608* LICENSE file in the root directory of this source tree.
86609*/
86610
86611'use strict';
86612
86613var Lib = _dereq_('../../lib');
86614var Fx = _dereq_('../../components/fx');
86615var Registry = _dereq_('../../registry');
86616var getTraceColor = _dereq_('./get_trace_color');
86617var Color = _dereq_('../../components/color');
86618var fillText = Lib.fillText;
86619
86620module.exports = function hoverPoints(pointData, xval, yval, hovermode) {
86621 var cd = pointData.cd;
86622 var trace = cd[0].trace;
86623 var xa = pointData.xa;
86624 var ya = pointData.ya;
86625 var xpx = xa.c2p(xval);
86626 var ypx = ya.c2p(yval);
86627 var pt = [xpx, ypx];
86628 var hoveron = trace.hoveron || '';
86629 var minRad = (trace.mode.indexOf('markers') !== -1) ? 3 : 0.5;
86630
86631 // look for points to hover on first, then take fills only if we
86632 // didn't find a point
86633 if(hoveron.indexOf('points') !== -1) {
86634 var dx = function(di) {
86635 // dx and dy are used in compare modes - here we want to always
86636 // prioritize the closest data point, at least as long as markers are
86637 // the same size or nonexistent, but still try to prioritize small markers too.
86638 var rad = Math.max(3, di.mrc || 0);
86639 var kink = 1 - 1 / rad;
86640 var dxRaw = Math.abs(xa.c2p(di.x) - xpx);
86641 var d = (dxRaw < rad) ? (kink * dxRaw / rad) : (dxRaw - rad + kink);
86642 return d;
86643 };
86644 var dy = function(di) {
86645 var rad = Math.max(3, di.mrc || 0);
86646 var kink = 1 - 1 / rad;
86647 var dyRaw = Math.abs(ya.c2p(di.y) - ypx);
86648 return (dyRaw < rad) ? (kink * dyRaw / rad) : (dyRaw - rad + kink);
86649 };
86650 var dxy = function(di) {
86651 // scatter points: d.mrc is the calculated marker radius
86652 // adjust the distance so if you're inside the marker it
86653 // always will show up regardless of point size, but
86654 // prioritize smaller points
86655 var rad = Math.max(minRad, di.mrc || 0);
86656 var dx = xa.c2p(di.x) - xpx;
86657 var dy = ya.c2p(di.y) - ypx;
86658 return Math.max(Math.sqrt(dx * dx + dy * dy) - rad, 1 - minRad / rad);
86659 };
86660 var distfn = Fx.getDistanceFunction(hovermode, dx, dy, dxy);
86661
86662 Fx.getClosest(cd, distfn, pointData);
86663
86664 // skip the rest (for this trace) if we didn't find a close point
86665 if(pointData.index !== false) {
86666 // the closest data point
86667 var di = cd[pointData.index];
86668 var xc = xa.c2p(di.x, true);
86669 var yc = ya.c2p(di.y, true);
86670 var rad = di.mrc || 1;
86671
86672 // now we're done using the whole `calcdata` array, replace the
86673 // index with the original index (in case of inserted point from
86674 // stacked area)
86675 pointData.index = di.i;
86676
86677 var orientation = cd[0].t.orientation;
86678 // TODO: for scatter and bar, option to show (sub)totals and
86679 // raw data? Currently stacked and/or normalized bars just show
86680 // the normalized individual sizes, so that's what I'm doing here
86681 // for now.
86682 var sizeVal = orientation && (di.sNorm || di.s);
86683 var xLabelVal = (orientation === 'h') ? sizeVal : di.x;
86684 var yLabelVal = (orientation === 'v') ? sizeVal : di.y;
86685
86686 Lib.extendFlat(pointData, {
86687 color: getTraceColor(trace, di),
86688
86689 x0: xc - rad,
86690 x1: xc + rad,
86691 xLabelVal: xLabelVal,
86692
86693 y0: yc - rad,
86694 y1: yc + rad,
86695 yLabelVal: yLabelVal,
86696
86697 spikeDistance: dxy(di),
86698 hovertemplate: trace.hovertemplate
86699 });
86700
86701 fillText(di, trace, pointData);
86702 Registry.getComponentMethod('errorbars', 'hoverInfo')(di, trace, pointData);
86703
86704 return [pointData];
86705 }
86706 }
86707
86708 // even if hoveron is 'fills', only use it if we have polygons too
86709 if(hoveron.indexOf('fills') !== -1 && trace._polygons) {
86710 var polygons = trace._polygons;
86711 var polygonsIn = [];
86712 var inside = false;
86713 var xmin = Infinity;
86714 var xmax = -Infinity;
86715 var ymin = Infinity;
86716 var ymax = -Infinity;
86717
86718 var i, j, polygon, pts, xCross, x0, x1, y0, y1;
86719
86720 for(i = 0; i < polygons.length; i++) {
86721 polygon = polygons[i];
86722 // TODO: this is not going to work right for curved edges, it will
86723 // act as though they're straight. That's probably going to need
86724 // the elements themselves to capture the events. Worth it?
86725 if(polygon.contains(pt)) {
86726 inside = !inside;
86727 // TODO: need better than just the overall bounding box
86728 polygonsIn.push(polygon);
86729 ymin = Math.min(ymin, polygon.ymin);
86730 ymax = Math.max(ymax, polygon.ymax);
86731 }
86732 }
86733
86734 if(inside) {
86735 // constrain ymin/max to the visible plot, so the label goes
86736 // at the middle of the piece you can see
86737 ymin = Math.max(ymin, 0);
86738 ymax = Math.min(ymax, ya._length);
86739
86740 // find the overall left-most and right-most points of the
86741 // polygon(s) we're inside at their combined vertical midpoint.
86742 // This is where we will draw the hover label.
86743 // Note that this might not be the vertical midpoint of the
86744 // whole trace, if it's disjoint.
86745 var yAvg = (ymin + ymax) / 2;
86746 for(i = 0; i < polygonsIn.length; i++) {
86747 pts = polygonsIn[i].pts;
86748 for(j = 1; j < pts.length; j++) {
86749 y0 = pts[j - 1][1];
86750 y1 = pts[j][1];
86751 if((y0 > yAvg) !== (y1 >= yAvg)) {
86752 x0 = pts[j - 1][0];
86753 x1 = pts[j][0];
86754 if(y1 - y0) {
86755 xCross = x0 + (x1 - x0) * (yAvg - y0) / (y1 - y0);
86756 xmin = Math.min(xmin, xCross);
86757 xmax = Math.max(xmax, xCross);
86758 }
86759 }
86760 }
86761 }
86762
86763 // constrain xmin/max to the visible plot now too
86764 xmin = Math.max(xmin, 0);
86765 xmax = Math.min(xmax, xa._length);
86766
86767 // get only fill or line color for the hover color
86768 var color = Color.defaultLine;
86769 if(Color.opacity(trace.fillcolor)) color = trace.fillcolor;
86770 else if(Color.opacity((trace.line || {}).color)) {
86771 color = trace.line.color;
86772 }
86773
86774 Lib.extendFlat(pointData, {
86775 // never let a 2D override 1D type as closest point
86776 // also: no spikeDistance, it's not allowed for fills
86777 distance: pointData.maxHoverDistance,
86778 x0: xmin,
86779 x1: xmax,
86780 y0: yAvg,
86781 y1: yAvg,
86782 color: color,
86783 hovertemplate: false
86784 });
86785
86786 delete pointData.index;
86787
86788 if(trace.text && !Array.isArray(trace.text)) {
86789 pointData.text = String(trace.text);
86790 } else pointData.text = trace.name;
86791
86792 return [pointData];
86793 }
86794 }
86795};
86796
86797},{"../../components/color":52,"../../components/fx":92,"../../lib":178,"../../registry":269,"./get_trace_color":399}],401:[function(_dereq_,module,exports){
86798/**
86799* Copyright 2012-2020, Plotly, Inc.
86800* All rights reserved.
86801*
86802* This source code is licensed under the MIT license found in the
86803* LICENSE file in the root directory of this source tree.
86804*/
86805
86806'use strict';
86807
86808var subtypes = _dereq_('./subtypes');
86809
86810module.exports = {
86811 hasLines: subtypes.hasLines,
86812 hasMarkers: subtypes.hasMarkers,
86813 hasText: subtypes.hasText,
86814 isBubble: subtypes.isBubble,
86815
86816 attributes: _dereq_('./attributes'),
86817 supplyDefaults: _dereq_('./defaults'),
86818 crossTraceDefaults: _dereq_('./cross_trace_defaults'),
86819 calc: _dereq_('./calc').calc,
86820 crossTraceCalc: _dereq_('./cross_trace_calc'),
86821 arraysToCalcdata: _dereq_('./arrays_to_calcdata'),
86822 plot: _dereq_('./plot'),
86823 colorbar: _dereq_('./marker_colorbar'),
86824 formatLabels: _dereq_('./format_labels'),
86825 style: _dereq_('./style').style,
86826 styleOnSelect: _dereq_('./style').styleOnSelect,
86827 hoverPoints: _dereq_('./hover'),
86828 selectPoints: _dereq_('./select'),
86829 animatable: true,
86830
86831 moduleType: 'trace',
86832 name: 'scatter',
86833 basePlotModule: _dereq_('../../plots/cartesian'),
86834 categories: [
86835 'cartesian', 'svg', 'symbols', 'errorBarsOK', 'showLegend', 'scatter-like',
86836 'zoomScale'
86837 ],
86838 meta: {
86839
86840 }
86841};
86842
86843},{"../../plots/cartesian":235,"./arrays_to_calcdata":388,"./attributes":389,"./calc":390,"./cross_trace_calc":394,"./cross_trace_defaults":395,"./defaults":396,"./format_labels":398,"./hover":400,"./marker_colorbar":407,"./plot":409,"./select":410,"./style":412,"./subtypes":413}],402:[function(_dereq_,module,exports){
86844/**
86845* Copyright 2012-2020, Plotly, Inc.
86846* All rights reserved.
86847*
86848* This source code is licensed under the MIT license found in the
86849* LICENSE file in the root directory of this source tree.
86850*/
86851
86852'use strict';
86853
86854var isArrayOrTypedArray = _dereq_('../../lib').isArrayOrTypedArray;
86855var hasColorscale = _dereq_('../../components/colorscale/helpers').hasColorscale;
86856var colorscaleDefaults = _dereq_('../../components/colorscale/defaults');
86857
86858module.exports = function lineDefaults(traceIn, traceOut, defaultColor, layout, coerce, opts) {
86859 var markerColor = (traceIn.marker || {}).color;
86860
86861 coerce('line.color', defaultColor);
86862
86863 if(hasColorscale(traceIn, 'line')) {
86864 colorscaleDefaults(traceIn, traceOut, layout, coerce, {prefix: 'line.', cLetter: 'c'});
86865 } else {
86866 var lineColorDflt = (isArrayOrTypedArray(markerColor) ? false : markerColor) || defaultColor;
86867 coerce('line.color', lineColorDflt);
86868 }
86869
86870 coerce('line.width');
86871 if(!(opts || {}).noDash) coerce('line.dash');
86872};
86873
86874},{"../../components/colorscale/defaults":62,"../../components/colorscale/helpers":63,"../../lib":178}],403:[function(_dereq_,module,exports){
86875/**
86876* Copyright 2012-2020, Plotly, Inc.
86877* All rights reserved.
86878*
86879* This source code is licensed under the MIT license found in the
86880* LICENSE file in the root directory of this source tree.
86881*/
86882
86883
86884'use strict';
86885
86886var numConstants = _dereq_('../../constants/numerical');
86887var BADNUM = numConstants.BADNUM;
86888var LOG_CLIP = numConstants.LOG_CLIP;
86889var LOG_CLIP_PLUS = LOG_CLIP + 0.5;
86890var LOG_CLIP_MINUS = LOG_CLIP - 0.5;
86891var Lib = _dereq_('../../lib');
86892var segmentsIntersect = Lib.segmentsIntersect;
86893var constrain = Lib.constrain;
86894var constants = _dereq_('./constants');
86895
86896
86897module.exports = function linePoints(d, opts) {
86898 var xa = opts.xaxis;
86899 var ya = opts.yaxis;
86900 var xLog = xa.type === 'log';
86901 var yLog = ya.type === 'log';
86902 var xLen = xa._length;
86903 var yLen = ya._length;
86904 var connectGaps = opts.connectGaps;
86905 var baseTolerance = opts.baseTolerance;
86906 var shape = opts.shape;
86907 var linear = shape === 'linear';
86908 var fill = opts.fill && opts.fill !== 'none';
86909 var segments = [];
86910 var minTolerance = constants.minTolerance;
86911 var len = d.length;
86912 var pts = new Array(len);
86913 var pti = 0;
86914
86915 var i;
86916
86917 // pt variables are pixel coordinates [x,y] of one point
86918 // these four are the outputs of clustering on a line
86919 var clusterStartPt, clusterEndPt, clusterHighPt, clusterLowPt;
86920
86921 // "this" is the next point we're considering adding to the cluster
86922 var thisPt;
86923
86924 // did we encounter the high point first, then a low point, or vice versa?
86925 var clusterHighFirst;
86926
86927 // the first two points in the cluster determine its unit vector
86928 // so the second is always in the "High" direction
86929 var clusterUnitVector;
86930
86931 // the pixel delta from clusterStartPt
86932 var thisVector;
86933
86934 // val variables are (signed) pixel distances along the cluster vector
86935 var clusterRefDist, clusterHighVal, clusterLowVal, thisVal;
86936
86937 // deviation variables are (signed) pixel distances normal to the cluster vector
86938 var clusterMinDeviation, clusterMaxDeviation, thisDeviation;
86939
86940 // turn one calcdata point into pixel coordinates
86941 function getPt(index) {
86942 var di = d[index];
86943 if(!di) return false;
86944 var x = opts.linearized ? xa.l2p(di.x) : xa.c2p(di.x);
86945 var y = opts.linearized ? ya.l2p(di.y) : ya.c2p(di.y);
86946
86947 // if non-positive log values, set them VERY far off-screen
86948 // so the line looks essentially straight from the previous point.
86949 if(x === BADNUM) {
86950 if(xLog) x = xa.c2p(di.x, true);
86951 if(x === BADNUM) return false;
86952 // If BOTH were bad log values, make the line follow a constant
86953 // exponent rather than a constant slope
86954 if(yLog && y === BADNUM) {
86955 x *= Math.abs(xa._m * yLen * (xa._m > 0 ? LOG_CLIP_PLUS : LOG_CLIP_MINUS) /
86956 (ya._m * xLen * (ya._m > 0 ? LOG_CLIP_PLUS : LOG_CLIP_MINUS)));
86957 }
86958 x *= 1000;
86959 }
86960 if(y === BADNUM) {
86961 if(yLog) y = ya.c2p(di.y, true);
86962 if(y === BADNUM) return false;
86963 y *= 1000;
86964 }
86965 return [x, y];
86966 }
86967
86968 function crossesViewport(xFrac0, yFrac0, xFrac1, yFrac1) {
86969 var dx = xFrac1 - xFrac0;
86970 var dy = yFrac1 - yFrac0;
86971 var dx0 = 0.5 - xFrac0;
86972 var dy0 = 0.5 - yFrac0;
86973 var norm2 = dx * dx + dy * dy;
86974 var dot = dx * dx0 + dy * dy0;
86975 if(dot > 0 && dot < norm2) {
86976 var cross = dx0 * dy - dy0 * dx;
86977 if(cross * cross < norm2) return true;
86978 }
86979 }
86980
86981 var latestXFrac, latestYFrac;
86982 // if we're off-screen, increase tolerance over baseTolerance
86983 function getTolerance(pt, nextPt) {
86984 var xFrac = pt[0] / xLen;
86985 var yFrac = pt[1] / yLen;
86986 var offScreenFraction = Math.max(0, -xFrac, xFrac - 1, -yFrac, yFrac - 1);
86987 if(offScreenFraction && (latestXFrac !== undefined) &&
86988 crossesViewport(xFrac, yFrac, latestXFrac, latestYFrac)
86989 ) {
86990 offScreenFraction = 0;
86991 }
86992 if(offScreenFraction && nextPt &&
86993 crossesViewport(xFrac, yFrac, nextPt[0] / xLen, nextPt[1] / yLen)
86994 ) {
86995 offScreenFraction = 0;
86996 }
86997
86998 return (1 + constants.toleranceGrowth * offScreenFraction) * baseTolerance;
86999 }
87000
87001 function ptDist(pt1, pt2) {
87002 var dx = pt1[0] - pt2[0];
87003 var dy = pt1[1] - pt2[1];
87004 return Math.sqrt(dx * dx + dy * dy);
87005 }
87006
87007 // last bit of filtering: clip paths that are VERY far off-screen
87008 // so we don't get near the browser's hard limit (+/- 2^29 px in Chrome and FF)
87009
87010 var maxScreensAway = constants.maxScreensAway;
87011
87012 // find the intersections between the segment from pt1 to pt2
87013 // and the large rectangle maxScreensAway around the viewport
87014 // if one of pt1 and pt2 is inside and the other outside, there
87015 // will be only one intersection.
87016 // if both are outside there will be 0 or 2 intersections
87017 // (or 1 if it's right at a corner - we'll treat that like 0)
87018 // returns an array of intersection pts
87019 var xEdge0 = -xLen * maxScreensAway;
87020 var xEdge1 = xLen * (1 + maxScreensAway);
87021 var yEdge0 = -yLen * maxScreensAway;
87022 var yEdge1 = yLen * (1 + maxScreensAway);
87023 var edges = [
87024 [xEdge0, yEdge0, xEdge1, yEdge0],
87025 [xEdge1, yEdge0, xEdge1, yEdge1],
87026 [xEdge1, yEdge1, xEdge0, yEdge1],
87027 [xEdge0, yEdge1, xEdge0, yEdge0]
87028 ];
87029 var xEdge, yEdge, lastXEdge, lastYEdge, lastFarPt, edgePt;
87030
87031 // for linear line shape, edge intersections should be linearly interpolated
87032 // spline uses this too, which isn't precisely correct but is actually pretty
87033 // good, because Catmull-Rom weights far-away points less in creating the curvature
87034 function getLinearEdgeIntersections(pt1, pt2) {
87035 var out = [];
87036 var ptCount = 0;
87037 for(var i = 0; i < 4; i++) {
87038 var edge = edges[i];
87039 var ptInt = segmentsIntersect(
87040 pt1[0], pt1[1], pt2[0], pt2[1],
87041 edge[0], edge[1], edge[2], edge[3]
87042 );
87043 if(ptInt && (!ptCount ||
87044 Math.abs(ptInt.x - out[0][0]) > 1 ||
87045 Math.abs(ptInt.y - out[0][1]) > 1
87046 )) {
87047 ptInt = [ptInt.x, ptInt.y];
87048 // if we have 2 intersections, make sure the closest one to pt1 comes first
87049 if(ptCount && ptDist(ptInt, pt1) < ptDist(out[0], pt1)) out.unshift(ptInt);
87050 else out.push(ptInt);
87051 ptCount++;
87052 }
87053 }
87054 return out;
87055 }
87056
87057 function onlyConstrainedPoint(pt) {
87058 if(pt[0] < xEdge0 || pt[0] > xEdge1 || pt[1] < yEdge0 || pt[1] > yEdge1) {
87059 return [constrain(pt[0], xEdge0, xEdge1), constrain(pt[1], yEdge0, yEdge1)];
87060 }
87061 }
87062
87063 function sameEdge(pt1, pt2) {
87064 if(pt1[0] === pt2[0] && (pt1[0] === xEdge0 || pt1[0] === xEdge1)) return true;
87065 if(pt1[1] === pt2[1] && (pt1[1] === yEdge0 || pt1[1] === yEdge1)) return true;
87066 }
87067
87068 // for line shapes hv and vh, movement in the two dimensions is decoupled,
87069 // so all we need to do is constrain each dimension independently
87070 function getHVEdgeIntersections(pt1, pt2) {
87071 var out = [];
87072 var ptInt1 = onlyConstrainedPoint(pt1);
87073 var ptInt2 = onlyConstrainedPoint(pt2);
87074 if(ptInt1 && ptInt2 && sameEdge(ptInt1, ptInt2)) return out;
87075
87076 if(ptInt1) out.push(ptInt1);
87077 if(ptInt2) out.push(ptInt2);
87078 return out;
87079 }
87080
87081 // hvh and vhv we sometimes have to move one of the intersection points
87082 // out BEYOND the clipping rect, by a maximum of a factor of 2, so that
87083 // the midpoint line is drawn in the right place
87084 function getABAEdgeIntersections(dim, limit0, limit1) {
87085 return function(pt1, pt2) {
87086 var ptInt1 = onlyConstrainedPoint(pt1);
87087 var ptInt2 = onlyConstrainedPoint(pt2);
87088
87089 var out = [];
87090 if(ptInt1 && ptInt2 && sameEdge(ptInt1, ptInt2)) return out;
87091
87092 if(ptInt1) out.push(ptInt1);
87093 if(ptInt2) out.push(ptInt2);
87094
87095 var midShift = 2 * Lib.constrain((pt1[dim] + pt2[dim]) / 2, limit0, limit1) -
87096 ((ptInt1 || pt1)[dim] + (ptInt2 || pt2)[dim]);
87097 if(midShift) {
87098 var ptToAlter;
87099 if(ptInt1 && ptInt2) {
87100 ptToAlter = (midShift > 0 === ptInt1[dim] > ptInt2[dim]) ? ptInt1 : ptInt2;
87101 } else ptToAlter = ptInt1 || ptInt2;
87102
87103 ptToAlter[dim] += midShift;
87104 }
87105
87106 return out;
87107 };
87108 }
87109
87110 var getEdgeIntersections;
87111 if(shape === 'linear' || shape === 'spline') {
87112 getEdgeIntersections = getLinearEdgeIntersections;
87113 } else if(shape === 'hv' || shape === 'vh') {
87114 getEdgeIntersections = getHVEdgeIntersections;
87115 } else if(shape === 'hvh') getEdgeIntersections = getABAEdgeIntersections(0, xEdge0, xEdge1);
87116 else if(shape === 'vhv') getEdgeIntersections = getABAEdgeIntersections(1, yEdge0, yEdge1);
87117
87118 // a segment pt1->pt2 entirely outside the nearby region:
87119 // find the corner it gets closest to touching
87120 function getClosestCorner(pt1, pt2) {
87121 var dx = pt2[0] - pt1[0];
87122 var m = (pt2[1] - pt1[1]) / dx;
87123 var b = (pt1[1] * pt2[0] - pt2[1] * pt1[0]) / dx;
87124
87125 if(b > 0) return [m > 0 ? xEdge0 : xEdge1, yEdge1];
87126 else return [m > 0 ? xEdge1 : xEdge0, yEdge0];
87127 }
87128
87129 function updateEdge(pt) {
87130 var x = pt[0];
87131 var y = pt[1];
87132 var xSame = x === pts[pti - 1][0];
87133 var ySame = y === pts[pti - 1][1];
87134 // duplicate point?
87135 if(xSame && ySame) return;
87136 if(pti > 1) {
87137 // backtracking along an edge?
87138 var xSame2 = x === pts[pti - 2][0];
87139 var ySame2 = y === pts[pti - 2][1];
87140 if(xSame && (x === xEdge0 || x === xEdge1) && xSame2) {
87141 if(ySame2) pti--; // backtracking exactly - drop prev pt and don't add
87142 else pts[pti - 1] = pt; // not exact: replace the prev pt
87143 } else if(ySame && (y === yEdge0 || y === yEdge1) && ySame2) {
87144 if(xSame2) pti--;
87145 else pts[pti - 1] = pt;
87146 } else pts[pti++] = pt;
87147 } else pts[pti++] = pt;
87148 }
87149
87150 function updateEdgesForReentry(pt) {
87151 // if we're outside the nearby region and going back in,
87152 // we may need to loop around a corner point
87153 if(pts[pti - 1][0] !== pt[0] && pts[pti - 1][1] !== pt[1]) {
87154 updateEdge([lastXEdge, lastYEdge]);
87155 }
87156 updateEdge(pt);
87157 lastFarPt = null;
87158 lastXEdge = lastYEdge = 0;
87159 }
87160
87161 function addPt(pt) {
87162 latestXFrac = pt[0] / xLen;
87163 latestYFrac = pt[1] / yLen;
87164 // Are we more than maxScreensAway off-screen any direction?
87165 // if so, clip to this box, but in such a way that on-screen
87166 // drawing is unchanged
87167 xEdge = (pt[0] < xEdge0) ? xEdge0 : (pt[0] > xEdge1) ? xEdge1 : 0;
87168 yEdge = (pt[1] < yEdge0) ? yEdge0 : (pt[1] > yEdge1) ? yEdge1 : 0;
87169 if(xEdge || yEdge) {
87170 if(!pti) {
87171 // to get fills right - if first point is far, push it toward the
87172 // screen in whichever direction(s) are far
87173
87174 pts[pti++] = [xEdge || pt[0], yEdge || pt[1]];
87175 } else if(lastFarPt) {
87176 // both this point and the last are outside the nearby region
87177 // check if we're crossing the nearby region
87178 var intersections = getEdgeIntersections(lastFarPt, pt);
87179 if(intersections.length > 1) {
87180 updateEdgesForReentry(intersections[0]);
87181 pts[pti++] = intersections[1];
87182 }
87183 } else {
87184 // we're leaving the nearby region - add the point where we left it
87185
87186 edgePt = getEdgeIntersections(pts[pti - 1], pt)[0];
87187 pts[pti++] = edgePt;
87188 }
87189
87190 var lastPt = pts[pti - 1];
87191 if(xEdge && yEdge && (lastPt[0] !== xEdge || lastPt[1] !== yEdge)) {
87192 // we've gone out beyond a new corner: add the corner too
87193 // so that the next point will take the right winding
87194 if(lastFarPt) {
87195 if(lastXEdge !== xEdge && lastYEdge !== yEdge) {
87196 if(lastXEdge && lastYEdge) {
87197 // we've gone around to an opposite corner - we
87198 // need to add the correct extra corner
87199 // in order to get the right winding
87200 updateEdge(getClosestCorner(lastFarPt, pt));
87201 } else {
87202 // we're coming from a far edge - the extra corner
87203 // we need is determined uniquely by the sectors
87204 updateEdge([lastXEdge || xEdge, lastYEdge || yEdge]);
87205 }
87206 } else if(lastXEdge && lastYEdge) {
87207 updateEdge([lastXEdge, lastYEdge]);
87208 }
87209 }
87210 updateEdge([xEdge, yEdge]);
87211 } else if((lastXEdge - xEdge) && (lastYEdge - yEdge)) {
87212 // we're coming from an edge or far corner to an edge - again the
87213 // extra corner we need is uniquely determined by the sectors
87214 updateEdge([xEdge || lastXEdge, yEdge || lastYEdge]);
87215 }
87216 lastFarPt = pt;
87217 lastXEdge = xEdge;
87218 lastYEdge = yEdge;
87219 } else {
87220 if(lastFarPt) {
87221 // this point is in range but the previous wasn't: add its entry pt first
87222 updateEdgesForReentry(getEdgeIntersections(lastFarPt, pt)[0]);
87223 }
87224
87225 pts[pti++] = pt;
87226 }
87227 }
87228
87229 // loop over ALL points in this trace
87230 for(i = 0; i < len; i++) {
87231 clusterStartPt = getPt(i);
87232 if(!clusterStartPt) continue;
87233
87234 pti = 0;
87235 lastFarPt = null;
87236 addPt(clusterStartPt);
87237
87238 // loop over one segment of the trace
87239 for(i++; i < len; i++) {
87240 clusterHighPt = getPt(i);
87241 if(!clusterHighPt) {
87242 if(connectGaps) continue;
87243 else break;
87244 }
87245
87246 // can't decimate if nonlinear line shape
87247 // TODO: we *could* decimate [hv]{2,3} shapes if we restricted clusters to horz or vert again
87248 // but spline would be verrry awkward to decimate
87249 if(!linear || !opts.simplify) {
87250 addPt(clusterHighPt);
87251 continue;
87252 }
87253
87254 var nextPt = getPt(i + 1);
87255
87256 clusterRefDist = ptDist(clusterHighPt, clusterStartPt);
87257
87258 // #3147 - always include the very first and last points for fills
87259 if(!(fill && (pti === 0 || pti === len - 1)) &&
87260 clusterRefDist < getTolerance(clusterHighPt, nextPt) * minTolerance) continue;
87261
87262 clusterUnitVector = [
87263 (clusterHighPt[0] - clusterStartPt[0]) / clusterRefDist,
87264 (clusterHighPt[1] - clusterStartPt[1]) / clusterRefDist
87265 ];
87266
87267 clusterLowPt = clusterStartPt;
87268 clusterHighVal = clusterRefDist;
87269 clusterLowVal = clusterMinDeviation = clusterMaxDeviation = 0;
87270 clusterHighFirst = false;
87271 clusterEndPt = clusterHighPt;
87272
87273 // loop over one cluster of points that collapse onto one line
87274 for(i++; i < d.length; i++) {
87275 thisPt = nextPt;
87276 nextPt = getPt(i + 1);
87277 if(!thisPt) {
87278 if(connectGaps) continue;
87279 else break;
87280 }
87281 thisVector = [
87282 thisPt[0] - clusterStartPt[0],
87283 thisPt[1] - clusterStartPt[1]
87284 ];
87285 // cross product (or dot with normal to the cluster vector)
87286 thisDeviation = thisVector[0] * clusterUnitVector[1] - thisVector[1] * clusterUnitVector[0];
87287 clusterMinDeviation = Math.min(clusterMinDeviation, thisDeviation);
87288 clusterMaxDeviation = Math.max(clusterMaxDeviation, thisDeviation);
87289
87290 if(clusterMaxDeviation - clusterMinDeviation > getTolerance(thisPt, nextPt)) break;
87291
87292 clusterEndPt = thisPt;
87293 thisVal = thisVector[0] * clusterUnitVector[0] + thisVector[1] * clusterUnitVector[1];
87294
87295 if(thisVal > clusterHighVal) {
87296 clusterHighVal = thisVal;
87297 clusterHighPt = thisPt;
87298 clusterHighFirst = false;
87299 } else if(thisVal < clusterLowVal) {
87300 clusterLowVal = thisVal;
87301 clusterLowPt = thisPt;
87302 clusterHighFirst = true;
87303 }
87304 }
87305
87306 // insert this cluster into pts
87307 // we've already inserted the start pt, now check if we have high and low pts
87308 if(clusterHighFirst) {
87309 addPt(clusterHighPt);
87310 if(clusterEndPt !== clusterLowPt) addPt(clusterLowPt);
87311 } else {
87312 if(clusterLowPt !== clusterStartPt) addPt(clusterLowPt);
87313 if(clusterEndPt !== clusterHighPt) addPt(clusterHighPt);
87314 }
87315 // and finally insert the end pt
87316 addPt(clusterEndPt);
87317
87318 // have we reached the end of this segment?
87319 if(i >= d.length || !thisPt) break;
87320
87321 // otherwise we have an out-of-cluster point to insert as next clusterStartPt
87322 addPt(thisPt);
87323 clusterStartPt = thisPt;
87324 }
87325
87326 // to get fills right - repeat what we did at the start
87327 if(lastFarPt) updateEdge([lastXEdge || lastFarPt[0], lastYEdge || lastFarPt[1]]);
87328
87329 segments.push(pts.slice(0, pti));
87330 }
87331
87332 return segments;
87333};
87334
87335},{"../../constants/numerical":158,"../../lib":178,"./constants":393}],404:[function(_dereq_,module,exports){
87336/**
87337* Copyright 2012-2020, Plotly, Inc.
87338* All rights reserved.
87339*
87340* This source code is licensed under the MIT license found in the
87341* LICENSE file in the root directory of this source tree.
87342*/
87343
87344
87345'use strict';
87346
87347
87348// common to 'scatter' and 'scatterternary'
87349module.exports = function handleLineShapeDefaults(traceIn, traceOut, coerce) {
87350 var shape = coerce('line.shape');
87351 if(shape === 'spline') coerce('line.smoothing');
87352};
87353
87354},{}],405:[function(_dereq_,module,exports){
87355/**
87356* Copyright 2012-2020, Plotly, Inc.
87357* All rights reserved.
87358*
87359* This source code is licensed under the MIT license found in the
87360* LICENSE file in the root directory of this source tree.
87361*/
87362
87363'use strict';
87364
87365var LINKEDFILLS = {tonextx: 1, tonexty: 1, tonext: 1};
87366
87367module.exports = function linkTraces(gd, plotinfo, cdscatter) {
87368 var trace, i, group, prevtrace, groupIndex;
87369
87370 // first sort traces to keep stacks & filled-together groups together
87371 var groupIndices = {};
87372 var needsSort = false;
87373 var prevGroupIndex = -1;
87374 var nextGroupIndex = 0;
87375 var prevUnstackedGroupIndex = -1;
87376 for(i = 0; i < cdscatter.length; i++) {
87377 trace = cdscatter[i][0].trace;
87378 group = trace.stackgroup || '';
87379 if(group) {
87380 if(group in groupIndices) {
87381 groupIndex = groupIndices[group];
87382 } else {
87383 groupIndex = groupIndices[group] = nextGroupIndex;
87384 nextGroupIndex++;
87385 }
87386 } else if(trace.fill in LINKEDFILLS && prevUnstackedGroupIndex >= 0) {
87387 groupIndex = prevUnstackedGroupIndex;
87388 } else {
87389 groupIndex = prevUnstackedGroupIndex = nextGroupIndex;
87390 nextGroupIndex++;
87391 }
87392
87393 if(groupIndex < prevGroupIndex) needsSort = true;
87394 trace._groupIndex = prevGroupIndex = groupIndex;
87395 }
87396
87397 var cdscatterSorted = cdscatter.slice();
87398 if(needsSort) {
87399 cdscatterSorted.sort(function(a, b) {
87400 var traceA = a[0].trace;
87401 var traceB = b[0].trace;
87402 return (traceA._groupIndex - traceB._groupIndex) ||
87403 (traceA.index - traceB.index);
87404 });
87405 }
87406
87407 // now link traces to each other
87408 var prevtraces = {};
87409 for(i = 0; i < cdscatterSorted.length; i++) {
87410 trace = cdscatterSorted[i][0].trace;
87411 group = trace.stackgroup || '';
87412
87413 // Note: The check which ensures all cdscatter here are for the same axis and
87414 // are either cartesian or scatterternary has been removed. This code assumes
87415 // the passed scattertraces have been filtered to the proper plot types and
87416 // the proper subplots.
87417 if(trace.visible === true) {
87418 trace._nexttrace = null;
87419
87420 if(trace.fill in LINKEDFILLS) {
87421 prevtrace = prevtraces[group];
87422 trace._prevtrace = prevtrace || null;
87423
87424 if(prevtrace) {
87425 prevtrace._nexttrace = trace;
87426 }
87427 }
87428
87429 trace._ownfill = (trace.fill && (
87430 trace.fill.substr(0, 6) === 'tozero' ||
87431 trace.fill === 'toself' ||
87432 (trace.fill.substr(0, 2) === 'to' && !trace._prevtrace)
87433 ));
87434
87435 prevtraces[group] = trace;
87436 } else {
87437 trace._prevtrace = trace._nexttrace = trace._ownfill = null;
87438 }
87439 }
87440
87441 return cdscatterSorted;
87442};
87443
87444},{}],406:[function(_dereq_,module,exports){
87445/**
87446* Copyright 2012-2020, Plotly, Inc.
87447* All rights reserved.
87448*
87449* This source code is licensed under the MIT license found in the
87450* LICENSE file in the root directory of this source tree.
87451*/
87452
87453
87454'use strict';
87455
87456var isNumeric = _dereq_('fast-isnumeric');
87457
87458
87459// used in the drawing step for 'scatter' and 'scattegeo' and
87460// in the convert step for 'scatter3d'
87461module.exports = function makeBubbleSizeFn(trace) {
87462 var marker = trace.marker;
87463 var sizeRef = marker.sizeref || 1;
87464 var sizeMin = marker.sizemin || 0;
87465
87466 // for bubble charts, allow scaling the provided value linearly
87467 // and by area or diameter.
87468 // Note this only applies to the array-value sizes
87469
87470 var baseFn = (marker.sizemode === 'area') ?
87471 function(v) { return Math.sqrt(v / sizeRef); } :
87472 function(v) { return v / sizeRef; };
87473
87474 // TODO add support for position/negative bubbles?
87475 // TODO add 'sizeoffset' attribute?
87476 return function(v) {
87477 var baseSize = baseFn(v / 2);
87478
87479 // don't show non-numeric and negative sizes
87480 return (isNumeric(baseSize) && (baseSize > 0)) ?
87481 Math.max(baseSize, sizeMin) :
87482 0;
87483 };
87484};
87485
87486},{"fast-isnumeric":18}],407:[function(_dereq_,module,exports){
87487/**
87488* Copyright 2012-2020, Plotly, Inc.
87489* All rights reserved.
87490*
87491* This source code is licensed under the MIT license found in the
87492* LICENSE file in the root directory of this source tree.
87493*/
87494
87495
87496'use strict';
87497
87498module.exports = {
87499 container: 'marker',
87500 min: 'cmin',
87501 max: 'cmax'
87502};
87503
87504},{}],408:[function(_dereq_,module,exports){
87505/**
87506* Copyright 2012-2020, Plotly, Inc.
87507* All rights reserved.
87508*
87509* This source code is licensed under the MIT license found in the
87510* LICENSE file in the root directory of this source tree.
87511*/
87512
87513'use strict';
87514
87515var Color = _dereq_('../../components/color');
87516var hasColorscale = _dereq_('../../components/colorscale/helpers').hasColorscale;
87517var colorscaleDefaults = _dereq_('../../components/colorscale/defaults');
87518
87519var subTypes = _dereq_('./subtypes');
87520
87521/*
87522 * opts: object of flags to control features not all marker users support
87523 * noLine: caller does not support marker lines
87524 * gradient: caller supports gradients
87525 * noSelect: caller does not support selected/unselected attribute containers
87526 */
87527module.exports = function markerDefaults(traceIn, traceOut, defaultColor, layout, coerce, opts) {
87528 var isBubble = subTypes.isBubble(traceIn);
87529 var lineColor = (traceIn.line || {}).color;
87530 var defaultMLC;
87531
87532 opts = opts || {};
87533
87534 // marker.color inherit from line.color (even if line.color is an array)
87535 if(lineColor) defaultColor = lineColor;
87536
87537 coerce('marker.symbol');
87538 coerce('marker.opacity', isBubble ? 0.7 : 1);
87539 coerce('marker.size');
87540
87541 coerce('marker.color', defaultColor);
87542 if(hasColorscale(traceIn, 'marker')) {
87543 colorscaleDefaults(traceIn, traceOut, layout, coerce, {prefix: 'marker.', cLetter: 'c'});
87544 }
87545
87546 if(!opts.noSelect) {
87547 coerce('selected.marker.color');
87548 coerce('unselected.marker.color');
87549 coerce('selected.marker.size');
87550 coerce('unselected.marker.size');
87551 }
87552
87553 if(!opts.noLine) {
87554 // if there's a line with a different color than the marker, use
87555 // that line color as the default marker line color
87556 // (except when it's an array)
87557 // mostly this is for transparent markers to behave nicely
87558 if(lineColor && !Array.isArray(lineColor) && (traceOut.marker.color !== lineColor)) {
87559 defaultMLC = lineColor;
87560 } else if(isBubble) defaultMLC = Color.background;
87561 else defaultMLC = Color.defaultLine;
87562
87563 coerce('marker.line.color', defaultMLC);
87564 if(hasColorscale(traceIn, 'marker.line')) {
87565 colorscaleDefaults(traceIn, traceOut, layout, coerce, {prefix: 'marker.line.', cLetter: 'c'});
87566 }
87567
87568 coerce('marker.line.width', isBubble ? 1 : 0);
87569 }
87570
87571 if(isBubble) {
87572 coerce('marker.sizeref');
87573 coerce('marker.sizemin');
87574 coerce('marker.sizemode');
87575 }
87576
87577 if(opts.gradient) {
87578 var gradientType = coerce('marker.gradient.type');
87579 if(gradientType !== 'none') {
87580 coerce('marker.gradient.color');
87581 }
87582 }
87583};
87584
87585},{"../../components/color":52,"../../components/colorscale/defaults":62,"../../components/colorscale/helpers":63,"./subtypes":413}],409:[function(_dereq_,module,exports){
87586/**
87587* Copyright 2012-2020, Plotly, Inc.
87588* All rights reserved.
87589*
87590* This source code is licensed under the MIT license found in the
87591* LICENSE file in the root directory of this source tree.
87592*/
87593
87594
87595'use strict';
87596
87597var d3 = _dereq_('d3');
87598
87599var Registry = _dereq_('../../registry');
87600var Lib = _dereq_('../../lib');
87601var ensureSingle = Lib.ensureSingle;
87602var identity = Lib.identity;
87603var Drawing = _dereq_('../../components/drawing');
87604
87605var subTypes = _dereq_('./subtypes');
87606var linePoints = _dereq_('./line_points');
87607var linkTraces = _dereq_('./link_traces');
87608var polygonTester = _dereq_('../../lib/polygon').tester;
87609
87610module.exports = function plot(gd, plotinfo, cdscatter, scatterLayer, transitionOpts, makeOnCompleteCallback) {
87611 var join, onComplete;
87612
87613 // If transition config is provided, then it is only a partial replot and traces not
87614 // updated are removed.
87615 var isFullReplot = !transitionOpts;
87616 var hasTransition = !!transitionOpts && transitionOpts.duration > 0;
87617
87618 // Link traces so the z-order of fill layers is correct
87619 var cdscatterSorted = linkTraces(gd, plotinfo, cdscatter);
87620
87621 join = scatterLayer.selectAll('g.trace')
87622 .data(cdscatterSorted, function(d) { return d[0].trace.uid; });
87623
87624 // Append new traces:
87625 join.enter().append('g')
87626 .attr('class', function(d) {
87627 return 'trace scatter trace' + d[0].trace.uid;
87628 })
87629 .style('stroke-miterlimit', 2);
87630 join.order();
87631
87632 createFills(gd, join, plotinfo);
87633
87634 if(hasTransition) {
87635 if(makeOnCompleteCallback) {
87636 // If it was passed a callback to register completion, make a callback. If
87637 // this is created, then it must be executed on completion, otherwise the
87638 // pos-transition redraw will not execute:
87639 onComplete = makeOnCompleteCallback();
87640 }
87641
87642 var transition = d3.transition()
87643 .duration(transitionOpts.duration)
87644 .ease(transitionOpts.easing)
87645 .each('end', function() {
87646 onComplete && onComplete();
87647 })
87648 .each('interrupt', function() {
87649 onComplete && onComplete();
87650 });
87651
87652 transition.each(function() {
87653 // Must run the selection again since otherwise enters/updates get grouped together
87654 // and these get executed out of order. Except we need them in order!
87655 scatterLayer.selectAll('g.trace').each(function(d, i) {
87656 plotOne(gd, i, plotinfo, d, cdscatterSorted, this, transitionOpts);
87657 });
87658 });
87659 } else {
87660 join.each(function(d, i) {
87661 plotOne(gd, i, plotinfo, d, cdscatterSorted, this, transitionOpts);
87662 });
87663 }
87664
87665 if(isFullReplot) {
87666 join.exit().remove();
87667 }
87668
87669 // remove paths that didn't get used
87670 scatterLayer.selectAll('path:not([d])').remove();
87671};
87672
87673function createFills(gd, traceJoin, plotinfo) {
87674 traceJoin.each(function(d) {
87675 var fills = ensureSingle(d3.select(this), 'g', 'fills');
87676 Drawing.setClipUrl(fills, plotinfo.layerClipId, gd);
87677
87678 var trace = d[0].trace;
87679
87680 var fillData = [];
87681 if(trace._ownfill) fillData.push('_ownFill');
87682 if(trace._nexttrace) fillData.push('_nextFill');
87683
87684 var fillJoin = fills.selectAll('g').data(fillData, identity);
87685
87686 fillJoin.enter().append('g');
87687
87688 fillJoin.exit()
87689 .each(function(d) { trace[d] = null; })
87690 .remove();
87691
87692 fillJoin.order().each(function(d) {
87693 // make a path element inside the fill group, just so
87694 // we can give it its own data later on and the group can
87695 // keep its simple '_*Fill' data
87696 trace[d] = ensureSingle(d3.select(this), 'path', 'js-fill');
87697 });
87698 });
87699}
87700
87701function plotOne(gd, idx, plotinfo, cdscatter, cdscatterAll, element, transitionOpts) {
87702 var i;
87703
87704 // Since this has been reorganized and we're executing this on individual traces,
87705 // we need to pass it the full list of cdscatter as well as this trace's index (idx)
87706 // since it does an internal n^2 loop over comparisons with other traces:
87707 selectMarkers(gd, idx, plotinfo, cdscatter, cdscatterAll);
87708
87709 var hasTransition = !!transitionOpts && transitionOpts.duration > 0;
87710
87711 function transition(selection) {
87712 return hasTransition ? selection.transition() : selection;
87713 }
87714
87715 var xa = plotinfo.xaxis;
87716 var ya = plotinfo.yaxis;
87717
87718 var trace = cdscatter[0].trace;
87719 var line = trace.line;
87720 var tr = d3.select(element);
87721
87722 var errorBarGroup = ensureSingle(tr, 'g', 'errorbars');
87723 var lines = ensureSingle(tr, 'g', 'lines');
87724 var points = ensureSingle(tr, 'g', 'points');
87725 var text = ensureSingle(tr, 'g', 'text');
87726
87727 // error bars are at the bottom
87728 Registry.getComponentMethod('errorbars', 'plot')(gd, errorBarGroup, plotinfo, transitionOpts);
87729
87730 if(trace.visible !== true) return;
87731
87732 transition(tr).style('opacity', trace.opacity);
87733
87734 // BUILD LINES AND FILLS
87735 var ownFillEl3, tonext;
87736 var ownFillDir = trace.fill.charAt(trace.fill.length - 1);
87737 if(ownFillDir !== 'x' && ownFillDir !== 'y') ownFillDir = '';
87738
87739 // store node for tweaking by selectPoints
87740 cdscatter[0][plotinfo.isRangePlot ? 'nodeRangePlot3' : 'node3'] = tr;
87741
87742 var prevRevpath = '';
87743 var prevPolygons = [];
87744 var prevtrace = trace._prevtrace;
87745
87746 if(prevtrace) {
87747 prevRevpath = prevtrace._prevRevpath || '';
87748 tonext = prevtrace._nextFill;
87749 prevPolygons = prevtrace._polygons;
87750 }
87751
87752 var thispath;
87753 var thisrevpath;
87754 // fullpath is all paths for this curve, joined together straight
87755 // across gaps, for filling
87756 var fullpath = '';
87757 // revpath is fullpath reversed, for fill-to-next
87758 var revpath = '';
87759 // functions for converting a point array to a path
87760 var pathfn, revpathbase, revpathfn;
87761 // variables used before and after the data join
87762 var pt0, lastSegment, pt1, thisPolygons;
87763
87764 // initialize line join data / method
87765 var segments = [];
87766 var makeUpdate = Lib.noop;
87767
87768 ownFillEl3 = trace._ownFill;
87769
87770 if(subTypes.hasLines(trace) || trace.fill !== 'none') {
87771 if(tonext) {
87772 // This tells .style which trace to use for fill information:
87773 tonext.datum(cdscatter);
87774 }
87775
87776 if(['hv', 'vh', 'hvh', 'vhv'].indexOf(line.shape) !== -1) {
87777 pathfn = Drawing.steps(line.shape);
87778 revpathbase = Drawing.steps(
87779 line.shape.split('').reverse().join('')
87780 );
87781 } else if(line.shape === 'spline') {
87782 pathfn = revpathbase = function(pts) {
87783 var pLast = pts[pts.length - 1];
87784 if(pts.length > 1 && pts[0][0] === pLast[0] && pts[0][1] === pLast[1]) {
87785 // identical start and end points: treat it as a
87786 // closed curve so we don't get a kink
87787 return Drawing.smoothclosed(pts.slice(1), line.smoothing);
87788 } else {
87789 return Drawing.smoothopen(pts, line.smoothing);
87790 }
87791 };
87792 } else {
87793 pathfn = revpathbase = function(pts) {
87794 return 'M' + pts.join('L');
87795 };
87796 }
87797
87798 revpathfn = function(pts) {
87799 // note: this is destructive (reverses pts in place) so can't use pts after this
87800 return revpathbase(pts.reverse());
87801 };
87802
87803 segments = linePoints(cdscatter, {
87804 xaxis: xa,
87805 yaxis: ya,
87806 connectGaps: trace.connectgaps,
87807 baseTolerance: Math.max(line.width || 1, 3) / 4,
87808 shape: line.shape,
87809 simplify: line.simplify,
87810 fill: trace.fill
87811 });
87812
87813 // since we already have the pixel segments here, use them to make
87814 // polygons for hover on fill
87815 // TODO: can we skip this if hoveron!=fills? That would mean we
87816 // need to redraw when you change hoveron...
87817 thisPolygons = trace._polygons = new Array(segments.length);
87818 for(i = 0; i < segments.length; i++) {
87819 trace._polygons[i] = polygonTester(segments[i]);
87820 }
87821
87822 if(segments.length) {
87823 pt0 = segments[0][0];
87824 lastSegment = segments[segments.length - 1];
87825 pt1 = lastSegment[lastSegment.length - 1];
87826 }
87827
87828 makeUpdate = function(isEnter) {
87829 return function(pts) {
87830 thispath = pathfn(pts);
87831 thisrevpath = revpathfn(pts);
87832 if(!fullpath) {
87833 fullpath = thispath;
87834 revpath = thisrevpath;
87835 } else if(ownFillDir) {
87836 fullpath += 'L' + thispath.substr(1);
87837 revpath = thisrevpath + ('L' + revpath.substr(1));
87838 } else {
87839 fullpath += 'Z' + thispath;
87840 revpath = thisrevpath + 'Z' + revpath;
87841 }
87842
87843 if(subTypes.hasLines(trace) && pts.length > 1) {
87844 var el = d3.select(this);
87845
87846 // This makes the coloring work correctly:
87847 el.datum(cdscatter);
87848
87849 if(isEnter) {
87850 transition(el.style('opacity', 0)
87851 .attr('d', thispath)
87852 .call(Drawing.lineGroupStyle))
87853 .style('opacity', 1);
87854 } else {
87855 var sel = transition(el);
87856 sel.attr('d', thispath);
87857 Drawing.singleLineStyle(cdscatter, sel);
87858 }
87859 }
87860 };
87861 };
87862 }
87863
87864 var lineJoin = lines.selectAll('.js-line').data(segments);
87865
87866 transition(lineJoin.exit())
87867 .style('opacity', 0)
87868 .remove();
87869
87870 lineJoin.each(makeUpdate(false));
87871
87872 lineJoin.enter().append('path')
87873 .classed('js-line', true)
87874 .style('vector-effect', 'non-scaling-stroke')
87875 .call(Drawing.lineGroupStyle)
87876 .each(makeUpdate(true));
87877
87878 Drawing.setClipUrl(lineJoin, plotinfo.layerClipId, gd);
87879
87880 function clearFill(selection) {
87881 transition(selection).attr('d', 'M0,0Z');
87882 }
87883
87884 if(segments.length) {
87885 if(ownFillEl3) {
87886 ownFillEl3.datum(cdscatter);
87887 if(pt0 && pt1) {
87888 if(ownFillDir) {
87889 if(ownFillDir === 'y') {
87890 pt0[1] = pt1[1] = ya.c2p(0, true);
87891 } else if(ownFillDir === 'x') {
87892 pt0[0] = pt1[0] = xa.c2p(0, true);
87893 }
87894
87895 // fill to zero: full trace path, plus extension of
87896 // the endpoints to the appropriate axis
87897 // For the sake of animations, wrap the points around so that
87898 // the points on the axes are the first two points. Otherwise
87899 // animations get a little crazy if the number of points changes.
87900 transition(ownFillEl3).attr('d', 'M' + pt1 + 'L' + pt0 + 'L' + fullpath.substr(1))
87901 .call(Drawing.singleFillStyle);
87902 } else {
87903 // fill to self: just join the path to itself
87904 transition(ownFillEl3).attr('d', fullpath + 'Z')
87905 .call(Drawing.singleFillStyle);
87906 }
87907 }
87908 } else if(tonext) {
87909 if(trace.fill.substr(0, 6) === 'tonext' && fullpath && prevRevpath) {
87910 // fill to next: full trace path, plus the previous path reversed
87911 if(trace.fill === 'tonext') {
87912 // tonext: for use by concentric shapes, like manually constructed
87913 // contours, we just add the two paths closed on themselves.
87914 // This makes strange results if one path is *not* entirely
87915 // inside the other, but then that is a strange usage.
87916 transition(tonext).attr('d', fullpath + 'Z' + prevRevpath + 'Z')
87917 .call(Drawing.singleFillStyle);
87918 } else {
87919 // tonextx/y: for now just connect endpoints with lines. This is
87920 // the correct behavior if the endpoints are at the same value of
87921 // y/x, but if they *aren't*, we should ideally do more complicated
87922 // things depending on whether the new endpoint projects onto the
87923 // existing curve or off the end of it
87924 transition(tonext).attr('d', fullpath + 'L' + prevRevpath.substr(1) + 'Z')
87925 .call(Drawing.singleFillStyle);
87926 }
87927 trace._polygons = trace._polygons.concat(prevPolygons);
87928 } else {
87929 clearFill(tonext);
87930 trace._polygons = null;
87931 }
87932 }
87933 trace._prevRevpath = revpath;
87934 trace._prevPolygons = thisPolygons;
87935 } else {
87936 if(ownFillEl3) clearFill(ownFillEl3);
87937 else if(tonext) clearFill(tonext);
87938 trace._polygons = trace._prevRevpath = trace._prevPolygons = null;
87939 }
87940
87941
87942 function visFilter(d) {
87943 return d.filter(function(v) { return !v.gap && v.vis; });
87944 }
87945
87946 function visFilterWithGaps(d) {
87947 return d.filter(function(v) { return v.vis; });
87948 }
87949
87950 function gapFilter(d) {
87951 return d.filter(function(v) { return !v.gap; });
87952 }
87953
87954 function keyFunc(d) {
87955 return d.id;
87956 }
87957
87958 // Returns a function if the trace is keyed, otherwise returns undefined
87959 function getKeyFunc(trace) {
87960 if(trace.ids) {
87961 return keyFunc;
87962 }
87963 }
87964
87965 function hideFilter() {
87966 return false;
87967 }
87968
87969 function makePoints(points, text, cdscatter) {
87970 var join, selection, hasNode;
87971
87972 var trace = cdscatter[0].trace;
87973 var showMarkers = subTypes.hasMarkers(trace);
87974 var showText = subTypes.hasText(trace);
87975
87976 var keyFunc = getKeyFunc(trace);
87977 var markerFilter = hideFilter;
87978 var textFilter = hideFilter;
87979
87980 if(showMarkers || showText) {
87981 var showFilter = identity;
87982 // if we're stacking, "infer zero" gap mode gets markers in the
87983 // gap points - because we've inferred a zero there - but other
87984 // modes (currently "interpolate", later "interrupt" hopefully)
87985 // we don't draw generated markers
87986 var stackGroup = trace.stackgroup;
87987 var isInferZero = stackGroup && (
87988 gd._fullLayout._scatterStackOpts[xa._id + ya._id][stackGroup].stackgaps === 'infer zero');
87989 if(trace.marker.maxdisplayed || trace._needsCull) {
87990 showFilter = isInferZero ? visFilterWithGaps : visFilter;
87991 } else if(stackGroup && !isInferZero) {
87992 showFilter = gapFilter;
87993 }
87994
87995 if(showMarkers) markerFilter = showFilter;
87996 if(showText) textFilter = showFilter;
87997 }
87998
87999 // marker points
88000
88001 selection = points.selectAll('path.point');
88002
88003 join = selection.data(markerFilter, keyFunc);
88004
88005 var enter = join.enter().append('path')
88006 .classed('point', true);
88007
88008 if(hasTransition) {
88009 enter
88010 .call(Drawing.pointStyle, trace, gd)
88011 .call(Drawing.translatePoints, xa, ya)
88012 .style('opacity', 0)
88013 .transition()
88014 .style('opacity', 1);
88015 }
88016
88017 join.order();
88018
88019 var styleFns;
88020 if(showMarkers) {
88021 styleFns = Drawing.makePointStyleFns(trace);
88022 }
88023
88024 join.each(function(d) {
88025 var el = d3.select(this);
88026 var sel = transition(el);
88027 hasNode = Drawing.translatePoint(d, sel, xa, ya);
88028
88029 if(hasNode) {
88030 Drawing.singlePointStyle(d, sel, trace, styleFns, gd);
88031
88032 if(plotinfo.layerClipId) {
88033 Drawing.hideOutsideRangePoint(d, sel, xa, ya, trace.xcalendar, trace.ycalendar);
88034 }
88035
88036 if(trace.customdata) {
88037 el.classed('plotly-customdata', d.data !== null && d.data !== undefined);
88038 }
88039 } else {
88040 sel.remove();
88041 }
88042 });
88043
88044 if(hasTransition) {
88045 join.exit().transition()
88046 .style('opacity', 0)
88047 .remove();
88048 } else {
88049 join.exit().remove();
88050 }
88051
88052 // text points
88053 selection = text.selectAll('g');
88054 join = selection.data(textFilter, keyFunc);
88055
88056 // each text needs to go in its own 'g' in case
88057 // it gets converted to mathjax
88058 join.enter().append('g').classed('textpoint', true).append('text');
88059
88060 join.order();
88061
88062 join.each(function(d) {
88063 var g = d3.select(this);
88064 var sel = transition(g.select('text'));
88065 hasNode = Drawing.translatePoint(d, sel, xa, ya);
88066
88067 if(hasNode) {
88068 if(plotinfo.layerClipId) {
88069 Drawing.hideOutsideRangePoint(d, g, xa, ya, trace.xcalendar, trace.ycalendar);
88070 }
88071 } else {
88072 g.remove();
88073 }
88074 });
88075
88076 join.selectAll('text')
88077 .call(Drawing.textPointStyle, trace, gd)
88078 .each(function(d) {
88079 // This just *has* to be totally custom becuase of SVG text positioning :(
88080 // It's obviously copied from translatePoint; we just can't use that
88081 var x = xa.c2p(d.x);
88082 var y = ya.c2p(d.y);
88083
88084 d3.select(this).selectAll('tspan.line').each(function() {
88085 transition(d3.select(this)).attr({x: x, y: y});
88086 });
88087 });
88088
88089 join.exit().remove();
88090 }
88091
88092 points.datum(cdscatter);
88093 text.datum(cdscatter);
88094 makePoints(points, text, cdscatter);
88095
88096 // lastly, clip points groups of `cliponaxis !== false` traces
88097 // on `plotinfo._hasClipOnAxisFalse === true` subplots
88098 var hasClipOnAxisFalse = trace.cliponaxis === false;
88099 var clipUrl = hasClipOnAxisFalse ? null : plotinfo.layerClipId;
88100 Drawing.setClipUrl(points, clipUrl, gd);
88101 Drawing.setClipUrl(text, clipUrl, gd);
88102}
88103
88104function selectMarkers(gd, idx, plotinfo, cdscatter, cdscatterAll) {
88105 var xa = plotinfo.xaxis;
88106 var ya = plotinfo.yaxis;
88107 var xr = d3.extent(Lib.simpleMap(xa.range, xa.r2c));
88108 var yr = d3.extent(Lib.simpleMap(ya.range, ya.r2c));
88109
88110 var trace = cdscatter[0].trace;
88111 if(!subTypes.hasMarkers(trace)) return;
88112 // if marker.maxdisplayed is used, select a maximum of
88113 // mnum markers to show, from the set that are in the viewport
88114 var mnum = trace.marker.maxdisplayed;
88115
88116 // TODO: remove some as we get away from the viewport?
88117 if(mnum === 0) return;
88118
88119 var cd = cdscatter.filter(function(v) {
88120 return v.x >= xr[0] && v.x <= xr[1] && v.y >= yr[0] && v.y <= yr[1];
88121 });
88122 var inc = Math.ceil(cd.length / mnum);
88123 var tnum = 0;
88124 cdscatterAll.forEach(function(cdj, j) {
88125 var tracei = cdj[0].trace;
88126 if(subTypes.hasMarkers(tracei) &&
88127 tracei.marker.maxdisplayed > 0 && j < idx) {
88128 tnum++;
88129 }
88130 });
88131
88132 // if multiple traces use maxdisplayed, stagger which markers we
88133 // display this formula offsets successive traces by 1/3 of the
88134 // increment, adding an extra small amount after each triplet so
88135 // it's not quite periodic
88136 var i0 = Math.round(tnum * inc / 3 + Math.floor(tnum / 3) * inc / 7.1);
88137
88138 // for error bars: save in cd which markers to show
88139 // so we don't have to repeat this
88140 cdscatter.forEach(function(v) { delete v.vis; });
88141 cd.forEach(function(v, i) {
88142 if(Math.round((i + i0) % inc) === 0) v.vis = true;
88143 });
88144}
88145
88146},{"../../components/drawing":74,"../../lib":178,"../../lib/polygon":190,"../../registry":269,"./line_points":403,"./link_traces":405,"./subtypes":413,"d3":16}],410:[function(_dereq_,module,exports){
88147/**
88148* Copyright 2012-2020, Plotly, Inc.
88149* All rights reserved.
88150*
88151* This source code is licensed under the MIT license found in the
88152* LICENSE file in the root directory of this source tree.
88153*/
88154
88155
88156'use strict';
88157
88158var subtypes = _dereq_('./subtypes');
88159
88160module.exports = function selectPoints(searchInfo, selectionTester) {
88161 var cd = searchInfo.cd;
88162 var xa = searchInfo.xaxis;
88163 var ya = searchInfo.yaxis;
88164 var selection = [];
88165 var trace = cd[0].trace;
88166 var i;
88167 var di;
88168 var x;
88169 var y;
88170
88171 var hasOnlyLines = (!subtypes.hasMarkers(trace) && !subtypes.hasText(trace));
88172 if(hasOnlyLines) return [];
88173
88174 if(selectionTester === false) { // clear selection
88175 for(i = 0; i < cd.length; i++) {
88176 cd[i].selected = 0;
88177 }
88178 } else {
88179 for(i = 0; i < cd.length; i++) {
88180 di = cd[i];
88181 x = xa.c2p(di.x);
88182 y = ya.c2p(di.y);
88183
88184 if((di.i !== null) && selectionTester.contains([x, y], false, i, searchInfo)) {
88185 selection.push({
88186 pointNumber: di.i,
88187 x: xa.c2d(di.x),
88188 y: ya.c2d(di.y)
88189 });
88190 di.selected = 1;
88191 } else {
88192 di.selected = 0;
88193 }
88194 }
88195 }
88196
88197 return selection;
88198};
88199
88200},{"./subtypes":413}],411:[function(_dereq_,module,exports){
88201/**
88202* Copyright 2012-2020, Plotly, Inc.
88203* All rights reserved.
88204*
88205* This source code is licensed under the MIT license found in the
88206* LICENSE file in the root directory of this source tree.
88207*/
88208
88209'use strict';
88210
88211var perStackAttrs = ['orientation', 'groupnorm', 'stackgaps'];
88212
88213module.exports = function handleStackDefaults(traceIn, traceOut, layout, coerce) {
88214 var stackOpts = layout._scatterStackOpts;
88215
88216 var stackGroup = coerce('stackgroup');
88217 if(stackGroup) {
88218 // use independent stacking options per subplot
88219 var subplot = traceOut.xaxis + traceOut.yaxis;
88220 var subplotStackOpts = stackOpts[subplot];
88221 if(!subplotStackOpts) subplotStackOpts = stackOpts[subplot] = {};
88222
88223 var groupOpts = subplotStackOpts[stackGroup];
88224 var firstTrace = false;
88225 if(groupOpts) {
88226 groupOpts.traces.push(traceOut);
88227 } else {
88228 groupOpts = subplotStackOpts[stackGroup] = {
88229 // keep track of trace indices for use during stacking calculations
88230 // this will be filled in during `calc` and used during `crossTraceCalc`
88231 // so it's OK if we don't recreate it during a non-calc edit
88232 traceIndices: [],
88233 // Hold on to the whole set of prior traces
88234 // First one is most important, so we can clear defaults
88235 // there if we find explicit values only in later traces.
88236 // We're only going to *use* the values stored in groupOpts,
88237 // but for the editor and validate we want things self-consistent
88238 // The full set of traces is used only to fix `fill` default if
88239 // we find `orientation: 'h'` beyond the first trace
88240 traces: [traceOut]
88241 };
88242 firstTrace = true;
88243 }
88244 // TODO: how is this going to work with groupby transforms?
88245 // in principle it should be OK I guess, as long as explicit group styles
88246 // don't override explicit base-trace styles?
88247
88248 var dflts = {
88249 orientation: (traceOut.x && !traceOut.y) ? 'h' : 'v'
88250 };
88251
88252 for(var i = 0; i < perStackAttrs.length; i++) {
88253 var attr = perStackAttrs[i];
88254 var attrFound = attr + 'Found';
88255 if(!groupOpts[attrFound]) {
88256 var traceHasAttr = traceIn[attr] !== undefined;
88257 var isOrientation = attr === 'orientation';
88258 if(traceHasAttr || firstTrace) {
88259 groupOpts[attr] = coerce(attr, dflts[attr]);
88260
88261 if(isOrientation) {
88262 groupOpts.fillDflt = groupOpts[attr] === 'h' ?
88263 'tonextx' : 'tonexty';
88264 }
88265
88266 if(traceHasAttr) {
88267 // Note: this will show a value here even if it's invalid
88268 // in which case it will revert to default.
88269 groupOpts[attrFound] = true;
88270
88271 // Note: only one trace in the stack will get a _fullData
88272 // entry for a given stack-wide attribute. If no traces
88273 // (or the first trace) specify that attribute, the
88274 // first trace will get it. If the first trace does NOT
88275 // specify it but some later trace does, then it gets
88276 // removed from the first trace and only included in the
88277 // one that specified it. This is mostly important for
88278 // editors (that want to see the full values to know
88279 // what settings are available) and Plotly.react diffing.
88280 // Editors may want to use fullLayout._scatterStackOpts
88281 // directly and make these settings available from all
88282 // traces in the stack... then set the new value into
88283 // the first trace, and clear all later traces.
88284 if(!firstTrace) {
88285 delete groupOpts.traces[0][attr];
88286
88287 // orientation can affect default fill of previous traces
88288 if(isOrientation) {
88289 for(var j = 0; j < groupOpts.traces.length - 1; j++) {
88290 var trace2 = groupOpts.traces[j];
88291 if(trace2._input.fill !== trace2.fill) {
88292 trace2.fill = groupOpts.fillDflt;
88293 }
88294 }
88295 }
88296 }
88297 }
88298 }
88299 }
88300 }
88301 return groupOpts;
88302 }
88303};
88304
88305},{}],412:[function(_dereq_,module,exports){
88306/**
88307* Copyright 2012-2020, Plotly, Inc.
88308* All rights reserved.
88309*
88310* This source code is licensed under the MIT license found in the
88311* LICENSE file in the root directory of this source tree.
88312*/
88313
88314
88315'use strict';
88316
88317var d3 = _dereq_('d3');
88318var Drawing = _dereq_('../../components/drawing');
88319var Registry = _dereq_('../../registry');
88320
88321function style(gd) {
88322 var s = d3.select(gd).selectAll('g.trace.scatter');
88323
88324 s.style('opacity', function(d) {
88325 return d[0].trace.opacity;
88326 });
88327
88328 s.selectAll('g.points').each(function(d) {
88329 var sel = d3.select(this);
88330 var trace = d.trace || d[0].trace;
88331 stylePoints(sel, trace, gd);
88332 });
88333
88334 s.selectAll('g.text').each(function(d) {
88335 var sel = d3.select(this);
88336 var trace = d.trace || d[0].trace;
88337 styleText(sel, trace, gd);
88338 });
88339
88340 s.selectAll('g.trace path.js-line')
88341 .call(Drawing.lineGroupStyle);
88342
88343 s.selectAll('g.trace path.js-fill')
88344 .call(Drawing.fillGroupStyle);
88345
88346 Registry.getComponentMethod('errorbars', 'style')(s);
88347}
88348
88349function stylePoints(sel, trace, gd) {
88350 Drawing.pointStyle(sel.selectAll('path.point'), trace, gd);
88351}
88352
88353function styleText(sel, trace, gd) {
88354 Drawing.textPointStyle(sel.selectAll('text'), trace, gd);
88355}
88356
88357function styleOnSelect(gd, cd, sel) {
88358 var trace = cd[0].trace;
88359
88360 if(trace.selectedpoints) {
88361 Drawing.selectedPointStyle(sel.selectAll('path.point'), trace);
88362 Drawing.selectedTextStyle(sel.selectAll('text'), trace);
88363 } else {
88364 stylePoints(sel, trace, gd);
88365 styleText(sel, trace, gd);
88366 }
88367}
88368
88369module.exports = {
88370 style: style,
88371 stylePoints: stylePoints,
88372 styleText: styleText,
88373 styleOnSelect: styleOnSelect
88374};
88375
88376},{"../../components/drawing":74,"../../registry":269,"d3":16}],413:[function(_dereq_,module,exports){
88377/**
88378* Copyright 2012-2020, Plotly, Inc.
88379* All rights reserved.
88380*
88381* This source code is licensed under the MIT license found in the
88382* LICENSE file in the root directory of this source tree.
88383*/
88384
88385
88386'use strict';
88387
88388var Lib = _dereq_('../../lib');
88389
88390module.exports = {
88391 hasLines: function(trace) {
88392 return trace.visible && trace.mode &&
88393 trace.mode.indexOf('lines') !== -1;
88394 },
88395
88396 hasMarkers: function(trace) {
88397 return trace.visible && (
88398 (trace.mode && trace.mode.indexOf('markers') !== -1) ||
88399 // until splom implements 'mode'
88400 trace.type === 'splom'
88401 );
88402 },
88403
88404 hasText: function(trace) {
88405 return trace.visible && trace.mode &&
88406 trace.mode.indexOf('text') !== -1;
88407 },
88408
88409 isBubble: function(trace) {
88410 return Lib.isPlainObject(trace.marker) &&
88411 Lib.isArrayOrTypedArray(trace.marker.size);
88412 }
88413};
88414
88415},{"../../lib":178}],414:[function(_dereq_,module,exports){
88416/**
88417* Copyright 2012-2020, Plotly, Inc.
88418* All rights reserved.
88419*
88420* This source code is licensed under the MIT license found in the
88421* LICENSE file in the root directory of this source tree.
88422*/
88423
88424
88425'use strict';
88426
88427var Lib = _dereq_('../../lib');
88428
88429/*
88430 * opts: object of flags to control features not all text users support
88431 * noSelect: caller does not support selected/unselected attribute containers
88432 */
88433module.exports = function(traceIn, traceOut, layout, coerce, opts) {
88434 opts = opts || {};
88435
88436 coerce('textposition');
88437 Lib.coerceFont(coerce, 'textfont', layout.font);
88438
88439 if(!opts.noSelect) {
88440 coerce('selected.textfont.color');
88441 coerce('unselected.textfont.color');
88442 }
88443};
88444
88445},{"../../lib":178}],415:[function(_dereq_,module,exports){
88446/**
88447* Copyright 2012-2020, Plotly, Inc.
88448* All rights reserved.
88449*
88450* This source code is licensed under the MIT license found in the
88451* LICENSE file in the root directory of this source tree.
88452*/
88453
88454'use strict';
88455
88456var Lib = _dereq_('../../lib');
88457var Registry = _dereq_('../../registry');
88458
88459module.exports = function handleXYDefaults(traceIn, traceOut, layout, coerce) {
88460 var x = coerce('x');
88461 var y = coerce('y');
88462 var len;
88463
88464 var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleTraceDefaults');
88465 handleCalendarDefaults(traceIn, traceOut, ['x', 'y'], layout);
88466
88467 if(x) {
88468 var xlen = Lib.minRowLength(x);
88469 if(y) {
88470 len = Math.min(xlen, Lib.minRowLength(y));
88471 } else {
88472 len = xlen;
88473 coerce('y0');
88474 coerce('dy');
88475 }
88476 } else {
88477 if(!y) return 0;
88478
88479 len = Lib.minRowLength(y);
88480 coerce('x0');
88481 coerce('dx');
88482 }
88483
88484 traceOut._length = len;
88485
88486 return len;
88487};
88488
88489},{"../../lib":178,"../../registry":269}],416:[function(_dereq_,module,exports){
88490/**
88491* Copyright 2012-2020, Plotly, Inc.
88492* All rights reserved.
88493*
88494* This source code is licensed under the MIT license found in the
88495* LICENSE file in the root directory of this source tree.
88496*/
88497
88498'use strict';
88499
88500var hovertemplateAttrs = _dereq_('../../plots/template_attributes').hovertemplateAttrs;
88501var texttemplateAttrs = _dereq_('../../plots/template_attributes').texttemplateAttrs;
88502var scatterAttrs = _dereq_('../scatter/attributes');
88503var baseAttrs = _dereq_('../../plots/attributes');
88504var colorScaleAttrs = _dereq_('../../components/colorscale/attributes');
88505var dash = _dereq_('../../components/drawing/attributes').dash;
88506
88507var extendFlat = _dereq_('../../lib/extend').extendFlat;
88508
88509var scatterMarkerAttrs = scatterAttrs.marker;
88510var scatterLineAttrs = scatterAttrs.line;
88511var scatterMarkerLineAttrs = scatterMarkerAttrs.line;
88512
88513module.exports = {
88514 a: {
88515 valType: 'data_array',
88516 editType: 'calc',
88517
88518 },
88519 b: {
88520 valType: 'data_array',
88521 editType: 'calc',
88522
88523 },
88524 c: {
88525 valType: 'data_array',
88526 editType: 'calc',
88527
88528 },
88529 sum: {
88530 valType: 'number',
88531
88532 dflt: 0,
88533 min: 0,
88534 editType: 'calc',
88535
88536 },
88537 mode: extendFlat({}, scatterAttrs.mode, {dflt: 'markers'}),
88538 text: extendFlat({}, scatterAttrs.text, {
88539
88540 }),
88541 texttemplate: texttemplateAttrs({editType: 'plot'}, {
88542 keys: ['a', 'b', 'c', 'text']
88543 }),
88544 hovertext: extendFlat({}, scatterAttrs.hovertext, {
88545
88546 }),
88547 line: {
88548 color: scatterLineAttrs.color,
88549 width: scatterLineAttrs.width,
88550 dash: dash,
88551 shape: extendFlat({}, scatterLineAttrs.shape,
88552 {values: ['linear', 'spline']}),
88553 smoothing: scatterLineAttrs.smoothing,
88554 editType: 'calc'
88555 },
88556 connectgaps: scatterAttrs.connectgaps,
88557 cliponaxis: scatterAttrs.cliponaxis,
88558 fill: extendFlat({}, scatterAttrs.fill, {
88559 values: ['none', 'toself', 'tonext'],
88560 dflt: 'none',
88561
88562 }),
88563 fillcolor: scatterAttrs.fillcolor,
88564 marker: extendFlat({
88565 symbol: scatterMarkerAttrs.symbol,
88566 opacity: scatterMarkerAttrs.opacity,
88567 maxdisplayed: scatterMarkerAttrs.maxdisplayed,
88568 size: scatterMarkerAttrs.size,
88569 sizeref: scatterMarkerAttrs.sizeref,
88570 sizemin: scatterMarkerAttrs.sizemin,
88571 sizemode: scatterMarkerAttrs.sizemode,
88572 line: extendFlat({
88573 width: scatterMarkerLineAttrs.width,
88574 editType: 'calc'
88575 },
88576 colorScaleAttrs('marker.line')
88577 ),
88578 gradient: scatterMarkerAttrs.gradient,
88579 editType: 'calc'
88580 },
88581 colorScaleAttrs('marker')
88582 ),
88583
88584 textfont: scatterAttrs.textfont,
88585 textposition: scatterAttrs.textposition,
88586
88587 selected: scatterAttrs.selected,
88588 unselected: scatterAttrs.unselected,
88589
88590 hoverinfo: extendFlat({}, baseAttrs.hoverinfo, {
88591 flags: ['a', 'b', 'c', 'text', 'name']
88592 }),
88593 hoveron: scatterAttrs.hoveron,
88594 hovertemplate: hovertemplateAttrs(),
88595};
88596
88597},{"../../components/colorscale/attributes":59,"../../components/drawing/attributes":73,"../../lib/extend":173,"../../plots/attributes":219,"../../plots/template_attributes":264,"../scatter/attributes":389}],417:[function(_dereq_,module,exports){
88598/**
88599* Copyright 2012-2020, Plotly, Inc.
88600* All rights reserved.
88601*
88602* This source code is licensed under the MIT license found in the
88603* LICENSE file in the root directory of this source tree.
88604*/
88605
88606
88607'use strict';
88608
88609var isNumeric = _dereq_('fast-isnumeric');
88610
88611var calcColorscale = _dereq_('../scatter/colorscale_calc');
88612var arraysToCalcdata = _dereq_('../scatter/arrays_to_calcdata');
88613var calcSelection = _dereq_('../scatter/calc_selection');
88614var calcMarkerSize = _dereq_('../scatter/calc').calcMarkerSize;
88615
88616var dataArrays = ['a', 'b', 'c'];
88617var arraysToFill = {a: ['b', 'c'], b: ['a', 'c'], c: ['a', 'b']};
88618
88619module.exports = function calc(gd, trace) {
88620 var ternary = gd._fullLayout[trace.subplot];
88621 var displaySum = ternary.sum;
88622 var normSum = trace.sum || displaySum;
88623 var arrays = {a: trace.a, b: trace.b, c: trace.c};
88624
88625 var i, j, dataArray, newArray, fillArray1, fillArray2;
88626
88627 // fill in one missing component
88628 for(i = 0; i < dataArrays.length; i++) {
88629 dataArray = dataArrays[i];
88630 if(arrays[dataArray]) continue;
88631
88632 fillArray1 = arrays[arraysToFill[dataArray][0]];
88633 fillArray2 = arrays[arraysToFill[dataArray][1]];
88634 newArray = new Array(fillArray1.length);
88635 for(j = 0; j < fillArray1.length; j++) {
88636 newArray[j] = normSum - fillArray1[j] - fillArray2[j];
88637 }
88638 arrays[dataArray] = newArray;
88639 }
88640
88641 // make the calcdata array
88642 var serieslen = trace._length;
88643 var cd = new Array(serieslen);
88644 var a, b, c, norm, x, y;
88645 for(i = 0; i < serieslen; i++) {
88646 a = arrays.a[i];
88647 b = arrays.b[i];
88648 c = arrays.c[i];
88649 if(isNumeric(a) && isNumeric(b) && isNumeric(c)) {
88650 a = +a;
88651 b = +b;
88652 c = +c;
88653 norm = displaySum / (a + b + c);
88654 if(norm !== 1) {
88655 a *= norm;
88656 b *= norm;
88657 c *= norm;
88658 }
88659 // map a, b, c onto x and y where the full scale of y
88660 // is [0, sum], and x is [-sum, sum]
88661 // TODO: this makes `a` always the top, `b` the bottom left,
88662 // and `c` the bottom right. Do we want options to rearrange
88663 // these?
88664 y = a;
88665 x = c - b;
88666 cd[i] = {x: x, y: y, a: a, b: b, c: c};
88667 } else cd[i] = {x: false, y: false};
88668 }
88669
88670 calcMarkerSize(trace, serieslen);
88671 calcColorscale(gd, trace);
88672 arraysToCalcdata(cd, trace);
88673 calcSelection(cd, trace);
88674
88675 return cd;
88676};
88677
88678},{"../scatter/arrays_to_calcdata":388,"../scatter/calc":390,"../scatter/calc_selection":391,"../scatter/colorscale_calc":392,"fast-isnumeric":18}],418:[function(_dereq_,module,exports){
88679/**
88680* Copyright 2012-2020, Plotly, Inc.
88681* All rights reserved.
88682*
88683* This source code is licensed under the MIT license found in the
88684* LICENSE file in the root directory of this source tree.
88685*/
88686
88687
88688'use strict';
88689
88690var Lib = _dereq_('../../lib');
88691
88692var constants = _dereq_('../scatter/constants');
88693var subTypes = _dereq_('../scatter/subtypes');
88694var handleMarkerDefaults = _dereq_('../scatter/marker_defaults');
88695var handleLineDefaults = _dereq_('../scatter/line_defaults');
88696var handleLineShapeDefaults = _dereq_('../scatter/line_shape_defaults');
88697var handleTextDefaults = _dereq_('../scatter/text_defaults');
88698var handleFillColorDefaults = _dereq_('../scatter/fillcolor_defaults');
88699
88700var attributes = _dereq_('./attributes');
88701
88702
88703module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
88704 function coerce(attr, dflt) {
88705 return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
88706 }
88707
88708 var a = coerce('a');
88709 var b = coerce('b');
88710 var c = coerce('c');
88711 var len;
88712
88713 // allow any one array to be missing, len is the minimum length of those
88714 // present. Note that after coerce data_array's are either Arrays (which
88715 // are truthy even if empty) or undefined. As in scatter, an empty array
88716 // is different from undefined, because it can signify that this data is
88717 // not known yet but expected in the future
88718 if(a) {
88719 len = a.length;
88720 if(b) {
88721 len = Math.min(len, b.length);
88722 if(c) len = Math.min(len, c.length);
88723 } else if(c) len = Math.min(len, c.length);
88724 else len = 0;
88725 } else if(b && c) {
88726 len = Math.min(b.length, c.length);
88727 }
88728
88729 if(!len) {
88730 traceOut.visible = false;
88731 return;
88732 }
88733
88734 traceOut._length = len;
88735
88736 coerce('sum');
88737
88738 coerce('text');
88739 coerce('hovertext');
88740 if(traceOut.hoveron !== 'fills') coerce('hovertemplate');
88741
88742 var defaultMode = len < constants.PTS_LINESONLY ? 'lines+markers' : 'lines';
88743 coerce('mode', defaultMode);
88744
88745 if(subTypes.hasLines(traceOut)) {
88746 handleLineDefaults(traceIn, traceOut, defaultColor, layout, coerce);
88747 handleLineShapeDefaults(traceIn, traceOut, coerce);
88748 coerce('connectgaps');
88749 }
88750
88751 if(subTypes.hasMarkers(traceOut)) {
88752 handleMarkerDefaults(traceIn, traceOut, defaultColor, layout, coerce, {gradient: true});
88753 }
88754
88755 if(subTypes.hasText(traceOut)) {
88756 coerce('texttemplate');
88757 handleTextDefaults(traceIn, traceOut, layout, coerce);
88758 }
88759
88760 var dfltHoverOn = [];
88761
88762 if(subTypes.hasMarkers(traceOut) || subTypes.hasText(traceOut)) {
88763 coerce('cliponaxis');
88764 coerce('marker.maxdisplayed');
88765 dfltHoverOn.push('points');
88766 }
88767
88768 coerce('fill');
88769 if(traceOut.fill !== 'none') {
88770 handleFillColorDefaults(traceIn, traceOut, defaultColor, coerce);
88771 if(!subTypes.hasLines(traceOut)) handleLineShapeDefaults(traceIn, traceOut, coerce);
88772 }
88773
88774 if(traceOut.fill === 'tonext' || traceOut.fill === 'toself') {
88775 dfltHoverOn.push('fills');
88776 }
88777 coerce('hoveron', dfltHoverOn.join('+') || 'points');
88778
88779 Lib.coerceSelectionMarkerOpacity(traceOut, coerce);
88780};
88781
88782},{"../../lib":178,"../scatter/constants":393,"../scatter/fillcolor_defaults":397,"../scatter/line_defaults":402,"../scatter/line_shape_defaults":404,"../scatter/marker_defaults":408,"../scatter/subtypes":413,"../scatter/text_defaults":414,"./attributes":416}],419:[function(_dereq_,module,exports){
88783/**
88784* Copyright 2012-2020, Plotly, Inc.
88785* All rights reserved.
88786*
88787* This source code is licensed under the MIT license found in the
88788* LICENSE file in the root directory of this source tree.
88789*/
88790
88791'use strict';
88792
88793module.exports = function eventData(out, pt, trace, cd, pointNumber) {
88794 if(pt.xa) out.xaxis = pt.xa;
88795 if(pt.ya) out.yaxis = pt.ya;
88796
88797 if(cd[pointNumber]) {
88798 var cdi = cd[pointNumber];
88799
88800 // N.B. These are the normalized coordinates.
88801 out.a = cdi.a;
88802 out.b = cdi.b;
88803 out.c = cdi.c;
88804 } else {
88805 // for fill-hover only
88806 out.a = pt.a;
88807 out.b = pt.b;
88808 out.c = pt.c;
88809 }
88810
88811 return out;
88812};
88813
88814},{}],420:[function(_dereq_,module,exports){
88815/**
88816* Copyright 2012-2020, Plotly, Inc.
88817* All rights reserved.
88818*
88819* This source code is licensed under the MIT license found in the
88820* LICENSE file in the root directory of this source tree.
88821*/
88822
88823'use strict';
88824
88825var Axes = _dereq_('../../plots/cartesian/axes');
88826
88827module.exports = function formatLabels(cdi, trace, fullLayout) {
88828 var labels = {};
88829
88830 var subplot = fullLayout[trace.subplot]._subplot;
88831 labels.aLabel = Axes.tickText(subplot.aaxis, cdi.a, true).text;
88832 labels.bLabel = Axes.tickText(subplot.baxis, cdi.b, true).text;
88833 labels.cLabel = Axes.tickText(subplot.caxis, cdi.c, true).text;
88834
88835 return labels;
88836};
88837
88838},{"../../plots/cartesian/axes":222}],421:[function(_dereq_,module,exports){
88839/**
88840* Copyright 2012-2020, Plotly, Inc.
88841* All rights reserved.
88842*
88843* This source code is licensed under the MIT license found in the
88844* LICENSE file in the root directory of this source tree.
88845*/
88846
88847'use strict';
88848
88849var scatterHover = _dereq_('../scatter/hover');
88850
88851module.exports = function hoverPoints(pointData, xval, yval, hovermode) {
88852 var scatterPointData = scatterHover(pointData, xval, yval, hovermode);
88853 if(!scatterPointData || scatterPointData[0].index === false) return;
88854
88855 var newPointData = scatterPointData[0];
88856
88857 // if hovering on a fill, we don't show any point data so the label is
88858 // unchanged from what scatter gives us - except that it needs to
88859 // be constrained to the trianglular plot area, not just the rectangular
88860 // area defined by the synthetic x and y axes
88861 // TODO: in some cases the vertical middle of the shape is not within
88862 // the triangular viewport at all, so the label can become disconnected
88863 // from the shape entirely. But calculating what portion of the shape
88864 // is actually visible, as constrained by the diagonal axis lines, is not
88865 // so easy and anyway we lost the information we would have needed to do
88866 // this inside scatterHover.
88867 if(newPointData.index === undefined) {
88868 var yFracUp = 1 - (newPointData.y0 / pointData.ya._length);
88869 var xLen = pointData.xa._length;
88870 var xMin = xLen * yFracUp / 2;
88871 var xMax = xLen - xMin;
88872 newPointData.x0 = Math.max(Math.min(newPointData.x0, xMax), xMin);
88873 newPointData.x1 = Math.max(Math.min(newPointData.x1, xMax), xMin);
88874 return scatterPointData;
88875 }
88876
88877 var cdi = newPointData.cd[newPointData.index];
88878 var trace = newPointData.trace;
88879 var subplot = newPointData.subplot;
88880
88881 newPointData.a = cdi.a;
88882 newPointData.b = cdi.b;
88883 newPointData.c = cdi.c;
88884
88885 newPointData.xLabelVal = undefined;
88886 newPointData.yLabelVal = undefined;
88887
88888 var fullLayout = {};
88889 fullLayout[trace.subplot] = {_subplot: subplot};
88890 var labels = trace._module.formatLabels(cdi, trace, fullLayout);
88891 newPointData.aLabel = labels.aLabel;
88892 newPointData.bLabel = labels.bLabel;
88893 newPointData.cLabel = labels.cLabel;
88894
88895 var hoverinfo = cdi.hi || trace.hoverinfo;
88896 var text = [];
88897 function textPart(ax, val) {
88898 text.push(ax._hovertitle + ': ' + val);
88899 }
88900 if(!trace.hovertemplate) {
88901 var parts = hoverinfo.split('+');
88902 if(parts.indexOf('all') !== -1) parts = ['a', 'b', 'c'];
88903 if(parts.indexOf('a') !== -1) textPart(subplot.aaxis, newPointData.aLabel);
88904 if(parts.indexOf('b') !== -1) textPart(subplot.baxis, newPointData.bLabel);
88905 if(parts.indexOf('c') !== -1) textPart(subplot.caxis, newPointData.cLabel);
88906 }
88907 newPointData.extraText = text.join('<br>');
88908 newPointData.hovertemplate = trace.hovertemplate;
88909 return scatterPointData;
88910};
88911
88912},{"../scatter/hover":400}],422:[function(_dereq_,module,exports){
88913/**
88914* Copyright 2012-2020, Plotly, Inc.
88915* All rights reserved.
88916*
88917* This source code is licensed under the MIT license found in the
88918* LICENSE file in the root directory of this source tree.
88919*/
88920
88921'use strict';
88922
88923module.exports = {
88924 attributes: _dereq_('./attributes'),
88925 supplyDefaults: _dereq_('./defaults'),
88926 colorbar: _dereq_('../scatter/marker_colorbar'),
88927 formatLabels: _dereq_('./format_labels'),
88928 calc: _dereq_('./calc'),
88929 plot: _dereq_('./plot'),
88930 style: _dereq_('../scatter/style').style,
88931 styleOnSelect: _dereq_('../scatter/style').styleOnSelect,
88932 hoverPoints: _dereq_('./hover'),
88933 selectPoints: _dereq_('../scatter/select'),
88934 eventData: _dereq_('./event_data'),
88935
88936 moduleType: 'trace',
88937 name: 'scatterternary',
88938 basePlotModule: _dereq_('../../plots/ternary'),
88939 categories: ['ternary', 'symbols', 'showLegend', 'scatter-like'],
88940 meta: {
88941
88942
88943 }
88944};
88945
88946},{"../../plots/ternary":265,"../scatter/marker_colorbar":407,"../scatter/select":410,"../scatter/style":412,"./attributes":416,"./calc":417,"./defaults":418,"./event_data":419,"./format_labels":420,"./hover":421,"./plot":423}],423:[function(_dereq_,module,exports){
88947/**
88948* Copyright 2012-2020, Plotly, Inc.
88949* All rights reserved.
88950*
88951* This source code is licensed under the MIT license found in the
88952* LICENSE file in the root directory of this source tree.
88953*/
88954
88955
88956'use strict';
88957
88958var scatterPlot = _dereq_('../scatter/plot');
88959
88960module.exports = function plot(gd, ternary, moduleCalcData) {
88961 var plotContainer = ternary.plotContainer;
88962
88963 // remove all nodes inside the scatter layer
88964 plotContainer.select('.scatterlayer').selectAll('*').remove();
88965
88966 // mimic cartesian plotinfo
88967 var plotinfo = {
88968 xaxis: ternary.xaxis,
88969 yaxis: ternary.yaxis,
88970 plot: plotContainer,
88971 layerClipId: ternary._hasClipOnAxisFalse ? ternary.clipIdRelative : null
88972 };
88973
88974 var scatterLayer = ternary.layers.frontplot.select('g.scatterlayer');
88975
88976 scatterPlot(gd, plotinfo, moduleCalcData, scatterLayer);
88977};
88978
88979},{"../scatter/plot":409}],424:[function(_dereq_,module,exports){
88980/**
88981* Copyright 2012-2020, Plotly, Inc.
88982* All rights reserved.
88983*
88984* This source code is licensed under the MIT license found in the
88985* LICENSE file in the root directory of this source tree.
88986*/
88987
88988'use strict';
88989
88990var boxAttrs = _dereq_('../box/attributes');
88991var extendFlat = _dereq_('../../lib/extend').extendFlat;
88992
88993module.exports = {
88994 y: boxAttrs.y,
88995 x: boxAttrs.x,
88996 x0: boxAttrs.x0,
88997 y0: boxAttrs.y0,
88998 name: extendFlat({}, boxAttrs.name, {
88999
89000 }),
89001 orientation: extendFlat({}, boxAttrs.orientation, {
89002
89003 }),
89004
89005 bandwidth: {
89006 valType: 'number',
89007 min: 0,
89008
89009 editType: 'calc',
89010
89011 },
89012
89013 scalegroup: {
89014 valType: 'string',
89015
89016 dflt: '',
89017 editType: 'calc',
89018
89019 },
89020 scalemode: {
89021 valType: 'enumerated',
89022 values: ['width', 'count'],
89023 dflt: 'width',
89024
89025 editType: 'calc',
89026
89027 },
89028
89029 spanmode: {
89030 valType: 'enumerated',
89031 values: ['soft', 'hard', 'manual'],
89032 dflt: 'soft',
89033
89034 editType: 'calc',
89035
89036 },
89037 span: {
89038 valType: 'info_array',
89039 items: [
89040 {valType: 'any', editType: 'calc'},
89041 {valType: 'any', editType: 'calc'}
89042 ],
89043
89044 editType: 'calc',
89045
89046 },
89047
89048 line: {
89049 color: {
89050 valType: 'color',
89051
89052 editType: 'style',
89053
89054 },
89055 width: {
89056 valType: 'number',
89057
89058 min: 0,
89059 dflt: 2,
89060 editType: 'style',
89061
89062 },
89063 editType: 'plot'
89064 },
89065 fillcolor: boxAttrs.fillcolor,
89066
89067 points: extendFlat({}, boxAttrs.boxpoints, {
89068
89069 }),
89070 jitter: extendFlat({}, boxAttrs.jitter, {
89071
89072 }),
89073 pointpos: extendFlat({}, boxAttrs.pointpos, {
89074
89075 }),
89076
89077 width: extendFlat({}, boxAttrs.width, {
89078
89079 }),
89080
89081 marker: boxAttrs.marker,
89082 text: boxAttrs.text,
89083 hovertext: boxAttrs.hovertext,
89084 hovertemplate: boxAttrs.hovertemplate,
89085
89086 box: {
89087 visible: {
89088 valType: 'boolean',
89089 dflt: false,
89090
89091 editType: 'plot',
89092
89093 },
89094 width: {
89095 valType: 'number',
89096 min: 0,
89097 max: 1,
89098 dflt: 0.25,
89099
89100 editType: 'plot',
89101
89102 },
89103 fillcolor: {
89104 valType: 'color',
89105
89106 editType: 'style',
89107
89108 },
89109 line: {
89110 color: {
89111 valType: 'color',
89112
89113 editType: 'style',
89114
89115 },
89116 width: {
89117 valType: 'number',
89118 min: 0,
89119
89120 editType: 'style',
89121
89122 },
89123 editType: 'style'
89124 },
89125 editType: 'plot'
89126 },
89127
89128 meanline: {
89129 visible: {
89130 valType: 'boolean',
89131 dflt: false,
89132
89133 editType: 'plot',
89134
89135 },
89136 color: {
89137 valType: 'color',
89138
89139 editType: 'style',
89140
89141 },
89142 width: {
89143 valType: 'number',
89144 min: 0,
89145
89146 editType: 'style',
89147
89148 },
89149 editType: 'plot'
89150 },
89151
89152 side: {
89153 valType: 'enumerated',
89154 values: ['both', 'positive', 'negative'],
89155 dflt: 'both',
89156
89157 editType: 'calc',
89158
89159 },
89160
89161 offsetgroup: boxAttrs.offsetgroup,
89162 alignmentgroup: boxAttrs.alignmentgroup,
89163
89164 selected: boxAttrs.selected,
89165 unselected: boxAttrs.unselected,
89166
89167 hoveron: {
89168 valType: 'flaglist',
89169 flags: ['violins', 'points', 'kde'],
89170 dflt: 'violins+points+kde',
89171 extras: ['all'],
89172
89173 editType: 'style',
89174
89175 }
89176};
89177
89178},{"../../lib/extend":173,"../box/attributes":296}],425:[function(_dereq_,module,exports){
89179/**
89180* Copyright 2012-2020, Plotly, Inc.
89181* All rights reserved.
89182*
89183* This source code is licensed under the MIT license found in the
89184* LICENSE file in the root directory of this source tree.
89185*/
89186
89187'use strict';
89188
89189var Lib = _dereq_('../../lib');
89190var Axes = _dereq_('../../plots/cartesian/axes');
89191var boxCalc = _dereq_('../box/calc');
89192var helpers = _dereq_('./helpers');
89193var BADNUM = _dereq_('../../constants/numerical').BADNUM;
89194
89195module.exports = function calc(gd, trace) {
89196 var cd = boxCalc(gd, trace);
89197
89198 if(cd[0].t.empty) return cd;
89199
89200 var fullLayout = gd._fullLayout;
89201 var valAxis = Axes.getFromId(
89202 gd,
89203 trace[trace.orientation === 'h' ? 'xaxis' : 'yaxis']
89204 );
89205
89206 var spanMin = Infinity;
89207 var spanMax = -Infinity;
89208 var maxKDE = 0;
89209 var maxCount = 0;
89210
89211 for(var i = 0; i < cd.length; i++) {
89212 var cdi = cd[i];
89213 var vals = cdi.pts.map(helpers.extractVal);
89214
89215 var bandwidth = cdi.bandwidth = calcBandwidth(trace, cdi, vals);
89216 var span = cdi.span = calcSpan(trace, cdi, valAxis, bandwidth);
89217
89218 if(cdi.min === cdi.max && bandwidth === 0) {
89219 // if span is zero and bandwidth is zero, we want a violin with zero width
89220 span = cdi.span = [cdi.min, cdi.max];
89221 cdi.density = [{v: 1, t: span[0]}];
89222 cdi.bandwidth = bandwidth;
89223 maxKDE = Math.max(maxKDE, 1);
89224 } else {
89225 // step that well covers the bandwidth and is multiple of span distance
89226 var dist = span[1] - span[0];
89227 var n = Math.ceil(dist / (bandwidth / 3));
89228 var step = dist / n;
89229
89230 if(!isFinite(step) || !isFinite(n)) {
89231 Lib.error('Something went wrong with computing the violin span');
89232 cd[0].t.empty = true;
89233 return cd;
89234 }
89235
89236 var kde = helpers.makeKDE(cdi, trace, vals);
89237 cdi.density = new Array(n);
89238
89239 for(var k = 0, t = span[0]; t < (span[1] + step / 2); k++, t += step) {
89240 var v = kde(t);
89241 cdi.density[k] = {v: v, t: t};
89242 maxKDE = Math.max(maxKDE, v);
89243 }
89244 }
89245
89246 maxCount = Math.max(maxCount, vals.length);
89247 spanMin = Math.min(spanMin, span[0]);
89248 spanMax = Math.max(spanMax, span[1]);
89249 }
89250
89251 var extremes = Axes.findExtremes(valAxis, [spanMin, spanMax], {padded: true});
89252 trace._extremes[valAxis._id] = extremes;
89253
89254 if(trace.width) {
89255 cd[0].t.maxKDE = maxKDE;
89256 } else {
89257 var violinScaleGroupStats = fullLayout._violinScaleGroupStats;
89258 var scaleGroup = trace.scalegroup;
89259 var groupStats = violinScaleGroupStats[scaleGroup];
89260
89261 if(groupStats) {
89262 groupStats.maxKDE = Math.max(groupStats.maxKDE, maxKDE);
89263 groupStats.maxCount = Math.max(groupStats.maxCount, maxCount);
89264 } else {
89265 violinScaleGroupStats[scaleGroup] = {
89266 maxKDE: maxKDE,
89267 maxCount: maxCount
89268 };
89269 }
89270 }
89271
89272 cd[0].t.labels.kde = Lib._(gd, 'kde:');
89273
89274 return cd;
89275};
89276
89277// Default to Silveman's rule of thumb
89278// - https://stats.stackexchange.com/a/6671
89279// - https://en.wikipedia.org/wiki/Kernel_density_estimation#A_rule-of-thumb_bandwidth_estimator
89280// - https://github.com/statsmodels/statsmodels/blob/master/statsmodels/nonparametric/bandwidths.py
89281function silvermanRule(len, ssd, iqr) {
89282 var a = Math.min(ssd, iqr / 1.349);
89283 return 1.059 * a * Math.pow(len, -0.2);
89284}
89285
89286function calcBandwidth(trace, cdi, vals) {
89287 var span = cdi.max - cdi.min;
89288
89289 // If span is zero
89290 if(!span) {
89291 if(trace.bandwidth) {
89292 return trace.bandwidth;
89293 } else {
89294 // if span is zero and no bandwidth is specified
89295 // it returns zero bandwidth which is a special case
89296 return 0;
89297 }
89298 }
89299
89300 // Limit how small the bandwidth can be.
89301 //
89302 // Silverman's rule of thumb can be "very" small
89303 // when IQR does a poor job at describing the spread
89304 // of the distribution.
89305 // We also want to limit custom bandwidths
89306 // to not blow up kde computations.
89307
89308 if(trace.bandwidth) {
89309 return Math.max(trace.bandwidth, span / 1e4);
89310 } else {
89311 var len = vals.length;
89312 var ssd = Lib.stdev(vals, len - 1, cdi.mean);
89313 return Math.max(
89314 silvermanRule(len, ssd, cdi.q3 - cdi.q1),
89315 span / 100
89316 );
89317 }
89318}
89319
89320function calcSpan(trace, cdi, valAxis, bandwidth) {
89321 var spanmode = trace.spanmode;
89322 var spanIn = trace.span || [];
89323 var spanTight = [cdi.min, cdi.max];
89324 var spanLoose = [cdi.min - 2 * bandwidth, cdi.max + 2 * bandwidth];
89325 var spanOut;
89326
89327 function calcSpanItem(index) {
89328 var s = spanIn[index];
89329 var sc = valAxis.type === 'multicategory' ?
89330 valAxis.r2c(s) :
89331 valAxis.d2c(s, 0, trace[cdi.valLetter + 'calendar']);
89332 return sc === BADNUM ? spanLoose[index] : sc;
89333 }
89334
89335 if(spanmode === 'soft') {
89336 spanOut = spanLoose;
89337 } else if(spanmode === 'hard') {
89338 spanOut = spanTight;
89339 } else {
89340 spanOut = [calcSpanItem(0), calcSpanItem(1)];
89341 }
89342
89343 // to reuse the equal-range-item block
89344 var dummyAx = {
89345 type: 'linear',
89346 range: spanOut
89347 };
89348 Axes.setConvert(dummyAx);
89349 dummyAx.cleanRange();
89350
89351 return spanOut;
89352}
89353
89354},{"../../constants/numerical":158,"../../lib":178,"../../plots/cartesian/axes":222,"../box/calc":297,"./helpers":428}],426:[function(_dereq_,module,exports){
89355/**
89356* Copyright 2012-2020, Plotly, Inc.
89357* All rights reserved.
89358*
89359* This source code is licensed under the MIT license found in the
89360* LICENSE file in the root directory of this source tree.
89361*/
89362
89363'use strict';
89364
89365var setPositionOffset = _dereq_('../box/cross_trace_calc').setPositionOffset;
89366var orientations = ['v', 'h'];
89367
89368module.exports = function crossTraceCalc(gd, plotinfo) {
89369 var calcdata = gd.calcdata;
89370 var xa = plotinfo.xaxis;
89371 var ya = plotinfo.yaxis;
89372
89373 for(var i = 0; i < orientations.length; i++) {
89374 var orientation = orientations[i];
89375 var posAxis = orientation === 'h' ? ya : xa;
89376 var violinList = [];
89377
89378 for(var j = 0; j < calcdata.length; j++) {
89379 var cd = calcdata[j];
89380 var t = cd[0].t;
89381 var trace = cd[0].trace;
89382
89383 if(trace.visible === true && trace.type === 'violin' &&
89384 !t.empty &&
89385 trace.orientation === orientation &&
89386 trace.xaxis === xa._id &&
89387 trace.yaxis === ya._id
89388 ) {
89389 violinList.push(j);
89390 }
89391 }
89392
89393 setPositionOffset('violin', gd, violinList, posAxis);
89394 }
89395};
89396
89397},{"../box/cross_trace_calc":298}],427:[function(_dereq_,module,exports){
89398/**
89399* Copyright 2012-2020, Plotly, Inc.
89400* All rights reserved.
89401*
89402* This source code is licensed under the MIT license found in the
89403* LICENSE file in the root directory of this source tree.
89404*/
89405
89406'use strict';
89407
89408var Lib = _dereq_('../../lib');
89409var Color = _dereq_('../../components/color');
89410
89411var boxDefaults = _dereq_('../box/defaults');
89412var attributes = _dereq_('./attributes');
89413
89414module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
89415 function coerce(attr, dflt) {
89416 return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
89417 }
89418 function coerce2(attr, dflt) {
89419 return Lib.coerce2(traceIn, traceOut, attributes, attr, dflt);
89420 }
89421
89422 boxDefaults.handleSampleDefaults(traceIn, traceOut, coerce, layout);
89423 if(traceOut.visible === false) return;
89424
89425 coerce('bandwidth');
89426 coerce('side');
89427
89428 var width = coerce('width');
89429 if(!width) {
89430 coerce('scalegroup', traceOut.name);
89431 coerce('scalemode');
89432 }
89433
89434 var span = coerce('span');
89435 var spanmodeDflt;
89436 if(Array.isArray(span)) spanmodeDflt = 'manual';
89437 coerce('spanmode', spanmodeDflt);
89438
89439 var lineColor = coerce('line.color', (traceIn.marker || {}).color || defaultColor);
89440 var lineWidth = coerce('line.width');
89441 var fillColor = coerce('fillcolor', Color.addOpacity(traceOut.line.color, 0.5));
89442
89443 boxDefaults.handlePointsDefaults(traceIn, traceOut, coerce, {prefix: ''});
89444
89445 var boxWidth = coerce2('box.width');
89446 var boxFillColor = coerce2('box.fillcolor', fillColor);
89447 var boxLineColor = coerce2('box.line.color', lineColor);
89448 var boxLineWidth = coerce2('box.line.width', lineWidth);
89449 var boxVisible = coerce('box.visible', Boolean(boxWidth || boxFillColor || boxLineColor || boxLineWidth));
89450 if(!boxVisible) traceOut.box = {visible: false};
89451
89452 var meanLineColor = coerce2('meanline.color', lineColor);
89453 var meanLineWidth = coerce2('meanline.width', lineWidth);
89454 var meanLineVisible = coerce('meanline.visible', Boolean(meanLineColor || meanLineWidth));
89455 if(!meanLineVisible) traceOut.meanline = {visible: false};
89456};
89457
89458},{"../../components/color":52,"../../lib":178,"../box/defaults":299,"./attributes":424}],428:[function(_dereq_,module,exports){
89459/**
89460* Copyright 2012-2020, Plotly, Inc.
89461* All rights reserved.
89462*
89463* This source code is licensed under the MIT license found in the
89464* LICENSE file in the root directory of this source tree.
89465*/
89466
89467'use strict';
89468
89469var Lib = _dereq_('../../lib');
89470
89471// Maybe add kernels more down the road,
89472// but note that the default `spanmode: 'soft'` bounds might have
89473// to become kernel-dependent
89474var kernels = {
89475 gaussian: function(v) {
89476 return (1 / Math.sqrt(2 * Math.PI)) * Math.exp(-0.5 * v * v);
89477 }
89478};
89479
89480exports.makeKDE = function(calcItem, trace, vals) {
89481 var len = vals.length;
89482 var kernel = kernels.gaussian;
89483 var bandwidth = calcItem.bandwidth;
89484 var factor = 1 / (len * bandwidth);
89485
89486 // don't use Lib.aggNums to skip isNumeric checks
89487 return function(x) {
89488 var sum = 0;
89489 for(var i = 0; i < len; i++) {
89490 sum += kernel((x - vals[i]) / bandwidth);
89491 }
89492 return factor * sum;
89493 };
89494};
89495
89496exports.getPositionOnKdePath = function(calcItem, trace, valuePx) {
89497 var posLetter, valLetter;
89498
89499 if(trace.orientation === 'h') {
89500 posLetter = 'y';
89501 valLetter = 'x';
89502 } else {
89503 posLetter = 'x';
89504 valLetter = 'y';
89505 }
89506
89507 var pointOnPath = Lib.findPointOnPath(
89508 calcItem.path,
89509 valuePx,
89510 valLetter,
89511 {pathLength: calcItem.pathLength}
89512 );
89513
89514 var posCenterPx = calcItem.posCenterPx;
89515 var posOnPath0 = pointOnPath[posLetter];
89516 var posOnPath1 = trace.side === 'both' ?
89517 2 * posCenterPx - posOnPath0 :
89518 posCenterPx;
89519
89520 return [posOnPath0, posOnPath1];
89521};
89522
89523exports.getKdeValue = function(calcItem, trace, valueDist) {
89524 var vals = calcItem.pts.map(exports.extractVal);
89525 var kde = exports.makeKDE(calcItem, trace, vals);
89526 return kde(valueDist) / calcItem.posDensityScale;
89527};
89528
89529exports.extractVal = function(o) { return o.v; };
89530
89531},{"../../lib":178}],429:[function(_dereq_,module,exports){
89532/**
89533* Copyright 2012-2020, Plotly, Inc.
89534* All rights reserved.
89535*
89536* This source code is licensed under the MIT license found in the
89537* LICENSE file in the root directory of this source tree.
89538*/
89539
89540'use strict';
89541
89542var Lib = _dereq_('../../lib');
89543var Axes = _dereq_('../../plots/cartesian/axes');
89544var boxHoverPoints = _dereq_('../box/hover');
89545var helpers = _dereq_('./helpers');
89546
89547module.exports = function hoverPoints(pointData, xval, yval, hovermode, hoverLayer) {
89548 var cd = pointData.cd;
89549 var trace = cd[0].trace;
89550 var hoveron = trace.hoveron;
89551 var hasHoveronViolins = hoveron.indexOf('violins') !== -1;
89552 var hasHoveronKDE = hoveron.indexOf('kde') !== -1;
89553 var closeData = [];
89554 var closePtData;
89555 var violinLineAttrs;
89556
89557 if(hasHoveronViolins || hasHoveronKDE) {
89558 var closeBoxData = boxHoverPoints.hoverOnBoxes(pointData, xval, yval, hovermode);
89559
89560 if(hasHoveronKDE && closeBoxData.length > 0) {
89561 var xa = pointData.xa;
89562 var ya = pointData.ya;
89563 var pLetter, vLetter, pAxis, vAxis, vVal;
89564
89565 if(trace.orientation === 'h') {
89566 vVal = xval;
89567 pLetter = 'y';
89568 pAxis = ya;
89569 vLetter = 'x';
89570 vAxis = xa;
89571 } else {
89572 vVal = yval;
89573 pLetter = 'x';
89574 pAxis = xa;
89575 vLetter = 'y';
89576 vAxis = ya;
89577 }
89578
89579 var di = cd[pointData.index];
89580
89581 if(vVal >= di.span[0] && vVal <= di.span[1]) {
89582 var kdePointData = Lib.extendFlat({}, pointData);
89583 var vValPx = vAxis.c2p(vVal, true);
89584 var kdeVal = helpers.getKdeValue(di, trace, vVal);
89585 var pOnPath = helpers.getPositionOnKdePath(di, trace, vValPx);
89586 var paOffset = pAxis._offset;
89587 var paLength = pAxis._length;
89588
89589 kdePointData[pLetter + '0'] = pOnPath[0];
89590 kdePointData[pLetter + '1'] = pOnPath[1];
89591 kdePointData[vLetter + '0'] = kdePointData[vLetter + '1'] = vValPx;
89592 kdePointData[vLetter + 'Label'] = vLetter + ': ' + Axes.hoverLabelText(vAxis, vVal) + ', ' + cd[0].t.labels.kde + ' ' + kdeVal.toFixed(3);
89593
89594 // move the spike to the KDE point
89595 kdePointData.spikeDistance = closeBoxData[0].spikeDistance;
89596 var spikePosAttr = pLetter + 'Spike';
89597 kdePointData[spikePosAttr] = closeBoxData[0][spikePosAttr];
89598 closeBoxData[0].spikeDistance = undefined;
89599 closeBoxData[0][spikePosAttr] = undefined;
89600
89601 // no hovertemplate support yet
89602 kdePointData.hovertemplate = false;
89603
89604 closeData.push(kdePointData);
89605
89606 violinLineAttrs = {stroke: pointData.color};
89607 violinLineAttrs[pLetter + '1'] = Lib.constrain(paOffset + pOnPath[0], paOffset, paOffset + paLength);
89608 violinLineAttrs[pLetter + '2'] = Lib.constrain(paOffset + pOnPath[1], paOffset, paOffset + paLength);
89609 violinLineAttrs[vLetter + '1'] = violinLineAttrs[vLetter + '2'] = vAxis._offset + vValPx;
89610 }
89611 }
89612
89613 if(hasHoveronViolins) {
89614 closeData = closeData.concat(closeBoxData);
89615 }
89616 }
89617
89618 if(hoveron.indexOf('points') !== -1) {
89619 closePtData = boxHoverPoints.hoverOnPoints(pointData, xval, yval);
89620 }
89621
89622 // update violin line (if any)
89623 var violinLine = hoverLayer.selectAll('.violinline-' + trace.uid)
89624 .data(violinLineAttrs ? [0] : []);
89625 violinLine.enter().append('line')
89626 .classed('violinline-' + trace.uid, true)
89627 .attr('stroke-width', 1.5);
89628 violinLine.exit().remove();
89629 violinLine.attr(violinLineAttrs);
89630
89631 // same combine logic as box hoverPoints
89632 if(hovermode === 'closest') {
89633 if(closePtData) return [closePtData];
89634 return closeData;
89635 }
89636 if(closePtData) {
89637 closeData.push(closePtData);
89638 return closeData;
89639 }
89640 return closeData;
89641};
89642
89643},{"../../lib":178,"../../plots/cartesian/axes":222,"../box/hover":301,"./helpers":428}],430:[function(_dereq_,module,exports){
89644/**
89645* Copyright 2012-2020, Plotly, Inc.
89646* All rights reserved.
89647*
89648* This source code is licensed under the MIT license found in the
89649* LICENSE file in the root directory of this source tree.
89650*/
89651
89652'use strict';
89653
89654module.exports = {
89655 attributes: _dereq_('./attributes'),
89656 layoutAttributes: _dereq_('./layout_attributes'),
89657 supplyDefaults: _dereq_('./defaults'),
89658 crossTraceDefaults: _dereq_('../box/defaults').crossTraceDefaults,
89659 supplyLayoutDefaults: _dereq_('./layout_defaults'),
89660 calc: _dereq_('./calc'),
89661 crossTraceCalc: _dereq_('./cross_trace_calc'),
89662 plot: _dereq_('./plot'),
89663 style: _dereq_('./style'),
89664 styleOnSelect: _dereq_('../scatter/style').styleOnSelect,
89665 hoverPoints: _dereq_('./hover'),
89666 selectPoints: _dereq_('../box/select'),
89667
89668 moduleType: 'trace',
89669 name: 'violin',
89670 basePlotModule: _dereq_('../../plots/cartesian'),
89671 categories: ['cartesian', 'svg', 'symbols', 'oriented', 'box-violin', 'showLegend', 'violinLayout', 'zoomScale'],
89672 meta: {
89673
89674 }
89675};
89676
89677},{"../../plots/cartesian":235,"../box/defaults":299,"../box/select":306,"../scatter/style":412,"./attributes":424,"./calc":425,"./cross_trace_calc":426,"./defaults":427,"./hover":429,"./layout_attributes":431,"./layout_defaults":432,"./plot":433,"./style":434}],431:[function(_dereq_,module,exports){
89678/**
89679* Copyright 2012-2020, Plotly, Inc.
89680* All rights reserved.
89681*
89682* This source code is licensed under the MIT license found in the
89683* LICENSE file in the root directory of this source tree.
89684*/
89685
89686'use strict';
89687
89688var boxLayoutAttrs = _dereq_('../box/layout_attributes');
89689var extendFlat = _dereq_('../../lib').extendFlat;
89690
89691module.exports = {
89692 violinmode: extendFlat({}, boxLayoutAttrs.boxmode, {
89693
89694 }),
89695 violingap: extendFlat({}, boxLayoutAttrs.boxgap, {
89696
89697 }),
89698 violingroupgap: extendFlat({}, boxLayoutAttrs.boxgroupgap, {
89699
89700 })
89701};
89702
89703},{"../../lib":178,"../box/layout_attributes":303}],432:[function(_dereq_,module,exports){
89704/**
89705* Copyright 2012-2020, Plotly, Inc.
89706* All rights reserved.
89707*
89708* This source code is licensed under the MIT license found in the
89709* LICENSE file in the root directory of this source tree.
89710*/
89711
89712'use strict';
89713
89714var Lib = _dereq_('../../lib');
89715var layoutAttributes = _dereq_('./layout_attributes');
89716var boxLayoutDefaults = _dereq_('../box/layout_defaults');
89717
89718module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) {
89719 function coerce(attr, dflt) {
89720 return Lib.coerce(layoutIn, layoutOut, layoutAttributes, attr, dflt);
89721 }
89722 boxLayoutDefaults._supply(layoutIn, layoutOut, fullData, coerce, 'violin');
89723};
89724
89725},{"../../lib":178,"../box/layout_defaults":304,"./layout_attributes":431}],433:[function(_dereq_,module,exports){
89726/**
89727* Copyright 2012-2020, Plotly, Inc.
89728* All rights reserved.
89729*
89730* This source code is licensed under the MIT license found in the
89731* LICENSE file in the root directory of this source tree.
89732*/
89733
89734'use strict';
89735
89736var d3 = _dereq_('d3');
89737var Lib = _dereq_('../../lib');
89738var Drawing = _dereq_('../../components/drawing');
89739
89740var boxPlot = _dereq_('../box/plot');
89741var linePoints = _dereq_('../scatter/line_points');
89742var helpers = _dereq_('./helpers');
89743
89744module.exports = function plot(gd, plotinfo, cdViolins, violinLayer) {
89745 var fullLayout = gd._fullLayout;
89746 var xa = plotinfo.xaxis;
89747 var ya = plotinfo.yaxis;
89748
89749 function makePath(pts) {
89750 var segments = linePoints(pts, {
89751 xaxis: xa,
89752 yaxis: ya,
89753 connectGaps: true,
89754 baseTolerance: 0.75,
89755 shape: 'spline',
89756 simplify: true,
89757 linearized: true
89758 });
89759 return Drawing.smoothopen(segments[0], 1);
89760 }
89761
89762 Lib.makeTraceGroups(violinLayer, cdViolins, 'trace violins').each(function(cd) {
89763 var plotGroup = d3.select(this);
89764 var cd0 = cd[0];
89765 var t = cd0.t;
89766 var trace = cd0.trace;
89767
89768 if(trace.visible !== true || t.empty) {
89769 plotGroup.remove();
89770 return;
89771 }
89772
89773 var bPos = t.bPos;
89774 var bdPos = t.bdPos;
89775 var valAxis = plotinfo[t.valLetter + 'axis'];
89776 var posAxis = plotinfo[t.posLetter + 'axis'];
89777 var hasBothSides = trace.side === 'both';
89778 var hasPositiveSide = hasBothSides || trace.side === 'positive';
89779 var hasNegativeSide = hasBothSides || trace.side === 'negative';
89780
89781 var violins = plotGroup.selectAll('path.violin').data(Lib.identity);
89782
89783 violins.enter().append('path')
89784 .style('vector-effect', 'non-scaling-stroke')
89785 .attr('class', 'violin');
89786
89787 violins.exit().remove();
89788
89789 violins.each(function(d) {
89790 var pathSel = d3.select(this);
89791 var density = d.density;
89792 var len = density.length;
89793 var posCenter = posAxis.c2l(d.pos + bPos, true);
89794 var posCenterPx = posAxis.l2p(posCenter);
89795
89796 var scale;
89797 if(trace.width) {
89798 scale = t.maxKDE / bdPos;
89799 } else {
89800 var groupStats = fullLayout._violinScaleGroupStats[trace.scalegroup];
89801 scale = trace.scalemode === 'count' ?
89802 (groupStats.maxKDE / bdPos) * (groupStats.maxCount / d.pts.length) :
89803 groupStats.maxKDE / bdPos;
89804 }
89805
89806 var pathPos, pathNeg, path;
89807 var i, k, pts, pt;
89808
89809 if(hasPositiveSide) {
89810 pts = new Array(len);
89811 for(i = 0; i < len; i++) {
89812 pt = pts[i] = {};
89813 pt[t.posLetter] = posCenter + (density[i].v / scale);
89814 pt[t.valLetter] = valAxis.c2l(density[i].t, true);
89815 }
89816 pathPos = makePath(pts);
89817 }
89818
89819 if(hasNegativeSide) {
89820 pts = new Array(len);
89821 for(k = 0, i = len - 1; k < len; k++, i--) {
89822 pt = pts[k] = {};
89823 pt[t.posLetter] = posCenter - (density[i].v / scale);
89824 pt[t.valLetter] = valAxis.c2l(density[i].t, true);
89825 }
89826 pathNeg = makePath(pts);
89827 }
89828
89829 if(hasBothSides) {
89830 path = pathPos + 'L' + pathNeg.substr(1) + 'Z';
89831 } else {
89832 var startPt = [posCenterPx, valAxis.c2p(density[0].t)];
89833 var endPt = [posCenterPx, valAxis.c2p(density[len - 1].t)];
89834
89835 if(trace.orientation === 'h') {
89836 startPt.reverse();
89837 endPt.reverse();
89838 }
89839
89840 if(hasPositiveSide) {
89841 path = 'M' + startPt + 'L' + pathPos.substr(1) + 'L' + endPt;
89842 } else {
89843 path = 'M' + endPt + 'L' + pathNeg.substr(1) + 'L' + startPt;
89844 }
89845 }
89846 pathSel.attr('d', path);
89847
89848 // save a few things used in getPositionOnKdePath, getKdeValue
89849 // on hover and for meanline draw block below
89850 d.posCenterPx = posCenterPx;
89851 d.posDensityScale = scale * bdPos;
89852 d.path = pathSel.node();
89853 d.pathLength = d.path.getTotalLength() / (hasBothSides ? 2 : 1);
89854 });
89855
89856 var boxAttrs = trace.box;
89857 var boxWidth = boxAttrs.width;
89858 var boxLineWidth = (boxAttrs.line || {}).width;
89859 var bdPosScaled;
89860 var bPosPxOffset;
89861
89862 if(hasBothSides) {
89863 bdPosScaled = bdPos * boxWidth;
89864 bPosPxOffset = 0;
89865 } else if(hasPositiveSide) {
89866 bdPosScaled = [0, bdPos * boxWidth / 2];
89867 bPosPxOffset = boxLineWidth * {x: 1, y: -1}[t.posLetter];
89868 } else {
89869 bdPosScaled = [bdPos * boxWidth / 2, 0];
89870 bPosPxOffset = boxLineWidth * {x: -1, y: 1}[t.posLetter];
89871 }
89872
89873 // inner box
89874 boxPlot.plotBoxAndWhiskers(plotGroup, {pos: posAxis, val: valAxis}, trace, {
89875 bPos: bPos,
89876 bdPos: bdPosScaled,
89877 bPosPxOffset: bPosPxOffset
89878 });
89879
89880 // meanline insider box
89881 boxPlot.plotBoxMean(plotGroup, {pos: posAxis, val: valAxis}, trace, {
89882 bPos: bPos,
89883 bdPos: bdPosScaled,
89884 bPosPxOffset: bPosPxOffset
89885 });
89886
89887 var fn;
89888 if(!trace.box.visible && trace.meanline.visible) {
89889 fn = Lib.identity;
89890 }
89891
89892 // N.B. use different class name than boxPlot.plotBoxMean,
89893 // to avoid selectAll conflict
89894 var meanPaths = plotGroup.selectAll('path.meanline').data(fn || []);
89895 meanPaths.enter().append('path')
89896 .attr('class', 'meanline')
89897 .style('fill', 'none')
89898 .style('vector-effect', 'non-scaling-stroke');
89899 meanPaths.exit().remove();
89900 meanPaths.each(function(d) {
89901 var v = valAxis.c2p(d.mean, true);
89902 var p = helpers.getPositionOnKdePath(d, trace, v);
89903
89904 d3.select(this).attr('d',
89905 trace.orientation === 'h' ?
89906 'M' + v + ',' + p[0] + 'V' + p[1] :
89907 'M' + p[0] + ',' + v + 'H' + p[1]
89908 );
89909 });
89910
89911 boxPlot.plotPoints(plotGroup, {x: xa, y: ya}, trace, t);
89912 });
89913};
89914
89915},{"../../components/drawing":74,"../../lib":178,"../box/plot":305,"../scatter/line_points":403,"./helpers":428,"d3":16}],434:[function(_dereq_,module,exports){
89916/**
89917* Copyright 2012-2020, Plotly, Inc.
89918* All rights reserved.
89919*
89920* This source code is licensed under the MIT license found in the
89921* LICENSE file in the root directory of this source tree.
89922*/
89923
89924'use strict';
89925
89926var d3 = _dereq_('d3');
89927var Color = _dereq_('../../components/color');
89928var stylePoints = _dereq_('../scatter/style').stylePoints;
89929
89930module.exports = function style(gd) {
89931 var s = d3.select(gd).selectAll('g.trace.violins');
89932
89933 s.style('opacity', function(d) { return d[0].trace.opacity; });
89934
89935 s.each(function(d) {
89936 var trace = d[0].trace;
89937 var sel = d3.select(this);
89938 var box = trace.box || {};
89939 var boxLine = box.line || {};
89940 var meanline = trace.meanline || {};
89941 var meanLineWidth = meanline.width;
89942
89943 sel.selectAll('path.violin')
89944 .style('stroke-width', trace.line.width + 'px')
89945 .call(Color.stroke, trace.line.color)
89946 .call(Color.fill, trace.fillcolor);
89947
89948 sel.selectAll('path.box')
89949 .style('stroke-width', boxLine.width + 'px')
89950 .call(Color.stroke, boxLine.color)
89951 .call(Color.fill, box.fillcolor);
89952
89953 var meanLineStyle = {
89954 'stroke-width': meanLineWidth + 'px',
89955 'stroke-dasharray': (2 * meanLineWidth) + 'px,' + meanLineWidth + 'px'
89956 };
89957
89958 sel.selectAll('path.mean')
89959 .style(meanLineStyle)
89960 .call(Color.stroke, meanline.color);
89961
89962 sel.selectAll('path.meanline')
89963 .style(meanLineStyle)
89964 .call(Color.stroke, meanline.color);
89965
89966 stylePoints(sel, trace, gd);
89967 });
89968};
89969
89970},{"../../components/color":52,"../scatter/style":412,"d3":16}],435:[function(_dereq_,module,exports){
89971/**
89972* Copyright 2012-2020, Plotly, Inc.
89973* All rights reserved.
89974*
89975* This source code is licensed under the MIT license found in the
89976* LICENSE file in the root directory of this source tree.
89977*/
89978
89979'use strict';
89980
89981// package version injected by `npm run preprocess`
89982exports.version = '1.54.4';
89983
89984},{}]},{},[11])(11)
89985});